D3 adding dots to multiple stacked line charts - javascript

I'm creating multiple line-graphs that are stacked by "symbol" from a csv-file.
This is how the csv looks-like:
symbol,date,price
Agency,2015/5/6,33
Agency,2015/5/7,29
Agency,2015/5/8,32
test,2015/5/6,23
test,2015/5/7,19
test,2015/5/8,22
example,2015/5/6,43
example,2015/5/7,49
example,2015/5/8,42
So for each symbol contained in the csv-file a separate graph gets drawn. This all works fine so far.
But I'm struggling to add dots to each line.
This is the part that's creating the dots (line 142 - 148 in my fiddle):
var circles = svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 2)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.price); })
.style("fill", "black");
But this code creates the dots populated by the values of the last "symbol" off the csv and not separate for each "symbol".
I researched a lot here on stackoverflow, but none of the solutions or hints I found worked for me.
In this example http://jsbin.com/isuris/1/edit?html,output I found this part:
// add circles
pathContainers.selectAll('circle')
.data(function (d) { return d; })
.enter().append('circle')
.attr('cx', function (d) { return xScale(d.x); })
.attr('cy', function (d) { return yScale(d.y); })
.attr('r', 3);
So I tried to change line 143 of my fiddle http://fiddle.jshell.net/doyL1L0p/1/ like this:
.data(function (d) { return d; })
But this didn't work, the "wrong" dots disappear.
So I tried this:
.data(function (d) { return d.key; })
because the separate "symbol"-value gets displayed by using "d.key". But know I get this error in the console:
Unexpected value NaN for attribute cx
Unexpected value NaN for attribute cy
Then I tried to alter lines 146 and 147:
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.price); })
but without success.
I tried many other posted "solutions" off stackoverflow and others I found in the web, but all without success again.
Can anyone please give me a hint on how to solve this?
Heres the fiddle again:
http://fiddle.jshell.net/doyL1L0p/1/

Based on some editing of your fiddle, it seems that they ARE actually being displayed... but they're out of range of the canvas.
If you look at the outcome here, you can see that the newly added point near the top is displayed, on all of the graphs.
I'll see if I can work on a proper fix, because obviously this is not the desired behaviour.
UPDATE
It looks like the problem is that you aren't updating your domain when you display your dots, so they are using the last domain which was set (hence why only last show normally). In addition, you are displaying dots for the entire data set on each graph (although most of them are outside the domain).
I would actually recommend trying to parse your data into an array for each unique symbol, it make things much more straightforward for you. For example, you could simply set the domain using d3.extent.
SUMMARY
A couple of solutions spring to mind:
Setting up separate axes/domains per graph.
Putting your dots into a g element per symbol, then adding data to them via a nested selection.
This works, but you really should read up a bit more on (nested) selections. Ideally, you would probably want to separately generate each graph, out of scope of the others. This solution is badly designed, but hopefully it should help to give you an idea of how your code is working.

Related

Why Do I need to use Custom Event for Tooltip and Brush in D3?

I have tried to break down the code for applying tooltip on the brush in D3.
And the code I'm looking into seems like using custom event.
However, I don't see the custom event is that necessary. To check this, I commented out
the entire custom event part and it worked perfectly fine.
The part I commented out is below.
function brushend() {
//딱히 필요한거같지는 않은 기능.
get_button = d3.select(".clear-button");
if(get_button.empty() === true) {
clear_button = svg.append('text')
.attr("y", 460)
.attr("x", 825)
.attr("class", "clear-button")
.text("Clear Brush");
}
The entire code is attached below.
https://codepen.io/jotnajoa/pen/NWGXoQo
I think it's just an extra piece which doesn't do anything on the visualization.
What makes a difference here that enables tooltip on top of the brush function is
happening here I strongly believe.
It adds the helper function in the manner of 'call' instead of just
adding '.on('click', function() )'
points = svg.selectAll(".point")
.data(data)
.enter().append("circle")
.attr("class", "point")
.attr("clip-path", "url(#clip)")
.attr("r", function(d){return Math.floor(Math.random() * (20 - 5 + 1) + 5);})
.attr("cx", function(d) { return x(d.index); })
.attr("cy", function(d) { return y(d.value); })
.call(d3.helping.tooltip());
Thank you in advance.
P.S
In addition, as far as I understand,
Custom event is not really necessary in any situation.
Cause what it does is just same as regular function and custom events
can't really do anything special. To execute its functionality I need to dispatch it
which makes the code lengthy, no more than that.
This is just my thought and I'm willing to hear any critics on my thought.(It's not my main question)
Always feel grateful about constructive comments from Stackoverflow.
I figured out.
It wasn't a matter of custom event.
It was a matter of the ordder of code and calling function only.

