D3 chart like the US government analytics - javascript

I want to modify charts I have used in D3 to be like those on the US government analytics. I am unsure where to start. The JSfiddle below is where I am starting from.
Do I have to modify the chart to a stacked bar chart, a bullet chart or simply create the recs and add more space between them and position labels above the charts?
Has anyone done anything similar with D3?
My starting point: http://jsfiddle.net/Monduiz/drhdtsaq/
The Y axis, rects and labels:
[...]
var y = d3.scale.ordinal()
.domain(["Earnings", "Industry", "Housing", "Jobs"])
.rangeRoundBands([0, height], 0.2);
[...]
bar.append("rect")
.attr("width", 0)
.attr("height", barHeight - 1)
.attr("fill", "#9D489A")
.attr("width", function(d){return x(d.value);});
bar.append("text")
.attr("class", "text")
.attr("x", function(d) { return x(d.value) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return d.value; })
.attr("fill", "white")
.attr("font-family", "sans-serif")
.attr("font-size", "14px")
.attr("text-anchor", "end");
This is what I want to do:
EDIT
I have made some progress. Updated fiddle: http://jsfiddle.net/Monduiz/drhdtsaq/33/
Now I need to find how to move the ordinal categories between the bars.

Well, I got close enough after some attempts. I can polish things up with CSS.
updated fiddle: http://jsfiddle.net/Monduiz/drhdtsaq/66/
Basically, the solution is to create another set of rectangles called first using a second set of data to full value of the scale used. Then its just a matter of adjusting the size of the bars and positioning the labels.
bar2.append("rect")
.attr("height", y.rangeBand()-15)
.attr("fill", "#EDEDED")
.attr("width", function(d){return x(d);});
bar.append("rect")
.attr("height", y.rangeBand()-15)
.attr("fill", "#FFB340")
.attr("width", function(d){return x(d.value);});

Related

d3 v4 line chart transition not working

I would like my line to draw like this example:
https://bl.ocks.org/shimizu/f7ef798894427a99efe5e173e003260d
The code below does not make any transitions, the chart just appears.
I'm aware of browser caching and that is not the issue. I've also tried changing the duration and that doesn't help either. I feel like I'm probably not being explicit about how I want d3 to transition, but I'm unsure how to give d3 what it wants. Your help is greatly appreciated.
EDIT: x-axis domain: [0, 1]. y-axis domain: [-18600, -3300].
// Here's just a few rows of the data
data = [{"threshold": 0.0, "loss": -18600},
{"threshold": 0.008571428571428572, "loss": -18600},
{"threshold": 0.017142857142857144, "loss": -18600}]
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 20},
width = +svg.attr("width") - 400 - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleLinear()
.range([0, width]);
var y = d3.scaleLinear()
.range([0, height]);
var line = d3.line()
.x(d => x(d.threshold))
.y(d => y(d.loss));
var g = svg.append("g")
.attr("transform", "translate(" + (margin.left + 50) + "," + margin.top + ")");
d3.json("static/data/thresh_losses.json", function(thisData) {
draw(thisData);
});
let draw = function(data) {
$("svg").empty()
var x = d3.scaleLinear()
.range([0, width]);
var y = d3.scaleLinear()
.range([0, height]);
var line = d3.line()
.x(d => x(d.threshold))
.y(d => y(d.loss));
var g = svg.append("g")
.attr("transform", "translate(" + (margin.left + 50) + "," + margin.top + ")");
d3.selectAll("g").transition().duration(3000).ease(d3.easeLinear);
x.domain([0, d3.max(data, d => d.threshold)]);
y.domain([d3.max(data, d => d.loss), d3.min(data, d => d.loss)]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.append("text")
.attr("class", "axis-title")
.attr("y", 18)
.attr("dy", "1em")
.attr("x", (height/2) - 40)
.attr("dx", "1em")
.style("text-anchor", "start")
.attr("fill", "#5D6971")
.text("Threshold");
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
.append("text")
.attr("class", "axis-title")
.attr("transform", "rotate(-90)")
.attr("y", -40)
.attr("dy", ".71em")
.attr("x", -height/2 + 40)
.attr("dx", ".71em")
.style("text-anchor", "end")
.attr("fill", "#5D6971")
.text("Profit ($)");
var line_stuff = g.selectAll(".line")
.data([data]);
line_stuff.enter().append("path").classed("line", true)
.merge(line_stuff);
g.selectAll(".line")
.transition()
.duration(10000)
.ease(d3.easeLinear)
.attr("d", line);
};
From the D3 documentation:
To apply a transition, select elements, call selection.transition, and then make the desired changes.
I found this in the code:
d3.selectAll("g").transition().duration(3000).ease(d3.easeLinear);
This won't animate anything, because there's no .attr() or .style() at the end—no "desired changes" are being made. It's a transition with no changes to make.
Now, let's look at this:
g.selectAll(".line")
.transition()
.duration(10000)
.ease(d3.easeLinear)
.attr("d", line);
This almost fulfills the requirements. It selects .line, creates the transition (and customizes it), and sets the d attribute. If you have d set elsewhere, then this would to transition the path from being empty to having all the data, only...
D3 doesn't transition strings that way. After first checking if the attribute is a number or color, D3 settles on using something called interpolateString. You'd think interpolateString would change characters from a to ab to abc, but actually, all it does is look for numbers within the string, and interpolate those, leaving the rest of the string constant. The upshot is, you just can't animate a string like d from empty to having data unless you do it yourself.
Here's how you can do that, using attrTween (note: not a good idea):
.attrTween("d", function() {
return function(t) {
const l = line(data);
return l.substring(0, Math.ceil(l.length * t));
};
})
This will actually transition between no text to the entire text of the d attribute. However, because of the way SVG paths work, this doesn't look very good.
There is another way, as demonstrated in the example you linked to (and also mentioned by Ryan Morton in a comment): transitioning the stroke-dashoffset. Here's how you would do that:
line_stuff.enter().append("path").classed("line", true)
.merge(line_stuff)
.attr('d', line)
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-dasharray", function(d) {
return this.getTotalLength()
})
.attr("stroke-dashoffset", function(d) {
return this.getTotalLength()
});
g.selectAll(".line")
.transition()
.duration(10000)
.ease(d3.easeLinear)
.attr("stroke-dashoffset", 0);
Essentially, the first part tells D3 to:
create the line, make the fill invisible (so you can see the line)
make the stroke dashes equal to the total length of the line
offset the dashes, so that the line is completely hidden at the start
The next part sets up the transition and tells it to transition the offset to 0 (at which point the line will be completely visible because each dash is the same length as the line itself).
If you want to transition the fill, you could change .attr("fill", "none") to .attr("fill", "#fff"), and then do something like this:
g.selectAll(".line")
.transition()
.delay(10000)
.duration(2000)
.ease(d3.easeLinear)
.attr('fill', '#000');
This would use .delay() to wait for the first transition to finish before changing the background from white to black. Note that opacity might be better to animate for performance.

