d3.js Two Dimensional Array Bar Chart - javascript

I am trying to make a bar chart containing 3 'groups' of data in d3.js. I have been able to implement the example from "Let's Make a Bar Chart" sample http://bost.ocks.org/mike/bar/2/, but am wondering how you would implement that with a 2 dimensional data set instead.
I'm having a bit of a tough time wrapping my head around what I feel should be a fairly straight forward process (select first index of 2d array -> iterate through that array and display the values of each element in bars -> select second index of array -> iterate through that array and display the value of each element in bars etc...), so examples would be hugely appreciated.
The code thus far is:
var data = [4, 8, 15, 16, 23, 42];*/
var width = Math.max(500, innerWidth), height = Math.max(500, innerHeight), barHeight = 80;
var x = d3.scale.linear().domain([0, d3.max(data)]).range([0, width]);
var chart = d3.select(".chart").attr("width", width).attr("height", barHeight * data.length * 3);
var bar = chart.selectAll("g").data(data).enter().append("g").attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.append("rect").attr("width", x).attr("height", barHeight - 1);
bar.append("text").attr("x", function(d) { return x(d) - 3; }).attr("y", barHeight / 2).attr("dy", ".35em").text(function(d) { return d; });
however I would like to change the "data" variable to be:
var data = [[40,45,5],
[50,49,2],
[60,62,4],
[40,41,6],
[42,40,3],
[65,67,10],
[70,67,17],
[66,65,2],
[45,44,3],
[39,38,7],
[38,38,8],
[45,40,4],
[43,35,3],
[50,65,8],
[51,50,6]];

Related

how to create three-level donut chart in d3.js

I'm using trying to create a multi-level donut chart in d3 version5
This image is drawn by d3 version3. it is working fine in version3. I decided to upgrade d3 to the latest version. now, donut chart is not drawn by d3(also no errors in the console)
D3 version 3 > version 5
Here is the sample dataset I used:
Hint: first value in the array is used storage and second is free storage
{
average: [30.012, 69.988],
minimum: [10, 90],
maximum: [40, 60]
}
Note: Above data is just a sample this is not exact data.
Here is the code I tried:
var width = 300;
var height = 300;
var radius = Math.floor((width / 6) - 2);
var classFn = function(a, b) {
return a === 0 ? classes[b] : 'default';
};
var pie = d3.layout.pie().sort(null);
var arc = d3.svg.arc();
var svg = d3.select(selector).append("svg");
svg.attr("width", width);
svg.attr("height", height);
svg = svg.append("g");
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path");
path = path.data(function(d) {
return pie(d);
});
path.enter().append("path");
path.attr("class", function(d, i, j) {
return classFn(i, j);
})
path.attr("d", function(d, i, j) {
return arc.innerRadius((j === 0 ? 0 : 2) + radius * j).outerRadius(radius * (j + 1))(d);
});
Note: This code is working fine in d3 version3.
2. Update:
I've updated the answer with a better solution. I didn't do this at first, because I didn't grasp you structure. I've updated it to being more D3 idiomatic. Plus it does away with the hack I made in my first update :)
var dataset = {
average: [0, 100],
minimum: [0, 100],
maximum: [0, 100]
}
var width = 300;
var height = 300;
var radius = Math.floor((width / 6) - 2);
var pie = d3.pie().sort(null);
var arc = d3.arc();
var svg = d3.select('body').append("svg");
svg.attr("width", width);
svg.attr("height", height);
svg = svg.append("g");
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
gs.each(function (d, j) {
d3.select(this).selectAll('path')
.data(pie(d)).enter()
.append('path')
.attr("class", function(d, i) {
// return classFn(i);
})
.attr('d', function (d) {
return arc
.innerRadius((j === 0 ? 0 : 2) + radius * j)
.outerRadius(radius * (j + 1))(d);
})
})
The updated code uses the index (here j) that is available when appending the g elements, which corresponds to you original j index. This makes it possible to calculate the radii in the original way.
To achieve this, the arc appending code is wrapped into a .each function that iterates over the g elements, making j available to us.
The class application should work as well, but I've commented it out, as the classFn function doesn't work, since the classes variable is not present.
1. Update:
Besides the original answer, when calculating the arc radii you rely on a j value that is different from D3 v3 and v5. I summise that j is used the index of the d3.values array, so I've cooked up a way to reverse look-up that index based on the input values.
First create a map for reverse mapping data values into their corresponding index:
var dataValueJoinChar = 'ยค'
var datasetValuesToIndex = d3.values(dataset).reduce((acc, curr, i) => {
acc[`0${dataValueJoinChar}${curr[0]}`] = i
acc[`1${dataValueJoinChar}${curr[1]}`] = i
return acc
}, {})
Then change the last part of your code to:
path = path.data(function(d) {
return pie(d);
}).enter().append("path");
path.attr("class", function(d, i, j) {
return classFn(i, j);
})
path.attr("d", function(d, i, j) {
var orgIndex = datasetValuesToIndex[`${i}${dataValueJoinChar}${d.data}`]
return arc
.innerRadius((orgIndex === 0 ? 0 : 2) + radius * orgIndex)
.outerRadius(radius * (orgIndex + 1))(d);
});
It might not be too pretty, but it's a simple adaption of your code that works.
------- Original answer --------
In D3 v5 pie and arc are found at d3.pie and d3.arc respectively. Therefore, try changing:
var pie = d3.layout.pie().sort(null);
var arc = d3.svg.arc();
To this instead:
var pie = d3.pie().sort(null);
var arc = d3.arc();
Pie API reference: https://github.com/d3/d3-shape/blob/v1.3.4/README.md#pie
Arc API reference: https://github.com/d3/d3-shape/blob/v1.3.4/README.md#arc
If you use a bundler to bundle sub-modules, both are part of the d3-shape module. If not they are both available in the full D3 library.
Hope this helps!