d3 data manipulation before plotting

Good evening everybody,
I am working for the first time on a visualization using D3. Basically I want to plot a bar chart that plots means of groups.
For example:
d3.csv("sample.csv", function(d) {
return {
"currency" : +d["year"],
"month" : +d.month,
"spendings" : +d.spendings
}
})
.then(function(data) {
chart_group.selectAll(".bar")
.data(sample.filter(function(d) { return d["currency"] === temp_curr; }))
.enter()
.append("rect")
.attr("class", "bar")
It is easily possible to create a new object containing the dynamic data i want. Group by curreny and mean the spending.
Is it possible to replace the .data(sample) by another object I created. Somehow the plot is always empty changing it. If it is not. How can I get the mean as y value?
.attr("y", function(d) { return y(d.spendings); })
I tried many things including nesting and d3.mean but there are always errors messages complaining about lists instead of single values. I totally get that but don't know how to do the trick.
Can someone help me?
Try .data(data.filter since data is the var name provided to the function containing your d3 code

Getting color as a JSON input to a d3 bullet chart

I am following the following D3 bullet chart example, trying to modify it a bit so that the different colors of the ranges are also included in the JSON: http://www.d3noob.org/2013/07/introduction-to-bullet-charts-in-d3js.html. The reason for the change is that I need the colors to be dynamic and depend on various things.
This exists one other place in the forum, but old and unsolved. I should add that I am a total newbie to d3, and don't have a lot of JavaScript experience in general.
Here is the JSON I use. "rangecolor" will in the future be an array of different colors, as there are several ranges, but for simplicity I attempt only with one color to begin with.
{
"title":"Memory Used",
"subtitle":"MBytes",
"ranges":[256,512,1024],
"rangecolor": "red",
"measures":[768],
"markers":[900]
}
Now, getting an idea of how to use it, I looked at the working example for title:
var title = svg.append("g")
.style("text-anchor", "end")
.attr("transform", "translate(-6," + height / 2 + ")");
title.append("text")
.attr("class", "title")
.text(function(d) { return d.title; });
The problem is that I cannot get the following to work:
d3.selectAll(".bullet .range.s0")
.style("fill", function(d) { return d.rangecolor; });
The following does work:
d3.selectAll(".bullet .range.s0")
.style("fill", function(d) { return "red"; });
And I can also extract the rangecolor value to the title:
title.append("text")
.attr("class", "title")
.text(function(d) { return d.rangecolor; }); //works - title is now "red"
My approach might be misguided, so any help on how to best include color ranges to the JSON and using it would be much appreciated.
The problem is that when you select all bullets, there is no data bound to them so d is undefined here:
d3.selectAll(".bullet .range.s0")
.style("fill", function(d) { return d.rangecolor; });
Why? You did not perform a data join like this:
d3.selectAll('.something').data(somethingData)
.style('fill', function (d) { // d is defined });
You should wonder why it works for the title on the contrary. This is because when you do this:
var title = svg.append("g");
title inherits data from the svg selection. See Mike Bostock explanation. In fact I was myself not aware of this behaviour, I prefer performing data joins explicitly.
I don't know the overall structure of your code, but you might apply you rangecolor properties with data inheritance (as for title) or refactor to use explicit data joins.
I have talked with some experienced developers that state that the tutorial I am following is not a good one. It is a bit messy, and I am trying to find something cleaner. Troubleshooting has been difficult in this regard, and
The exact issue presented here was solved by using d3.selectAll(".bullet .range.s0").data(data).

How to add an array of annotations to a d3 barchart?

I've managed to draw a barchart (it's inverted I know :)) for each year of data by reading in the CSV data and then using d3.nest() to group the data for each date per year, see block here or blockbuilder here.
However I'm am now trying to append notes from my annotations array to each chart and I'm stuck.
One approach I'm trying is to selectAll(".charts") and to append my nested annotations array i.e. annotationsPerYear. But I'm finding it difficult to join my annotationsPerYear key with my charts and then to iterate and append the notes for each year onto the correct chart. Any ideas?
You can rely on nested selections for this. You're already using nested selection with this bit:
svg.selectAll(".bar")
.data(function(d) {return d.values;})
The above binds different data to each of the 3 SVGs created earlier. It does this by calling the function(d) 3 times, and returns a different d.values each time.
You can do a similar thing to bind and create a different set of annotations in each SVG. You need a function (passed to data()) that returns the applicable annotations per chart, but this time you don't have something pre-computed like d.values. Instead, you have to extract the applicable annotations out of annotations array, using filter():
svg.selectAll(".annotation")
.data(function(d) {
// return annotations whose key matches d's key
return annotations.filter(function(a) { return a.key == d.key; });
})
Then you can use enter() and append() as you've done for the bars to create the text and position it. I'm not sure how you intend to lay it out, but altogether you want something like this:
svg.selectAll(".annotation")
.data(function(d) {
return annotations.filter(function(a) { return a.key == d.key; });
})
.enter()
.append("text")
.attr("class", "annotation")
.attr("x", function(d) { return x(d.xPos); })
.attr("y", function(d) { return d.yPos; })
.text(function(d) { return d.note; })
See:
Updated block
Updated blockbuilder

How to align text vertically on a node within a Sankey diagram (D3.JS)?

I have modified Mike Bostok example of a Sankey diagram in D3.Js from http://bost.ocks.org/mike/sankey/ to display the value of each node as this:
Sankey http://uweb.cs.uvic.ca/~maleh2/sankey.png
node.append("text")
.attr("class","nodeValue");
///align vertically??
node.selectAll("text.nodeValue")
.attr("x", sankey.nodeWidth() / 2)
.attr("y", function (d) { return (d.dy / 2) })
.text(function (d) { return formatNumber(d.value); })
.attr("text-anchor", "middle");
//.attr("transform", "rotate(-90)")
All code from Mike's example and my modification is at jsfiddle
(http://jsfiddle.net/4xNK5/). My question is how can I display the value of the node vertically? I assumed a simple .attr("transform", "rotate(-90)"); would do it. Indeed, I get the value of the node aligned vertically BUT out of place. I cannot make sense of the logic behind it. Any ideas would be appreciated...
I am going to walk you through several experiments (all with accompanied jsfiddles) that will guide you to the solution of your problem.
Starting Point
(click for jsfiddle)
This is slightly modified example from your question:
It has been modified so that it contains data in JavaScript instead of in JSON file, and also it contains code from Sankey plugin. This is done just in order to have working example in JsFiddle. You can of course adjust it to suit your needs with respect to data etc...
The key code is here, as you already mentioned:
node.selectAll("text.nodeValue")
.attr("x", sankey.nodeWidth() / 2)
.attr("y", function (d) { return (d.dy / 2) })
.text(function (d) { return formatNumber(d.value); })
.attr("text-anchor", "middle");
Experiment 1
(click for jsfiddle)
I was really bothered that values and names of nodes are not aligned, so I added this code that slightly moves values downwards:
.attr("dy", 5)
The result is here:
Experiment 2
(click for jsfiddle)
Now lets try to add this code (I used 45 degrees on purpose to easier spot how rotation works):
.attr("transform", "rotate(-45)")
Surprisingly, we get this:
And if we examine the html/svg in firebug or similar tool, we'll notice that the reason for such rotation behavior is that value labels are actually part of other container: (and center of rotation is not center of label, but origin of that container)
Experiment 3
(click for jsfiddle)
In this experiment, I wanted to avoid using rotation at all. I wanted to use svg text property writing-mode, like this:
.style("writing-mode", "tb")
And I got this:
However, I couldn't get value labels to be oriented with its top to the left. Also, I heard this method has some Firefox compatibility problems, so I gave it up.
Solution
(click for jsfiddle)
After experiments above, my conclusion was that rotation should be done first, and that translation should be applied so that already rotated value labels are moved to the center of nodes. Since this translation is data-dependent, the code should look like this:
node.selectAll("text.nodeValue")
.text(function (d) { return formatNumber(d.value); })
.attr("text-anchor", "middle")
.attr("transform", function (d) {
return "rotate(-90) translate(" +
(-d.dy / 2) +
", " +
(sankey.nodeWidth() / 2 + 5) +
")";});
The result is:

Categories