Sorry for the lengthy post, but I just wanted to share all of the trouble shooting and progress I made up until this point. This error has been persistent for over a week.
I have a line chart supplemented with three buttons representing three days of data. I have the buttons mapped to three .txt files and a d3 event listener:
var fileMap = {
'Day 1':'2018-05-17.txt',
'Day 2':'2018-05-18.txt',
'Day 3':'2018-05-19.txt'
}
d3.selectAll('.button').on('click', function(d) {
var dayValue = this.innerHTML;
var thisFile = fileMap[dayValue];
createChart(thisFile);
});
So the idea is you can click the button and the graph will update itself. I kept running into trouble while implementing the .transition() call. I ended up using this, which works:
before calling the main createGraph function, I have a variable that counts how many times we create a graph:
var graphCount = 0;
Then within the scope of the createGraph function I have a counter:
graphCount +=1;
Then I have the logic necessary to draw the graph (initial state) or simply transition (if already drawn):
if (graphCount>1) {
xScale.domain(d3.extent(data, function(d) {return (d.date)}));
yScale.domain(d3.extent(data, function(d) {return (d.y2)}));
d3.selectAll(".line")
.data([data])
.transition() // change the line
.duration(750)
.attr("d", graphLine);
d3.selectAll("g.y.axis").transition() // change the y axis
.duration(750)
.call(yAxis);
} else {
svg.append('path')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')')
.datum(data)
.attr('class', 'line')
.attr('d', graphLine);
var yAxisNodes = svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')')
.call(yAxis);
}
Maybe it's not the most elegant way to handle update functionality. I admit it seems a little crude, but at least it does work.
Red Flags:
X Axis: First off, the x axis isn't visible in my graph, but that's by design. I have a tooltip that shows the x axis value, but I have omiited the tooltip for simplicity.
The X axis is a time dimension, the .txts use unix time, which I convert using new Date(((split[0]*300)+rawTime)*1000). Meaning that instead of hour:minutes (as I originally wanted) all my d.dates are full fledged dates. Here is an example slice:
data[0]->Object->date: Thu May 17 2018 09:35:00 ….
I have a few timeFormat variables as well, but oddly when I try to call d3.time.format("%H:%M") the graph isn't drawn. So in my code they are not called. Since I don't need a visible x axis, I have let this go. The graph works fine when I don't use timeFormat and use the full fledged date from new Date().
The problem: Whenever a button is clicked, the graph updates successfully -- new data is read from the respective .txt but for some reason I'm getting over 40 errors reading:
Error: attribute d: Expected number, "12342542342352452, Nan".
Which makes no sense because the graph is plotted fine. As noted earlier, the unix dates have all been successfully parsed into javascript date objects. I passed the data to the console to examine just to be safe, and yes, it seems there are no parsing errors. All the dates and all the y axis values are there.
I'm not sure what to do, since a few button clicks will result in the log showing 100+ errors. I don't think that's good for browswer stability, but on the other hand, the graph appears to work just fine.
Question: Why am I getting 40+ errors in the log? My data set is around 80 observations long. I noticed it only throws errors during transitions. The initial graph creation doesn't have any errors.
Please, please make the errors go awayyyy.
I have a very minimal example here:
https://plnkr.co/edit/Zq9Yd6Hf7mlTlegMXiIG
Hunches
I also thought it could be the xAxis itself. But I passed
d3.extent(data, function(d) {return d.date
into the console log, and everything checked out again. Very strange.
The errors are due only to an empty last row in the txt files, giving you a last object like this in the arrays:
{y2: NaN, date: Wed May 16 2018 23:35:00}
Of course, you're not getting anywhere with that NaN.
Solution: remove those empty rows. After that your arrays have the correct number of objects (77 versus 78 in the running code you linked)
Here is the updated Plunker: https://plnkr.co/edit/XdOcqsdCG3kXw8cvja9y?p=preview
PS: regarding that graphCount and your supposition...
Maybe it's not the most elegant way to handle update functionality.
Yes, it's certainly not the best way. There is a much better approach: the famous enter-update-exit pattern! Get rid of that awkward counter and the if...else statement!
Related
I have 1000 values in no particular order but I'd like to format them into a normal distribution to plot on a histogram using google-charts.
I've tried using d3.js and I got it working just based off some examples but it looks extremely ugly and I don't have enough time to learn d3 in and out to get the results I want. Google-charts visual format are great.
The problem is google-charts expects data in a format where each value has a name along with headers. So when I organized it into this:
'dsSample1': [
['price', 'number'],
['price', 11386.057139142767],
['price', 27659.397260273952],
['price', 44159.39726027395],
...
from
'dsSample2': [
11386.057139142767,
27659.397260273952,
44159.39726027395,
28026.04112639835,
...
google charts works, but I get the following:
This is as close as I've come to getting it working in d3: https://jsfiddle.net/0jtrq17x/1/. It works but it's extremely ugly.
I've managed to arrange the array data into bins using some d3 code but it is imcompatible with google-charts and I don't know to make it compatible, and also don't know how to format the data so it plays nice with google-charts histogram
this code
var values = this.hypo.dsSample2.map(x => {
return x + 128608.42487322348
})
var max = d3.max(values)
var min = d3.min(values)
var x = d3.scale.linear()
.domain([min, max])
.range([0, 800]);
var histGenerator = d3.layout.histogram()
.bins(x.ticks(100))
(values)
this.data1 = histGenerator
returns this array transformation
My problem is I don't know how to massage my array of data so I can get something like this in google-charts:
there are two data formats for the google charts version.
a single series format, with the names,
or a multi-series format, with just the numbers.
it is ok to use the multi-series format with a single series.
so, assigning names is not required.
but you will have to convert each value to its own array.
'dsSample1': [
[11386.057139142767],
[27659.397260273952],
[44159.39726027395],
...
from
'dsSample2': [
11386.057139142767,
27659.397260273952,
44159.39726027395,
...
you can use the map method to format the data.
dsSample.map(function (value) {
return [value];
});
see following fiddle...
https://jsfiddle.net/x684f1vs/
I know you have decided against D3, but since your question is still tagged with d3.js, I will post an answer using D3 anyways :)
I have made an updated JSFiddle, with an adaption of your code:
https://jsfiddle.net/w7r80cfo/1/
In short, to manipulate this histogram, look to the following lines:
1038 and 1039 to change the dimensions (width and height respectively) of the visualization. The values given are in pixels.
1049 to change the number of buckets for you histogram. Currently it is set to 100.
1083 to change the width of the individual bars. Currently, I've set it to 0.25 of the space calculated for each bar. If you e.g. change 0.25 to 1 the bars will be so wide, they will be drawn right next to each other.
1085 to change the color of the bars. Currently they are given a darker shade of red the higher number of values they represent. If you want e.g. just blue, change the line to .attr("fill", "steelblue")
Play around with these values and see if you can get to a chart that is close to what you want.
To elaborate a bit on the changes I've made, they consist mainly of the following:
Line 1038: lowered the width to 600.
Line 1073: updated to position the visualization correctly:
.attr('transform', `translate(${margin.left},${margin.top})`);
Line 1083: lowered the width of the bars by multiplying by 0.25:
.attr("width", (x(data[0].dx) - x(0)) * 0.25)
Other than that I have removed the following code to remove the text labels, as they indeed made the chart look messy:
bar.append("text")
.attr("dy", ".75em")
.attr("y", -12)
.attr("x", (x(data[0].dx) - x(0)) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return "$" + d3.format(",.2f")(d.x); });
Besides this, I have added an y axis and changed the way the axes are drawn in order to make them look a bit nicer. I can go into detail about these changes, but I think they are of lesser interest to your goal.
Hope this helps!
I am new to d3 and working with line chart, where i want to view the chart between the threshold values Fiddler. The chart works fine with y.domain([min,max]), since i want to view the chart with my own threshold values the line runs off the chart, i've no idea why this is happening. Any help is much appreciated.
If you want to avoid the lines bleeding over the graph area, what you might want is using a clip-path. I tweaked your code a little bit and added the clip path to it.
svg.append("clipPath")
.attr("id", "clip-path")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("path") // Add the valueline path.
.attr("d", valueline(data))
.attr("clip-path", "url(#clip-path)");
Here is the JSFiddle link for the same: http://jsfiddle.net/vxo1qsf9/2/
Please let me know if this is what you are looking for. :)
The reason why you get a broken graph is that, the values after 26 April is less than 75 which is the minimum you have defined. So the graph is actually drawing, but you can't see the lines because it is below the svg area you defined.
Here is my 2 cents.
Just update the data with correct values. So that you can view the data at the end.
{
date: "1-May-12",
close: "90.13"
}
or
Just update the min value to the minimum expected value.
var thresholdValues = {
minValue: 50,
maxValue: 100
};
This is what I did and seems it works fine. Here is the fiddle link for the same http://jsfiddle.net/vxo1qsf9/1/
If you want to automatically find the minimum and maximum value from data, use d3's native function d3.extent(), which returns an array containing min and max value of that array or you can also try finding min and max separately by using d3.min() and d3.max().
Here is the documentation link for d3 array methods.
Hope this helps! :)
Dashboard 1: https://bl.ocks.org/diggetybo/073f2f9b4b9a0c88211c9c643e47e9a9
Dashboard 2: https://bl.ocks.org/diggetybo/c16c1168705ea18b7d8711c15e90f8ec
I would love if someone could explain why the gridlines in the second link on the bottom right-hand graph are of a higher tick interval (or whatever you want to call it) than the ones in the first link. The d3 template is one I made a few weeks ago and it's just something I'm copying and pasting to use with different data sets. However, despite hours of double checking everything, I'm still not sure why it's doing that.
The only thing that's different is the data, but they are of very similar types of data, the domains should be similar. For example, the bottom right hand graph is all positive number for both dashboards. Maybe it's being parsed differently for some strange reason?
Let me know how you did it, whether you brute forced the styling of the gridlines, or if you found the trigger of the problem. That is to say, why it chose to add so many more gridlines on that graph but none of the others.
Thank you
To regulate the number of ticks use ticks(4)
read here this will generate 4 ticks in x axis.
var xGridlineNodes = svg.append('g')
.attr('transform', 'translate(' + margins.left + ',' + (totalHeight - margins.bottom + axisPadding) + ')')
.call(xGridlinesAxis.tickSize(-graphHeight - axisPadding, 0, 0).tickFormat("").ticks(4));
The reason why there are more ticks in dashboard2 than in dashboard1 is because of the different data sets (there is no css tricks in that). May be some expert can throw some light on why more ticks.
Here is a working plunk in which i have regulated the number of ticks
If you know what the tick values will be then use tickValues read here
I am new to D3.js. However after being practiced the examples of this site, I tried to play ahead with Mr.John Coogan's map given here. The output that I found in his site is as under
But when I am trying to do the same thing by placing his .js,css,.json and index.html in plunker it is coming as
Problems
a) No States are getting displayed
b) Zoom and Pan is not working
In another word, at this point of time I am looking only for the Indian map to work exactly as the example shown by Mr. Coogan.
What needs to be done for this?
Here's the working plunk: http://plnkr.co/1EqpIFecwJmkbvypTyQD?p=preview
You needed to uncomment this line:
.call(d3.behavior.zoom().on("zoom", redraw))
on line 40 of the index.html in your plunk, and then zoom and pan will work.
The state colors (based on wealth) are not showing because of various, more complex errors. The error shown in the console (svg is not defined referencing line 78) is just the start (you need to replace svg with india, which is defined).
In fact the whole original gist your example is based on is really just a work in progress, but most of the answers for how to fix it can be found in this thread from the google group, from Mike Bostock himself.
Essentially, the json data loads asynchronously, so need to be loaded in series.
// load the two JSON files in series
d3.json("states.json", function(states) {
d3.json("wealth.json", function(wealthdata) {
...
});
});
Then you can just apend the relevant colorbrewer CSS class when you first create each path:
india.selectAll("path")
.data(states.features)
.enter().append("path")
.attr("d", path)
.attr("class", function(d) {
return "q" + quantize(wealthdata[d.id]) + "-9";
})
.attr("d", path);
But you also need to define the quantize scale, range...:
var quantize = d3.scale.quantize()
.range(d3.range(9));
... and domain (which you can only do once the data has been loaded:
quantize.domain([0, d3.max(d3.values(wealthdata))]);
I have a working d3.js visualization that shows data return rates as a composite line and area graph (http://anf.ucsd.edu/tools/data_return_rates/).
The x-axis is time, the y-axis is percent data return rates. You can click various buttons at the top of the visualization to switch between datasets (just parsed JSON files).
In addition to the raw data plot, I want to add a simple line that defines the minimum data return rate required (85%). This is purely a visual aid to help users determine if the data return rates are above this minimum/threshold value. I calculate the x-values (time) for this 'minima'-line (only two points) using the d3.min() and d3.max() methods on the dataset. The y-values are just integers (85):
var min_data_return = [
{
"readable_time": d3.min(data, function(d) { return d.readable_time; }),
"value": 85
},
{
"readable_time": d3.max(data, function(d) { return d.readable_time; }),
"value": 85
}
]
(I do some other transformations to make sure everything plots okay)
Now, before I wanted this minima line added to the visualization, I just did the following to create the area and line plot, which worked:
svg.select("path.area").data([data]);
svg.select("path.line").data([data]);
There is some other plotting code later in the script:
svg.select("path.area").attr("d", area);
svg.select("path.line").attr("d", line);
All the d3.js examples I have read say that to create multiple lines, you just need to make your data array have all the datasets you want to plot, so in my example above, this:
svg.select("path.line").data([data]);
Becomes:
svg.select("path.line").data([data, min_data_return]);
And this should work. However, it doesn't. I see the data set line plotted as before, but not the min_data_return line.
Any ideas what I am doing wrong?
Gist here: https://gist.github.com/2662793
In the Gist, look at lines 133 - 140 (search for the commented string OPTION). These are the only lines that are relevant to getting this working. I put the whole script into the Gist for completeness.
Thanks in advance!
Instead of trying to draw both the reference line and the data line together, consider drawing them separately:
svg.append("svg:path")
.attr("class", "minline")
.attr("clip-path", "url(#clip)");
...
svg.select("path.minline").data([min_data_return]);
...
svg.select("path.minline").attr("d", line);
JSfiddle here with full example: http://jsfiddle.net/qAHC2/6/