d3.js group bar chart not working - javascript

Here is my working jsfiddle https://jsfiddle.net/dibyendu/wn6awvbe/ . Now this bar chart is working fine for single value and my requirement is to make it group bar chart.
So do this as a first step I have to create x-axis range based on min and max of all the values. I am able to do this successfully .
Second I tried to create a group and add individual group bars into the group. This part I am not able to crack.
Here is my code what I have tried
var combos = svg.selectAll("g").data(data)
.enter()
.append("g")
.attr({width: 2 * width})
.attr('height',y.rangeBand()+10)
;
combos.selectAll(".bar1")
.append("rect")
.attr("class", function(d) { return "bar bar--" + (d.value < 0 ? "negative" : "positive"); })
.attr("x", function(d) { return x(Math.min(0, d.value)); })
.attr("y", function(d) { return y(d.name); })
.attr("width", function(d) { return Math.abs(x(d.value) - x(0)); })
.attr("height", y.rangeBand());
combos.selectAll(".bar2")
.append("rect")
.attr("class", function(d) { return "bar bar--" + (d.value < 0 ? "negative" : "positive"); })
.attr("x", function(d) { return x(Math.min(0, d.value1)); })
.attr("y", function(d) { return y(d.name); })
.attr("width", function(d) { return Math.abs(x(d.value1) - x(0)); })
.attr("height", y.rangeBand());
But for some reason its not working . What is the mistake I am doing here. Thanks for the help in Advance.

If you are always guaranteed to have 2 bars per group, a quick fix would be:
combos
.append("rect")
.attr("class", function(d) { return "ba ba--" + (d.value < 0 ? "negative" : "positive"); })
.attr("x", function(d) { return x(Math.min(0, d.value)); })
.attr("y", function(d) { return y(d.name); })
.attr("width", function(d) { return Math.abs(x(d.value) - x(0)); })
.attr("height", y.rangeBand() / 2); //<-- cut height in half
combos
.append("rect")
.attr("class", function(d) { return "bar bar--" + (d.value < 0 ? "negative" : "positive"); })
.attr("x", function(d) { return x(Math.min(0, d.value1)); })
.attr("y", function(d) { return y(d.name) + (y.rangeBand() / 2); }) //<-- offset if from other bar
.attr("width", function(d) { return Math.abs(x(d.value1) - x(0)); })
.attr("height", y.rangeBand() / 2); //<-- cut height in 2
Updated fiddle.
EDITS FOR COMMENTS
If you want to have a dynamic number of bars, you really need to change your input data. value1:, value2:, etc is just not workable. A quick refactor might use an array of values:
{
"name": "M1",
"value": [-45, -13, -10, -8]
},
...
With this format you can use a sub-selection and really start getting some dynamic code:
var combos = svg.selectAll("g").data(data)
.enter()
.append("g");
combos
.selectAll("rect")
.data(function(d){
return d.value.map(function(d1){
return {
value: d1,
name: d.name,
total: d.value.length,
};
})
})
.enter()
.append("rect")
.attr("class", function(d) { return "ba bar--" + (d.value < 0 ? "negative" : "positive"); })
.attr("x", function(d) { return x(Math.min(0, d.value)); })
.attr("y", function(d,i) { return (y(d.name) + (y.rangeBand() / d.total * i)) ; })
.attr("width", function(d) { return Math.abs(x(d.value) - x(0)); })
.attr("height", function(d){
return y.rangeBand() / d.total
});
New updated fiddle.

Related

I cannot select the ("text") in my svg when I want to change the values