D3 legend for line chart axes

I have line chart made with the help of d3.js which has axes showing numbers with units such as 0m, 1m 2m, 3m..etc for varios units. I want to show legend for these units such m = mili, n=nano, B = billion etc near the line chart.
Here is the an example of line chart with legends. I am not sure if you are following the same process to draw your line charts as most examples in d3 use the same. But as your question do not have any code so here goes an example anyways.Below is the snippet of how to do so:
var legend = svg.selectAll(".legend")
.data(cities)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 4)
.style("fill", function(d) {
return color(d);
});
legend.append("text")
.attr("x", width - 24)
.attr("y", 6)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) {
return d;
});

Adding 'measures' label to D3 bullet chart

I'm working with D3's bullet chart and am trying to figure out how to display the actual measures number just to the right of the measures rectangle. Since I want to do this for every bullet chart, I figure it'd be best to do it right in the bullet.js code. I'm rather new to D3 so any help would be much appreciated! Here is the link to Mike Bostock's bullet chart example with the bullet.js included at the bottom.
It looks like the measures code is handled in this snippet:
// Update the measure rects.
var measure = g.selectAll("rect.measure")
.data(measurez);
measure.enter().append("rect")
.attr("class", function (d, i) { return "measure s" + i; })
.attr("width", w0)
.attr("height", height / 3)
.attr("x", reverse ? x0 : 0)
.attr("y", height / 3)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);
measure.transition()
.duration(duration)
.attr("width", w1)
.attr("height", height / 3)
.attr("x", reverse ? x1 : 0)
.attr("y", height / 3);
I thought I could just add something like this after the rect is appended but I've had no such luck.
measure.enter().append("text")
.attr("dy", "1em")
.text(function (d) { return d.measurez; })
.attr("x", reverse ? x0 : 0)
.attr("y", height / 3)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);
Thank you in advance for your consideration!
You almost got it -- there're just two small things to consider. First, you can't call .enter() twice. Once the enter selection has been operated on, it's merged into the update selection and your second selection will be empty. This is fixed easily by saving the selection in a variable, but in this case I would recommend making a separate selection for the text labels.
var measureLabel = g.selectAll("text.measure")
.data(measurez);
measureLabel.enter()....;
Second, to position the text to the right of the rect, you need to take not only the position, but also the width into account when computing the position of the text element. Also, you can omit a few elements that are not relevant to text elements.
measureLabel.enter()
.append("text")
.attr("class", function(d, i) { return "measure s" + i; })
.attr("dy", "1em")
.attr("dx", "1em")
.text(String)
.attr("x", reverse ? function(d) { return w0(d) + x0(d); } : w0)
.attr("y", height / 3);
measureLabel.transition()
.duration(duration)
.attr("x", reverse ? function(d) { return w1(d) + x1(d); } : w1);
Complete example here.

