D3.js ignores duplicate values - ajax

I'm exploring D3.js. I primarily used their tutorial to get what I have. But I have made some adjustments.
What I'm doing is counting the number of active & inactive items in a specific table. It then displays a graph with those values. Most everything works fines. I have 2 issues with it though:
It doesn't update automatically with my AJAX call when items are deleted. Only updates when items are added. But I'm not concerned about this for this post.
My primary issue: duplicate values aren't being treated as individual numbers. Instead it sees [10,10] and outputs it as a single bar in the graph as 10 (instead of 2 bars).
Looking at the D3 docs, it would seem the issue lies with .data. Documentation mentions that it joins data.
$(document).on("DOMSubtreeModified DOMNodeRemoved", ".newsfeed", function() {
var columns = ['created','deleted'];
var data = [numN, numD];
var y = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, 420]);
var x = d3.scale.ordinal()
.domain(columns)
.rangeBands([0, 120]);
chart.selectAll("rect")
.data(data)
//.enter().append("rect")
.attr("x", x)
.attr("height", y)
.attr("width", x.rangeBand())
.attr("rx", 10)
.attr("ry", 10);
chart.selectAll("text")
.data(data)
//.enter().append("text")
.attr("y", y)
.attr("x", function(d) {
return x(d) + x.rangeBand() / 2;
}) //offset
.attr("dy", -10) // padding-right
.attr("dx", ".35em") // vertical-align: middle
.attr("text-anchor", "end") // text-align: right
.text(String);
});
How can I make each value to display? If I pass in two different values, the chart displays as it should.

Your problem is at .attr("x", x). So the way you're doing it assigns the same x coordinate for both rects.
So try offsetting x coordinate.
.attr("x", function(d, i) { x + i * width_of_your_rect); })

Related

Why a horizontal line is not working correctly in stack bar chart