I have a problem where I can append the text into SVG but when I want it to change I can't seem to select it to change. The code above the d3.select("button") all works as well as the bar.transition. Can someone help me figure out how to select the "text" so when I click the button it'll use the new dataset?
var bar = svg.selectAll("rect")
.data(dataset)
.enter();
bar.append("rect")
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
return yScale(d[2]);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d) {
return height - yScale(d[2]);
})
.attr("class", function(d) {
var s = "bar ";
if (d[2] < 50) {
return s + "bar1";
} else if (d[2] < 100) {
return s + "bar2";
} else {
return s + "bar3";
}
});
bar.append("text")
.attr("dy", "1.3em")
.attr("x", function(d) {
return xScale(d[0]) + xScale.bandwidth() / 2;
})
.attr("y", function(d) {
return yScale(d[2]);
})
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "black")
.text(function(d) {
return d[2];
});
d3.select("button")
.on("click", function() {
var dataset = [
['Jun-19', 8.6, 21.7],
['Jul-19', 8.68, 17.98],
['Aug-19', 8.9, 25.38],
['Sep-19', 6.38, 11.6],
['Oct-19', 10.36, 65.08],
['Nov-19', 22.36, 125.72],
['Dec-19', 26.52, 112.22],
['Jan-20', 21.08, 76.1],
['Feb-20', 6, 44, 19.625],
['Mar-20', 4.68, 8.95],
['Apr-20', 7.4, 15.94],
['May-20', 7.36, 18.36]
];
bar = svg.selectAll("rect")
.data(dataset);
bar.transition()
.attr("x", function(d) {
return xScale(d[0]);
})
.attr("y", function(d) {
return yScale(d[2]);
})
.attr("width", xScale.bandwidth())
.attr("height", function(d) {
return height - yScale(d[2]);
})
.attr("class", function(d) {
var s = "bar ";
if (d[2] < 30) {
return s + "bar1";
} else if (d[2] < 60) {
return s + "bar2";
} else {
return s + "bar3";
}
});
svg.selectAll("text")
.data(dataset)
.transition()
.attr("dy", "1.3em")
.attr("x", function(d) {
return xScale(d[0]) + xScale.bandwidth() / 2;
})
.attr("y", function(d) {
return yScale(d[2]);
})
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "black")
.text(function(d) {
return d[2];
});
});
I've tried using bar.selectAll("text") but it doesn't work at all, but if I use svg.selectAll("text") it takes the ticks from my Axis.Left and moves it.
You cannot append text nodes as children of rect nodes - so the SVG probably never renders. Try using a g node per bar, and adding both the rect and the text to that. You can give it class barContainer, for example.
As for selecting the text and ignoring those from the axes, do as #Andrew Reid said in his comment and use barContainer.selectAll("text"). Alternatively, you can give all labels the class label and then use svg.selectAll(".label").

D3js labels on grouped bar chart