filtering data and working with scales - d3js

I have a simple scatterplot in d3js. The aim of the visualization is to fade out points on a selection. This works. Congruent with this, a new trendline should appear only for those selected points as well as an updated slope equation and R2 value. The fading of points and updating of slope equation/R2 values is working on selection. However, the trendline appears to be truncated and not scaled correctly, but I can't figure out why.
Here is a working version.
Following the on.change the following code is executed:
filteredData = filterJSON(data, 'name', value); // gets filtered json data
var x = d3.scaleLinear()
.range([0,width]);
var y = d3.scaleLinear()
.range([height,0]);
var xSeries1 = filteredData.map(function(e) { return e.x; }); // new x values
var ySeries1 = filteredData.map(function(e) { return e.y; }); // new y values
var rsq1 = leastSquares(xSeries1,ySeries1); // calculates r2/slope etc. - see function below
// Add trendline
ptAx1 = d3.min(xSeries1);
ptAy1 = rsq1[0] * d3.min(xSeries1) + rsq1[1];
ptBy1 = d3.min(ySeries1);
ptBx1 = (d3.min(ySeries1) - rsq1[1]) / rsq1[0];
svg.append("line")
.attr("class", "regression")
.attr("x1", x(ptAx1))
.attr("y1", y(ptAy1))
.attr("x2", x(ptBx1))
.attr("y2", y(ptBy1));
// calculate linear regression
function leastSquares(xSeries,ySeries) {
var reduceSumFunc = function(prev, cur) { return prev + cur; };
var xBar = xSeries.reduce(reduceSumFunc) * 1.0 / xSeries.length;
var yBar = ySeries.reduce(reduceSumFunc) * 1.0 / ySeries.length;
var ssXX = xSeries.map(function(d) { return Math.pow(d - xBar, 2); })
.reduce(reduceSumFunc);
var ssYY = ySeries.map(function(d) { return Math.pow(d - yBar, 2); })
.reduce(reduceSumFunc);
var ssXY = xSeries.map(function(d, i) { return (d - xBar) * (ySeries[i] - yBar); })
.reduce(reduceSumFunc);
var slope = ssXY / ssXX;
var intercept = yBar - (xBar * slope);
var rSquare = Math.pow(ssXY, 2) / (ssXX * ssYY);
return [slope, intercept, rSquare];
}
This code works well when all data points (no filtering of data), but doesn't when filtering occurs.
This is all points - trendline ok
This is filtered points - trendline truncated
It looks like you left "min" where you meant "max" in assigning values to ptBy1 and ptBx1
Made this change in your "blockbuilder" and it seemed to work as intended.

