d3 chart refresh duplicate - javascript

I´m using d3.js library but i´m having so problems with linear graphics. It looks like on each page refresh, the graphics is replicated, and after some running time i got lots of duplicated graphics. I´m also using static data (i entered some random data for the graphic to work). Can someone help me? Why is this happening? Any solutions?
chart3-controller.js
module.exports = function Chart3Ctrl($scope) {
d3 = require('d3')
$scope.chart = function() {
var arrData = [
["2012-10-02", 200],
["2012-10-09", 300],
["2012-10-12", 150]
];
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y-%m-%d").parse;
var x = d3.time.scale()
.range([0, width])
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.close);
});
var svg = d3.select("#chart3").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top +
")");
var data = arrData.map(function(d) {
return {
date: parseDate(d[0]),
close: d[1]
};
});
console.log(data);
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain(d3.extent(data, function(d) {
return d.close;
}));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
}
}
chart3.pug
.widget-container.fluid-height.stf-cpu(ng-controller='Chart3Ctrl')
.heading
i.fa
span(translate) Chart 3
.widget-content.padded
div#chart3(style='width:100%; height:100%;')
{{ chart() }}
Please any help welcome! :)
Thanx

Have you tried to remove the chart at the top of the function?
so;
d3.selectAll('#chart3 svg').remove();
this would remove any instance of the chart before re drawing, a better approach might be to wrap inside an if statement to check if the svg exists first and then drawing it if it doesn't, using jquery this could be ;
if(!$('#chart3 svg').length > 0) {
run code here...
}

Related

D3 line and chart graphs on the same page breaking

I am trying to render 2 graphs on the same page: chart graph and a linear graph
in html i have two divs which bind to the 2 different graphs respectively like so:
<div id="svg-container">
<div id="svg-container-avg">
The first graph that i import in html is this linear 'average' graph that just doesn't display the path or the x axis.
It works fine if i delete the chart graph which is imported straight after. I would automatically assume that there are some sort of dependencies between the two graphs, but i cannot find anything...
my first import, linear graph: scns-avg.js file contains this
// Set the dimensions of the canvas / graph
var margin = {
top: 30,
right: 20,
bottom: 70,
left: 50
},
width = 800 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
var valueline = d3.svg.line()
.x(function(d) {
return x(d.scnsID);
})
.y(function(d) {
return y(d.average);
});
var svg2 = d3.select("#svg-container-avg")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Get the data
d3.json("scns-avg-data-retrieval.php", function(error, data) {
data.forEach(function(d) {
d.scnsID = d.scnsID;
d.average = +d.average;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) {
return d.scnsID;
}));
y.domain([0, d3.max(data, function(d) {
return d.average;
})]);
svg2.append("path") // Add the valueline path.
.style("stroke", "rgba(13, 183, 196, 0.9)")
.attr("d", valueline(data));
svg2.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", function(d) {
return x(d.scnsID);
})
.attr("cy", function(d) {
return y(d.average);
})
svg2.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg2.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
});
My Data structor in JSON:
[{"date":"11-Mar-16","average":"3.18","scnsID":"2"},{"date":"12-Mar-16","average":"3.09","scnsID":"3"},{"date":"15-Mar-16","average":"3.16","scnsID":"4"},{"date":"17-Mar-16","average":"3.20","scnsID":"5"}]
When trying to run the html page with both graphs being imported the scns-avg.js throws up in the console Error: Invalid value for attribute d="M43,2.8124999999999902LNaN,15.46875000000002LNaN,5.6249999999999805L471,0"
which points to valueline(data) in this part of the code
svg2.append("path")
.style("stroke", "rgba(13, 183, 196, 0.9)")
.attr("d", valueline(data));
That is dependent on this piece of code, so the problem must be lying here, but for 6 hours now i can't find the solution to this..
var valueline = d3.svg.line()
.x(function(d) { return x(d.scnsID); })
.y(function(d) { return y(d.average); });
My chart graph that gets imported next and if disabled the linear graph renders properly is as follows below:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Frequency:</strong> <span style='color:rgba(13, 183, 196, 0.9)'>" + d.average + "</span>";
});
var svg = d3.select("#svg-container").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.call(tip);
// Get the data
d3.json("scns-data-retrieval.php", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.question = d.question;
d.average = +d.average;
});
x.domain(data.map(function(d) { return d.question; }));
y.domain([0, 5]);
legendSpace = width/5;
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x", 0 - (height / 3))
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Level of need for help:");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.question); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.average); })
.attr("height", function(d) { return height - y(d.average); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
});
function type(d) {
d.average = +d.average;
return d;
};
data structure in JSON:
[{"question":1,"average":3.3333333333333},{"question":2,"average":2.5},{"question":3,"average":4},{"question":4,"average":2.75},{"question":5,"average":2.75},{"question":6,"average":2.75},{"question":7,"average":3},{"question":8,"average":3},{"question":9,"average":2.75},{"question":10,"average":3.25},{"question":11,"average":3.25},{"question":12,"average":3.5},{"question":13,"average":3},{"question":14,"average":3.25},{"question":15,"average":3.5},{"question":16,"average":3.5},{"question":17,"average":3.25},{"question":18,"average":3.75},{"question":19,"average":3.5},{"question":20,"average":3},{"question":21,"average":3},{"question":22,"average":3.5},{"question":23,"average":3.25},{"question":24,"average":3.75},{"question":25,"average":3.75},{"question":26,"average":3.75},{"question":27,"average":3.5},{"question":28,"average":2.75},{"question":29,"average":2.25},{"question":30,"average":3.5},{"question":31,"average":3},{"question":32,"average":3}]
I can't do this anymore and it's driving me crazy so i am now turning to the power of stackoverflow!
After some mad research and trial and error i have fixed this problem.
When you are trying to render more than 1 graph, if they are of different type and not lets say - multiple linear graphs, but something like a linear graph with a bar chart the variable scope is getting mixed up and it's not restricted to the javascript file once it's imported into html.
To fix this problem every graph js file has to wrapped in (function(){ All your graph code goes here })();
This restricts the variable scope to just the function.