Very Simple D3: How to Draw an Arc?

It would be nice to learn D3. After reading many examples, I think I understand it. My first project is to make a color wheel, without transitions for simplicity. But it appears even that is not simple enough for my first project! For project zero, I am trying to get something to show on the screen. Hopefully something I wrote (and dear read has fixed), and not an example.
What did I do wrong? http://jsfiddle.net/aGdMX/1/
var arc = d3.svg.arc()
.innerRadius(40)
.outerRadius(100)
.startAngle(0)
.endAngle(1)
;
var chart = d3.select("body").append("svg:svg")
.attr("class", "chart")
.attr("width", 420)
.attr("height", 420).append("svg:g")
.attr("transform", "translate(200,200)")
;
chart.selectAll("path")
.data(data)
.enter().append("svg:path")
.attr("fill", function(d, i){
return d3.rgb("black");
})
.attr("d", arc)
;
Thank you
Your example here doesn't have any data defined. If you just want to draw the svg statically, skip the selectAll() and data() bindings:
chart
.append("svg:path")
.attr("fill", function(d, i){
return d3.rgb("black");
})
.attr("d", arc)
;
Or define some data and use that to drive the drawing:
http://jsfiddle.net/findango/aGdMX/2/
(plus .attr("fill"... should be .style("fill"...)

how to use svg:defs to create rect with same height in d3.js?

I am working on a d3.js project where I am displaying a number of rectangles to be the same height. The rectangles are connected to a input[type=number] that adjust the height of each group of rectangles. To make animation easier (so I only have to manipulate the svg:defs onchange of the number input), I would like to be able to specify the height of a group of rectangles with a svg:def tag like this:
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
svg.append("defs").selectAll(".rectdef")
.data(data).enter()
.append("rect")
.attr("class", "rectdef")
.attr("id", function (d, i){return "rect" + d.name;})
.attr("x", 0) // overridden below
.attr("width", 0) // overridden below
.attr("y", 0) // overridden below
.attr("height", function (d, i){return d.height});
and then to be able to just refine placement x, y and width of the rectangles with something like this:
svg.selectAll(".bar")
.data(data).enter()
.append("use")
.attr("class", "bar")
.attr("xlink:href",function (d){return "#rect"+d.type;})
.attr("x", function(d) { return d.x })
.attr("width", function (d) {return d.w;}) // this does NOT adjust width!
.attr("y", function (d) {return 0;});
This snippet correctly changes the x and y coordinates but it does not properly change the width! Any ideas what's wrong here? Is this a browser issue (I'm using Chrome 24.0.1312.52)? Is width not editable like this on an svg:use tag?
There aren't any problems with the data (I've checked that) and I have been able to confirm that the animation does work correctly.
If you point a <use> element at a <rect> the width/height of the <use> are ignored according to the SVG specification
I recomment you put the <rect> in a <symbol>, and then have the use reference the symbol. That way the width/height of the use will apply to the rect. You probably want to make the rect's width/height 100% within the symbol.
In other words, something like this should work:
svg.append("defs").selectAll(".rectdef")
.data(data).enter()
.append("symbol")
.attr("class", "rectdef")
.attr("id", function (d, i){return "rect" + d.name;})
.append("rect")
.attr("x", 0) // overridden below
.attr("width", "100%") // overridden below
.attr("y", 0) // overridden below
.attr("height", function (d, i){return d.height});
svg.selectAll(".bar")
.data(data).enter()
.append("use")
.attr("class", "bar")
.attr("xlink:href",function (d){return "#rect"+d.type;})
.attr("x", function(d) { return d.x })
.attr("width", function (d) {return d.w;}) // this correctly adjusts width!
.attr("y", function (d) {return 0;});

Categories