D3 map, 'd' attribute

(sorry for my english bad level)
Hi I'm using D3 for the first time with mithril js. The map is ok but I have a problem with colors of provinces and it comes from the 'd' attribute to get the id of provinces.The attribute is undefined and I don't understand what is 'd' exactly. is mithril the problem? is there an other way to get 'd' attribute?
controller.map = function(el){
var width = 1160;
var height = 960;
var scale = 10000;
var offset = [width / 2, height / 2];
var center = [0, 50.64];
var rotate = [-4.668, 0];
var parallels = [51.74, 49.34];
var projection = d3.geo.albers()
.center(center)
.rotate(rotate)
.parallels(parallels)
.scale(scale)
.translate(offset)
;
var path = d3.geo.path()
.projection(projection)
;
var svg = d3.select(el).append("svg")
.attr("width",width)
.attr("height",height)
;
d3.json("belprov.json",function(error,be){
if (error) return console.error(error);
var bounds = path.bounds(topojson.feature(be, be.objects.subunits));
var hscale = scale*width / (bounds[1][0] - bounds[0][0]);
var vscale = scale*height / (bounds[1][1] - bounds[0][1]);
scale = (hscale < vscale) ? hscale : vscale;
offset = [width - (bounds[0][0] + bounds[1][0])/2,
height - (bounds[0][1] + bounds[1][1])/2];
var centroid = d3.geo.centroid(topojson.feature(be, be.objects.subunits));
center = [0, centroid[1]];
rotate = [-centroid[0],0];
projection = d3.geo.albers()
.center(center)
.rotate(rotate)
.parallels(parallels)
.scale(scale)
.translate(offset);
svg.selectAll(".province")
.data(topojson.feature(be, be.objects.provinces).features)
.enter().append("path")
.attr("class", function(d) { return "province " + d.id })
.attr("d", path)
;
})
};
The "d" attribute in a path object defines the successive coordinates of the points through which the path has to go (it also gives indication about whether the path should use bezier curves, straight lines, etc.). See some documentation here.
Be careful: in d3, d is often used as a parameter for anonymous functions representing the data currently binded to the current element. So the two are completely different things.
Here, your line
.attr("d", path)
should probably look more like
.attr("d", function(d){return d.path})
i.e., take the field path within the data elements.
You can do something like this to color diffrent paths:
//make a color scale
var color20 = d3.scale.category20();
//your code as you doing
//on making paths do
svg.selectAll(".province")
.data(topojson.feature(be, be.objects.provinces).features)
.enter().append("path")
.attr("class", function(d) { return "province " + d.id })
.style("fill", function(d){return color(d.id);})//do this to color path based on id.
.attr("d", path)

How to update a D3 chart dynamically