Filter with d3.js

I have a big problem.
I use d3 to visualize my (poll)data. (So Questions and answers) It works fine except one problem.
Sometimes it doesn't visualize the data like they are. For example one time d3 get all right data about d3.json. But the graph is not right. It should load 300 answers in the first bars but it just show 50. I tested it and the json works.
I tried to filter the graph and I got the same problem.
This is my d3 code.
<div>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
var margin = {
top: 20,
right: 30,
bottom: 30,
left: 430
},
width = 1400 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var xScale = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var yScale = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(10);
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("class", "chart");
var chart = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("getdata.php", function(error, data) {
xScale.domain(data.map(function(d) {
return d.text;
}));
yScale.domain([0, d3.max(data, function(d) {
return d.count;
})]);
chart.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return xScale(d.text);
})
.attr("y", function(d) {
return yScale(d.count);
})
.attr("height", function(d) {
return height - yScale(d.count);
})
.attr("width", xScale.rangeBand())
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Antworten");
});
function type(d) {
d.count = +d.text;
return d;
}
</script>
</div>
I guess the problem starts here. But I don't know why. The problem doesn't appear at every question. Only at a few or when I try to use my filter.
Maybe it happens when two values have a big difference?
Update:
It is like: D3 doesn't want to lose the smallest bar. So other bars must based on that smallest. And then the values are not correct for the bigger bars. Maybe the Y scale depends on the smallest bar.

Line chart in D3 is all over the place