I'm trying to put labels on my d3js grouped bar chart, which is supposed to be easy, but I haven't been able to do it correctly. I know it must be similar to the way I'm adding the rects but no.. I tried to follow this example but it didn't work.
This is how I add my rectangles:
var rectG = pp.selectAll('rect')
.data(dataFilter);
rectG.exit().remove();
var rectGEnter= rectG.enter().append("g")
.append("g")
.attr("transform", function(d) {return "translate(" + x(d.group) + ",0)"; })
.selectAll("rect")
.data(function(d) { return subgroups.map(function(key) {return {key: key, value: d[key]}; }); })
.enter().append("rect")
.attr("x", function(d) { return xSubgroup(d.key); })
.attr("y", function(d) { return y(d.value? d.value : 0); })
.attr("width", xSubgroup.bandwidth())
.attr("height", function(d) { return height - y(d.value? d.value : 0); })
.attr("fill", function(d) { return getColor(d.key); })
so I tried to do the same with the lables but didn't work, except for this:
rectGEnter= rectG.enter().append("g")
.append("g")
.attr("transform", function(d) {return "translate(" + x(d.group) + ",0)"; })
.selectAll("text")
.data(function(d) { return subgroups.map(function(key) {return {key: key, value: d[key]}; }); })
.enter().append("text")
.attr("x", function(d) { return xSubgroup(d.key)+1; })
.attr("y", function(d) { return y(d.value? d.value : 0); })
.attr("text-anchor", "start")
.style("alignment-baseline", "middle")
.text(function(d) { return (d.value? d.value : 0); });
}
and if ok for the first time, but if I update my data the labels don't update well, I haven an example here, any help will be apreciate
If I understand right you only want to remove the old labels. I tried it in you plunker. The following line of code does the trick.
function updates(selectedGroup) {
pp.selectAll("rect").remove();
pp.selectAll("text").remove(); // <-- remove the old text elements
...
EDIT:
To prevent the removal of the labels we can simply add a class to specify which text we want to remove.
rectGEnter= rectG.enter().append("g")
.append("g")
.attr("transform", function(d) {return "translate(" + x(d.group) + ",0)"; })
.selectAll("text")
.data(function(d) { return subgroups.map(function(key) {return {key: key, value: d[key]}; }); })
.enter().append("text")
.attr('class', 'valueLabels') // <-- Here we add a class
.attr("x", function(d) { return xSubgroup(d.key)+1; })
.attr("y", function(d) { return y(d.value? d.value : 0); })
.attr("text-anchor", "start")
.style("alignment-baseline", "middle")
.text(function(d) { return (d.value? d.value : 0); });
Now we can specify the class when removing the elements.
function updates(selectedGroup) {
pp.selectAll("rect").remove();
pp.selectAll('g').selectAll('g').selectAll("text.valueLabels").remove(); // <-- remove the old text elements
...

D3: can't set proper columns in .datum()

I'm digging D3.js and found datum() function.
That's my current testing page, based on some example.
Example data.
.datum() use:
label.append("rect", "text")
.datum(function() { return this.nextSibling.getBBox(); })
.attr("x", function(d) { return d.x - (d.width*1.5)+labelPadding*2; })
.attr("y", function(d) { return d.y + labelPadding; })
.attr("width", function(d) { return d.width * 3; })
.attr("height", function(d) { return height-d.y; })
.attr("class", "pink_grad");
The problem is that I can't place columns good — their height counts wrong.
How to do this properly?
Solution found. Working code:
label.append("rect", "text")
.datum(function() { return this.nextSibling.getBBox(); })
.attr("x", function(d) { return d.x - (d.width*1.5)+labelPadding*2; })
.attr("y", function(d) { return d.y + labelPadding; })
.attr("width", function(d) { return d.width * 3; })
.attr("height", function(d,i) { return height-y(data[i].blue); })
.attr("class", "pink_grad");
The only problem left is horizontal alignment, which is not part of this question.

dynamically update d3.js treemap

I am trying to dynamically update my d3 treemap when the data updates. I have two functions, one that I call initially to build the treemap and another to redraw the treemap. When I redraw the treemap, I get a thin black bar on top of the treemap, and the first element in the array that I am graphing goes black. If I click on either of the two black elements. I get the error...
Uncaught TypeError: Cannot read property 'dx' of undefined
So the data being passed to the cell is undefined.
Additionally when I call regraph I have checked and the data has changed, but the graph is unchanged from when I initially built the treemap with the exception of the two black elements.
The code for building the treemap is below. Also the createObj function takes two arrays and creates a Json object.
function drawTreeMap(array1,array2){
console.log("got to drawing");
nestedJson=createObj(array1, array2);
w = 1880 - 80,
h = 900 - 180,
x = d3.scale.linear().range([0, w]),
y = d3.scale.linear().range([0, h]),
color = d3.scale.linear()
.range(['lightgreen', 'darkgreen']) // or use hex values
.domain([computeMin(array2), computeMaxNum(array2)]);
root,
node;
treemap = d3.layout.treemap()
.round(false)
.size([w, h])
.sticky(true)
.padding([10, 0, 0, 0])
.value(function(d) { return d.size; });
svg = d3.select("#body").append("div")
.attr("class", "chart")
.style("width", w + "px")
.style("height", h + "px")
.append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(.5,.5)");
node = root = nestedJson;
var nodes = treemap.nodes(root)
.filter(function(d) { return !d.children; });
var cell = svg.selectAll("g")
.data(nodes)
.enter().append("svg:g")
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", function(d) { return zoom(node == d.parent ? root : d.parent); });
cell.append("svg:rect")
.attr("width", function(d) { return d.dx - 1; })
.attr("height", function(d) { return d.dy ; })
.style("fill", function(d) { return color(d.size)});
cell.append("svg:text")
.attr("x", function(d) { return d.dx / 2; })
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return (d.name+",-- "+d.size); })
.style("opacity", function(d) { d.w = this.getComputedTextLength(); return d.dx > d.w ? 1 : 0; });
d3.select(window).on("click", function() { zoom(root); });
d3.select("select").on("change", function() {
treemap.value(this.value == "size" ? size : count).nodes(root);
zoom(node);
});
}
The code for redrawing is below.
function redrawGraphFromJson(data) {
treemap = d3.layout.treemap()
.round(false)
.size([w,h])
.value(function(d) { return d.size; })
.sticky(true);
// Draw the graph
node = root = data;
var nodes = treemap.nodes(root)
.filter(function(d) { return !d.children; });
var cell = svg.selectAll("g")
.data(nodes)
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", function(d) { return zoom(node == d.parent ? root : d.parent); });
cell.append("svg:rect")
.attr("width", function(d) { return d.dx - 1; })
.attr("height", function(d) { return d.dy ; })
.style("fill", function(d) { return color(d.size);});
cell.append("svg:text")
.attr("x", function(d) { return d.dx / 2; })
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return (d.name+",-- "+d.size); })
.style("opacity", function(d) { d.w = this.getComputedTextLength(); return d.dx > d.w ? 1 : 0; });
}
Thank you.
In your redrawing of the graph, you are going to want to .exit() on the initial graph and then .enter() to update the groups with the new data. This will replace the old map with the new.
http://bost.ocks.org/mike/join/ explains it perfectly and has a really good example to look at.