I have a stacked bar chart. You can see the fiddle here.
I have drawn a line that is actually a horizontal line leveling the current stack of a bar. Below is the code.
.on('mouseenter', function (actual, i) {
const y = yScale(actual.y + actual.y0);
debugger;
line = svg.append('line')
.attr('id', 'limit')
.attr('x1', 0)
.attr('y1', y)
.attr('x2', width)
.attr('y2', y);
And the output is,
Here, you can see that, for the monthly data, the line is correct. But for the quarterly data, the line is a bit above the actual position. And for the yearly data, the line is not showing.
What is the problem here?
And how can I show a tooltip along with the line?
Looking at the fiddle, it seems that the scale you are using to render the rectangles is not yScale, but actually just y
Changing the following fragment:
const y = yScale(actual.y + actual.y0)
line = svg.append('line')
.attr('id', 'limit')
.attr('x1', 0)
.attr('y1', y)
.attr('x2', width)
.attr('y2', y);
To:
const limitY = y(actual.y + actual.y0);
line = svg.append('line')
.attr('id', 'limit')
.attr('x1', 0)
.attr('y1', limitY)
.attr('x2', width)
.attr('y2', limitY);
Adjusts the position of the line to match the rectangles, because it is now using the same scale that the bars and the axis are using.
Regarding the tooltip, I see there is a rectangle you want to append:
line.append("rect")
.attr("width", "10px")
.attr("height", "10px")
.style("fill", "red");
However, a <line> can not have a <rect> element inside. What you actually want is to add the <rect> to the <svg>:
svg.append("rect")
.attr('id', 'myId') // Also give it an Id for clean up
.attr("width", "10px")
.attr("height", "10px")
.attr("y", limitY) // The limitY is available to position the tooltip under the line
.style("fill", "red");
Don't forget to remove it in the mouseout event, as you are doing with <line#limit>:
.on("mouseout", function() {
svg.selectAll('#limit').remove();
// clean the rectangle on mouseout:
svg.selectAll('#myId').remove();
})
You can use the same premise of the above <rect> in a <g> element to create a full tooltip with text and background, but coding it is outside of the scope of this answer. I hope the above explanations can give you a direction.
Here is a fiddle with the changes.

nvd3 multiBarChart with line along x axis

I need to add a line with multiple values along the x axis of a multiBarChart. I cannot use another nvd3 control like multiChart or linePlusBarChart due to existing functionality breaking if i switch.
The following code and fiddle show what i have so far.
var limits = [[60,166990904656],[300,154990904656],[500,142990904656]];
var lineFunction = d3.svg.line()
.x(function(d) { return d[0] })
.y(function(d) {
d = d[1] / 1000000000;
return d
}).interpolate('step-after');
//The line SVG Path we draw
d3.select("#chart svg")
.append("path")
.attr("d", lineFunction(limits))
.attr("stroke", "red")
.attr("stroke-width", 1)
.attr("fill", "none");
https://jsfiddle.net/s2vemzht/11/
I am facing 3 issues: First is the x axis placement of the line. At the moment i have hardcoded this value into the limits array because i am unsure how to position it dynamically based on where the next bar starts.
The second issue is the limit value in relation to the other values in the data array. It does not seem accurately positioned.
The third issue is with the line not drawing over the 3rd bar even though there are 3 values in the limits array. I tried changing the interpolate property but it's still an issue.
I am a beginner with D3 so apologies for all the questions :-)
I managed to figure it out by drawing lines over the bars at particular points and positioning based on the y and x axis scales
var yValueScale = chart.yAxis.scale(),
yValue = 0,
xValueScale = chart.xAxis.scale(),
g = d3.select("#chart svg .nvd3");
for(var i=0;i<limits.length;i++){
g.append("line")
.attr("class","limit-line")
.attr("x1", xValueScale(d3.time.format('%b-%d')(new Date(limits[i][0]))))
.attr("y1", yValueScale(limits[i][1]/ 1000000000))
.attr("x2", xValueScale(d3.time.format('%b-%d')(new Date(limits[i][0]))) + parseInt(d3.select("#chart svg .nv-bar").attr("width")))
.attr("y2", yValueScale(limits[i][1]/ 1000000000))
;
}
https://jsfiddle.net/s2vemzht/25/

Legend transition not working properly

I have a graph with a legend, and the legend is always the same. The only thing that changes with the transitions is the size of the legend tiles, that are the same as this example.
When I update my graph, its size changes, and so does the legend's. Here is what I have for the legend :
var couleurs = ["#ffffb2", "#fed976", "#feb24c", "#fd8d3c", "#fc4e2a", "#e31a1c", "#b10026"];
var legende = ["0-15", "15-30", "30-45", "45-60", "60-75", "75-90", "90-100"];
canevas.append("g").selectAll(".legende").data(legende).enter()
.append("rect")
.attr("class", "legende")
.attr("width", cellPosX / 7)
.attr("height", largeurCellule / 2)
.attr("x", (d, i) => i * (cellPosX / 7))
.attr("y", cellPosY + 10)
.attr("fill", ((d, i) => couleurs[i]));
canevas.selectAll(".legende").data(legende).transition()
.duration(transitionTime)
.attr("width", cellPosX / 7)
.attr("x", (d, i) => i * (cellPosY / 7))
.attr("y", cellPosY + 10)
.attr("fill", ((d, i) => couleurs[i]));
canevas.selectAll(".legende").data(legende).exit()
.remove();
This works fine, except that when the graph is updated, for a while there are 2 legends at the same time. One that goes from the old position to the new one, which is expected, but there is also one that instantly appears to the new position. Here is a very low fps gif that I quickly made to show an example.
How would I go about having the legend going from its initial position to the other one, without it also appearing instantly at the new position?

How to draw vertical text as labels in D3

I'm trying to draw vertical labels for the heatmap that I'm working. I'm using the example from http://bl.ocks.org/tjdecke/5558084. Here is the part of the code that I've changed:
var timeLabels = svg.selectAll(".timeLabel")
.data(ife_nr)
.enter().append("text")
.text(function(d) {
return d;
})
.attr("x", function(d, i) {
return (i * gridSize);
})
.attr("y", 0)
//.style("text-anchor", "middle")
//.attr("transform", "translate(" + gridSize / 2 + '-5' + ")")
.attr("transform", "translate(" + gridSize/2 + '-8' + "), rotate(-90)")
.attr("class", function(d, i) {
return ((i >= 0) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis");
});
But it appears the labels seems to be stacked on top one another on top of the first grid. How can I edit this code to get the labels correctly displayed?
Two problems: first, the translate should have a comma separating the values:
"translate(" + gridSize/2 + ",-8), rotate(-90)")
Assuming that -8 is the y value for the translate. If you don't have a comma, the value inside the parenthesis should be just the x translation (If y is not provided, it is assumed to be zero). But even if there is actually no comma and all that gridSize/2 + '-8' is just the x value you still have a problem, because number plus string is a string. You'll have to clarify this point.
Besides that, for rotating the texts over their centres, you'll have to set the cx and cy of the rotate. Have a look at this demo:
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 100);
var texts = svg.selectAll(".texts")
.data(["foo", "bar", "baz"])
.enter()
.append("text");
texts.attr("y", 50)
.attr("x", function(d,i){ return 50 + 80*i})
.text(function(d){ return d});
texts.attr("transform", function(d,i){
return "rotate(-90 " + (50 + 80*i) + " 50)";
});
<script src="https://d3js.org/d3.v4.js"></script>