everyone! Currently trying to generate a line chart using D3 with a simple JSON data. I'm using mbostock's line chart code, I just change data.tsv with my own JSON file:
[
{"date":"Aug-14","close":"1"},
{"date":"Feb-14","close":"2"},
{"date":"Jan-14","close":"1"},
{"date":"Jul-14","close":"3"},
{"date":"Jun-14","close":"1"},
{"date":"Mar-14","close":"3"},
{"date":"May-14","close":"1"},
{"date":"Nov-14","close":"1"},
{"date":"Oct-14","close":"1"},
{"date":"Sep-14","close":"1"}
]
However, I get a graph that looks like this crazy line chart
And I have no idea why it's drawing the lines like that. Any input would be amazing!
Edit: Adding the current code I'm using.
<script>
//Setting up...
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("test.php", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
console.log(d.date);
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
});
</script>
Hard to tell without looking directly at your code, but you might not be passing a Number into your scale function. In the source code you copied, see:
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
Or, just pass in data where close is a Number.
[
{"date":"Aug-14","close":1},
{"date":"Feb-14","close":2},
{"date":"Jan-14","close":1},
{"date":"Jul-14","close":3},
{"date":"Jun-14","close":1},
{"date":"Mar-14","close":3},
{"date":"May-14","close":1},
{"date":"Nov-14","close":1},
{"date":"Oct-14","close":1},
{"date":"Sep-14","close":1}
]
Edit:
Note also that the dates in the example you linked are sorted, yours are not.
var parseDate = d3.time.format("%b-%y").parse;
data.sort(function(a, b) {
return d3.ascending(parseDate(a.date), parseDate(b.date));
});

Add new D3 chart above older ones

I currently have a web app which adds a new d3 chart to the page upon every click of submit (after defining parameters). What I'd like to do is add the newest chart above the others and push the older ones down to allow space. It tried changing the svg append to prepend but it's obviously not going to be that simple!
Here's my script;
function chartGenerator(file_location)
{
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
var svg = d3.select("body").prepend("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("{% static 'dgconnection/data.csv' %}", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
svg.prepend("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.predend("g")
.attr("class", "y axis")
.call(yAxis)
.prepend("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value");
svg.prepend("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
});
}
You can prepend elements to existing elements in D3, but the function isn't called prepend(), but insert() and takes two arguments. The first is, like for .append(), the name of the element to insert. The second is a selector. The new element will be inserted before the element that matches the selector.
In your case, the only code you need to change is this:
var svg = d3.select("body").insert("svg", "svg")

Error parsing d3 line chart JSON data

Having issues with getting JSON data on a line chart, i can do this for a bar chart totally fine but for some reason it gives me an error when doing a line chart. There isn't much documentation around so see if anyone knows.
I get this error
Error: Problem parsing d="MNaN,400LNaN,258.65447419986936LNaN,221.90289571086436LNaN,183.32244720226433LNaN,134.29131286740693LNaN,149.70607446113652LNaN,63.1395602003048LNaN,37.44829087742215LNaN,69.40997169605924LNaN,0LNaN,169.91073372523405LNaN,643.2397126061397"
JSON data is as follows:
[{"logins":"3333","month_name":"January"},{"logins":"4956","month_name":"February"},{"logins":"5378","month_name":"March"},{"logins":"5821","month_name":"April"},{"logins":"6384","month_name":"May"},{"logins":"6207","month_name":"June"},{"logins":"7201","month_name":"July"},{"logins":"7496","month_name":"August"},{"logins":"7129","month_name":"September"},{"logins":"7926","month_name":"October"},{"logins":"5975","month_name":"November"},{"logins":"540","month_name":"December"}]
Now the code:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = $("svg").parent().width();
height = $("svg").parent().height();
aspect = 500 / 950;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.month_name); })
.y(function(d) { return y(d.logins); });
var svg = d3.select(document.createElement("div")).append("svg")
.attr("preserveAspectRatio", "xMidYMid")
.attr("viewBox", "0 0 950 500")
.attr("width", width)
.attr("height", width * aspect)
.attr("id", "art_chart")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(d3.extent(data, function(d) { return d.month_name; }));
y.domain(d3.extent(data, function(d) { return d.logins; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Logins");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
Anyone know how to fix it so it displays the line chart?
You need to parse your dates and pass in the numbers as numbers:
data.forEach(function(d) {
d.month_name = d3.time.format("%B").parse(d.month_name);
d.logins = +d.logins;
});
If you run this code just after loading the JSON, everything should work fine.

Categories