How to display data value on every layout of stacked barchart in d3

I have created a stacked bar chart in d3.
Here I want to display the value like this below example (This is Simple Bar chart)
http://bl.ocks.org/enjalot/1218567
But not outside the bar, inside the bar like below :
http://bl.ocks.org/diethardsteiner/3287802
This is my Stacked function which is working fine :
function barStack(d)
{
var l = d[0].length
while (l--) {
var posBase = 0,
negBase = 0;
d.forEach(function (d) {
d = d[l]
d.size = Math.abs(d.y)
if (d.y < 0) {
d.y0 = negBase
negBase -= d.size
} else {
d.y0 = posBase = posBase + d.size
}
})
}
d.extent = d3.extent(d3.merge(d3.merge(d.map(function (e) {
return e.map(function (f) {
return [f.y0, f.y0 - f.size]
})
}))))
return d
}
For stacked Bar
svg.selectAll(".series")
.data(data)
.enter()
.append("g")
.attr("class", "g")
.style("fill", function (d, i) {return color(i)})
.selectAll("rect")
.data(Object)
.enter()
.append("rect")
.attr("x", function (d, i) {return x(x.domain()[i])})
.attr("y", function (d) { return y(d.y0) })
.attr("height", function (d) { return y(0) - y(d.size) })
.attr("width", x.rangeBand())
;
This was also running fine.
My data is like that
var data = [{x:"abc", y1:"3", y2:"4", y3:"10"},
{x:"abc2", y1:"6", y2:"-2", y3:"-3" },
{x:"abc3", y1:"-3", y2:"-9", y3:"4"}
]
Now I want to show this value of y1, y2 and y3 in every stacked layout.
I have tried this below code, but this is not displaying the value over layout.
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("transform", "translate(50,0)")
.attr("text-anchor", "middle")
.attr("x", function(d, i) {return (i * (width / data.length)) + ((width / data.length - 50) / 2);})
.attr("y", function(d) {return y(0) - y(d.size) + 14;})
.attr("class", "yAxis")
.text(function(d) {return y(d.size);})
;
Please help me on this, where I need to change or what exact I need to put instead of this or might be above code was totally wrong.
Please let me know if any more input required by me. I have the total POC and I can share that too.
i have added all code in jsfiddle
http://jsfiddle.net/goldenbutter/HZqkm/
Here is a fiddle that does what you want.
var plots = svg.selectAll(".series").data(data)
.enter()
.append("g")
.classed("series",true)
.style("fill", function(d,i) {return color(i)})
plots.selectAll("rect").data(Object)
.enter().append("rect")
.attr("x",function(d,i) { return x(x.domain()[i])})
.attr("y",function(d) { return y(d.y0)})
.attr("height",function(d) { return y(0)-y(d.size)})
.attr("width",x.rangeBand());
plots.selectAll("text.lab").data(Object)
.enter().append("text")
.attr('fill','black')
.attr("text-anchor", "middle")
.attr("x", function(d, i) { return x(x.domain()[i]) + (x.rangeBand()/2)})
.attr("y", function(d) {return y(d.y0) + 20})
.text(function(d) {return (d.size).toFixed(2);});

Categories