Displaying two strings in a table using d3.js

I've been trying to use d3.js to display a table using two input strings, I've added the code below. When displaying the second string, only the characters with indices that are greater than the length of the string x are displayed.
I think it's something related to the anonymous functions, when iterating through the second string, i begins with the index value it finished off with in the first string e.g., only "fo" is displayed from the second string instead of "fresihnfo". Can anyone give me some pointers on how to fix this?
Thanks!
var x = ["a", "e", "d", "i", "r", "z"];
var y = ["f", "r", "s", "i", "h", "n", "f", "o"];
var w = (x.length + 1) * 50;
var h = (y.length + 1) * 50;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
/*Displays the first string*/
svg.selectAll("rect")
.data(x)
.enter()
.append("rect")
.attr("x", function(d, i) {
return (i * 45) + 45;
})
.attr("y", "0px")
.attr("width", "40px")
.attr("height", "40px")
.attr("fill", "rgb(0, 0, 102)");
svg.selectAll("text")
.data(x)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return (i * 45) + 65;
})
.attr("y", "27px")
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "white");
/*Displays the second string*/
svg.selectAll("rect")
.data(y)
.enter()
.append("rect")
.attr("x", "0px")
.attr("y", function(d, i) {
return ((i - x.length) * 45) + 45;
})
.attr("width", "40px")
.attr("height", "40px")
.attr("fill", "rgb(0, 0, 102)");
svg.selectAll("text")
.data(y)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("text-anchor", "middle")
.attr("x", "20px")
.attr("y", function(d, i) {
return ((i - x.length) * 45) + 70;
})
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "white");
Current output:
Desired output would be to have the rest of the string y displayed in the left column.
As Marc said, the problem is that your second selection each time is being interpretted as an update to your first selection, not as a new set of elements. And since you only work on the enter() part of the update, you don't even see the fact that you've changed the data for your first set of rectangles.
To confirm that the first set of rectangles have been given the data from your second array, right-click on one, choose "Inspect Element" and then open up the properties tab in your inspector -- the __data__ property holds the element's D3 data object.
So how do you fix it? You need a way of distinguishing between the two groups of rectangles in your select statement. There are two options:
Option 1: Use sub-selections on SVG group elements (<g>)
You've got two groups of rectangles, so it makes sense to use the svg grouping element to keep them organized. Instead of adding your rectangles directly to your svg, add a group element to the svg and then add the rectangles/text to it.
svg.append("g").selectAll("rect") //etc.
Do the same for the second set of rectangles, and they'll all be nicely arranged in the second group, so long as your select statement is always called from the group selection, it will only select elements that are part of that group.
Option 2: Use a class attribute to distinguish the two element types
You've got two types of values, x and y, so you should distinguish which type your svg elements belong to by setting a corresponding class attribute. Then, in your select statement, make sure you only select elements of the correct class. The CSS selector format for an element of a certain class is elementName.ClassName, so your code would look like:
.selectAll("rect.x")
.data(x)
enter()
.append("rect")
.classed("x", true)
// etc.
Or, Option 3: Use both
If you're going to want to update the rectangles in the future, just putting them in two groups isn't good enough -- you need a way to distinguish the groups, too. So add an x or y class when you append the <g> elements and use a "g.x" or "g.y" selector when you create them.
I highly recommend you read up on selections, selectors, nested selections, and the update process if you want to keep your D3 code straight. There's a list of tutorials on the wiki.
P.S. The i values that you create as named parameters of your anonymous functions are always limited in scope to that function. You could give them different names if you wanted, their value will always be the value that D3 passes in to them -- the data object for the first parameter, and the index within the current selection for the second parameter.
the main goal of selectAll() is to select exist elements in the container, when worked with data()
it will join data with elements so we can
use enter() to add new element for new data items;
use update() to update elements which match the data.
use exit() to remove elements without data to match
when you use svg.selectAll("rect").data(x).enter() to append rect selectAll() will return 0 element
data(x).enter() will produce placeholder for each data so you can append all data in x
but when use svg.selectAll("rect").data(y).enter() to append rect for y. selectAll() will return 6
rects, so data(y).enter() will product placeholder for 'f' and 'o' this is why you only get two
elements
solution: use different selector for x and y, such as different class name
svg.selectAll(".x").data(x).enter().append("rect").attr("class", "x"); // other operation
svg.selectAll(".y").data(x).enter().append("rect").attr("class", "y"); // other operation
you can get deep understand of d3 selection and data join with these articles:
http://bost.ocks.org/mike/join/
http://bost.ocks.org/mike/selection/

Resources