I am trying to create an easy way to create and update donut charts (And other charts, later) in an application. This is built on top of D3.
I've created a drawDonutChart as shown below:
var data = [50, 50];
var dataTwo = [75, 25];
var options = {
colors: ["#0074D9", "#7FDBFF"],
};
function createDonutChart(data, height, width, domElement, options) {
var radius = Math.min(width, height);
var color = d3.scale.category20();
var donut = d3.layout.pie().sort(null);
var arc = d3.svg.arc()
.innerRadius(radius/4)
.outerRadius(radius/2);
var svg = d3.select(domElement).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(donut(data))
.enter().append("path")
.attr("fill", function(d, i) {
if (options.colors == null || options.colors == "undefined") {
return color(i);
} else {
return options.colors[i];
}
}).attr("d", arc);
}
var donutChart = createDonutChart(data, 50, 50, ".slices", options);
when you have a in the page, this will draw a donut chart there.
I would like to write a function updateDonutChart(originalChart, newData), pass it (donutChart, dataTwo) and have it transition this data into the chart instead of the old data.
I've been looking at multiple examples on the D3 website but I haven't been able to get something to work this way (where you simply pass the old chart, and new data). It might be simple, I am just new to D3 :)
Thanks in advance for the help.
So, it wasn't clear to me from the signature of your update call if you wanted to create a new SVG under a possibly new DOM element every time you updated the chart. Also, I am not sure if you are using the word transition to specify a D3 transition of just a change in the data (although that would be simple enough to add). I any case, a simple adaptation to your code to do what you want is in this FIDDLE.
function updateDonutChart(data, options) {...

Adding labels to a pv.Layout.Stack (streamgraph) in Protovis

I'm working with the Protovis library to do a streamgraph of data. I want to label the different layers with the "words" array. I can't seem to get the words to line up how I'd like. I want them to be inserted where the graph is the largest for that particular layer, similar to this site:
http://mbostock.github.com/protovis/ex/jobs.html
var words = [
"tasty","delicious","yum","scrumpious","dry"];
var data = [
[23,52,6,3,16,35,24,12,35,119,2,5,65,33,81,61,55,122,3,19,2,5,65,33,81,61,55,122,3,19,54,72,85,119,23,52,6,3,16,35],
[43,2,46,78,46,25,54,72,85,119,23,52,6,3,16,35,24,12,35,119,23,52,6,3,16,35,24,12,35,119,2,5,65,33,81,61,55,122,3,19],
[2,5,65,33,81,61,55,122,3,19,54,72,85,119,23,52,6,3,16,35,2,5,65,33,81,1,5,12,95,14,12,8,84,115,15,27,6,31,6,35],
[2,5,6,3,1,6,5,12,32,191,142,22,75,139,27,32,26,13,161,35,21,52,64,35,21,61,55,123,5,142,54,58,8,11,53,2,64,3,16,35],
[2,5,65,33,81,61,55,122,3,19,54,72,85,119,23,52,6,3,16,35,2,5,65,33,81,61,55,123,5,142,54,58,8,11,53,2,64,3,16,35]];
var w = 800,
h = 300,
x = pv.Scale.linear(0, 40).range(0, w),
y = pv.Scale.linear(0, 600).range(0, h);
var vis = new pv.Panel()
.canvas('streamgraph')
.width(w)
.height(h);
vis.add(pv.Layout.Stack)
.layers(data)
.order("inside-out")
.offset("wiggle")
.x(x.by(pv.index))
.y(y)
.layer.add(pv.Area)
.fillStyle(pv.ramp("#aad", "#556").by(Math.random))
.strokeStyle(function () { this.fillStyle().alpha(.5) });
vis.render();
Try this:
vis.add(pv.Layout.Stack)
.layers(data)
.order("inside-out")
.offset("wiggle")
.x(x.by(pv.index))
.y(y)
.layer.add(pv.Area)
.fillStyle(pv.ramp("#aad", "#556").by(Math.random))
.strokeStyle(function () { this.fillStyle().alpha(.5) })
// this is new code:
.anchor("center").add(pv.Label)
.def("max", function(d) {return pv.max.index(d)})
.visible(function() {return this.index == this.max() })
.text(function(d, p) {return words[this.parent.index]});
Basically this adds a whole bunch of labels to your areas, But then only makes them visible at the index where the value is the maximum, by defining a function max on the series. I adapted this code from the code in the link you sent.

Categories

Resources