I encapsulated d3 charts into function as suggested best practice from creator in blog Towards Reusable Charts. Is it possible to create optional functionalities on top of this chart, so calling specific function would trigger it, otherwise it would be omitted.
Working JSFiddle example (base working example from Rob Moore's blog)
In JS line 56 I added a function which I'd like to create and then conditionally call in line 67.
My current way of doing it, is creating a boolean and setting it to false and then calling function with argument true. Problem of doing it this way is that the code gets too many conditionals and edge cases.
P.S. this question is not meant to be a discussion how to correctly apply axis to the chart. This is just an example.
I think it's better to add additional functionalities after the chart is drawn
var runningChart = barChart().barPadding(2).fillColor('coral');
d3.select('#runningHistory')
.datum(milesRun)
.call(runningChart);
runningChart.x_axis(); // additional functionality
So that the original chart container can be saved in a variable and it can be used to append other functionalities. For example
function barChart() {
var charContainer;
function chart(selection){
charContainer = d3.select(this).append('svg')
.attr('height', height)
.attr('width', width);
}
chart.x_axis = function() {
// Add scales to axis
var x_axis = d3.axisBottom()
.scale(widthScale);
charContainer.append('g').call(x_axis);
return chart;
}
}
If there is any need to add additional functionality before the chart is drawn, then all the functionalities can be saved in a Javascript object and drawn like in this example. https://jsfiddle.net/10f7hdae/
I have been working on changing this block to v4 with my limited exposure to d3js.
The changes are made are;
"https://d3js.org/d3.v3.js" to "https://d3js.org/d3.v4.js"
.ease('linear') to .ease(d3.easeLinear)
which i understood further from the change readme here.
The changes results in the chart not being able to reach the cell.exit.transition() block when I tried to console.log the exit function, this is also evident as the grids do not exit and just append from new randomized data:
cell.exit().transition()
.delay(function(d, i) { return (n0 - i) * updateDelay; console.log(n0, n1);})
.duration(updateDuration)
.attr("width", 0)
.remove();
From the readme, there is no change in the transition methods but I am thinking this is due to the change in the select function. I am having trouble seeing what when wrong as there seems to be no errors within the console when I run this.
The problem here seems to be the infamous magic behaviour that was introduced by Mike Bostock (D3 creator) in D3 v2, and later removed in D3 v4.
According to Bostock:
D3 2.0 introduced a change: appending to the enter selection would now copy entering elements into the update selection [...] D3 4.0 removes the magic of enter.append. (In fact, D3 4.0 removes the distinction between enter and normal selections entirely: there is now only one class of selection.)
You can read more about this issue in these answers: https://stackoverflow.com/a/47032222/5768908 and https://stackoverflow.com/a/45093007/5768908
In your case, the solution is changing the update selection to this:
var cellUpdate = cell.selectAll("rect")
.data(d3.range(n1));
And then:
cellUpdate.exit()
//etc...
cellUpdate.enter()
.append("rect")
//etc...
Here is the updated bl.ocks: https://bl.ocks.org/GerardoFurtado/b0d66087d9888a2cac3a42b114e5e8c4/72a0e54de5ce8cba2c398b282d953dd5c2bcc66e
PS: for this to work in v4/5 (but not from v5.8 onwards) you have to change the text tween as well:
.tween("text", function() {
var self = this;
var i = d3.interpolateNumber(n0, n1);
return function(t) {
self.textContent = formatNumber(Math.round(i(t)));
};
});
I'm using the SVG located at http://upload.wikimedia.org/wikipedia/commons/3/32/Blank_US_Map.svg in a project and interacting with it with d3.js. I'd like to create a click to zoom effect like http://bl.ocks.org/2206590, however that example relies on path data stored in a JSON object to calculate the centroid. Is there any way to load path data in d3 from an existing SVG to get the centroid?
My (hackish) attempt so far:
function get_centroid(sel){
var coords = d3.select(sel).attr('d');
coords = coords.replace(/ *[LC] */g,'],[').replace(/ *M */g,'[[[').replace(/ *z */g,']]]').replace(/ /g,'],[');
return d3.geo.path().centroid({
"type":"Feature",
"geometry":{"type":"Polygon","coordinates":JSON.parse(coords)}
});
}
This seems to work on some states, such as Missouri, but others like Washington fail because my SVG data parsing is so rudimentary. Does d3 support something like this natively?
The D3 functions all seem to assume you're starting with GeoJSON. However, I don't actually think you need the centroid for this - what you really need is the bounding box, and fortunately this is available directly from the SVG DOM interface:
function getBoundingBoxCenter (selection) {
// get the DOM element from a D3 selection
// you could also use "this" inside .each()
var element = selection.node();
// use the native SVG interface to get the bounding box
var bbox = element.getBBox();
// return the center of the bounding box
return [bbox.x + bbox.width/2, bbox.y + bbox.height/2];
}
This is actually slightly better than the true centroid for the purpose of zooming, as it avoids some projection issues you might otherwise run into.
The accepted answer was working great for me until I tested in Edge. I can't comment since I don't have enough karma or whatever but was using this solution and found an issue with Microsoft Edge, which does not use x or y, just top/left/bottom/right, etc.
So the above code should be:
function getBoundingBoxCenter (selection) {
// get the DOM element from a D3 selection
// you could also use "this" inside .each()
var element = selection.node();
// use the native SVG interface to get the bounding box
var bbox = element.getBBox();
// return the center of the bounding box
return [bbox.left + bbox.width/2, bbox.top + bbox.height/2];
}
From here
The solution is to use the .datum() method on the selection.
var element = d3.select("#element");
var centroid = path.centroid(element.datum());
I read many codes about force layout with D3.js and I found that edges elements are only 'source' and 'target'. I want to generate a graph whose layout is like the 'Altas' in Gephi. In my data, edge has elements of 'weight', which describe the correlation of nodes it links, and this is supposed to be took into consideration when start the force layout. So that the similar nodes can gather together. Is there a way to implement so or the physics model used in force layout is irrelevant with the weight of edges?
Yes, that is possible in D3.js, however, I recommend the webcola library since it is more easy and faster and works very well with D3.js.
Each edge may contain other information besides source and target. So, it is easy to add a weight attribute, e.g.:
let edge = {
source: node1,
target: node2,
weight: 2
};
When using webcola (https://github.com/tgdwyer/WebCola/), you can add some constraints to use your weights, or you can use the linkDistance as a function, explained here: https://github.com/tgdwyer/WebCola/wiki/link-lengths, e.g.:
let weightFactor = 10;
let d3cola = cola.d3adaptor(d3)
.size([500, 400])
.linkDistance(function (l) { return l.weight * weightFactor; };
So, the steps are:
Build your graph with D3.js, however,
Simulate it with webcola
You can create an instance of a webcola simulation for d3.js like that:
d3cola.avoidOverlaps(true)
.handleDisconnected(false)
.start(30);
let simulation = this.d3cola
.nodes(graph.nodes) // graph is your graph
.links(graph.edges)
.start();
I want to achieve something like a growing arc which indicates 5 levels (see picture). My data has only an integer value which is between 1-5. You can ignore the icon in the middle for now. Is there any possibility to achieve something like that in d3? I couldn't find any example for this. Moreover I tried it with a cut off pie (donut) chart approach, but I couldn't make the growing arc... I would appreciate any help! Thanks for that.
You can do this with d3 without dependency on external images, SVG sprites or anything in the DOM — just d3.js.
Here's a working fiddle. The implementation is explained below.
But also, here's a more advanced fiddle that animates a clip-path over the growing arc. Check out its predecessor to see how the mask looks without clipping.
First, you need to represent the graphics as an array of data that you bind to with d3. Specifically, you need a color and a "line command" (the string you assign to d as in <path d="...">. Something like this:
var segmentData = [
{ color:"#ED6000", cmd:"M42.6,115.3c5.2,1.5,11,2.4,16.8,2.4c1.1,0,2.7,0,3.7-0.1v-2.2c-7,0-13.1-1.3-18.8-3.6L42.6,115.3z" },
{ color:"#EF7D00", cmd:"M25.7,99.3c4.3,4.7,9.5,8.6,15.3,11.3l-1.4,3.8c-6.9-2.4-13.2-6.1-18.6-10.8L25.7,99.3z" },
{ color:"#F4A300", cmd:"M23.7,97c-5.2-6.4-8.8-14-10.3-22.4L2.9,75.7c2.9,10,8.5,18.9,15.8,25.9L23.7,97z" },
{ color:"#F7BC00", cmd:"M13,71.5c-0.2-2-0.4-4-0.4-6c0-10.7,3.4-20.6,9.2-28.8L9.4,28.3c-5.6,9-8.9,19.6-8.9,30.9 c0,4.6,0.6,9.1,1.6,13.5L13,71.5z" },
{ color:"#FFCF36", cmd:"M63,15.7V0.8c-1-0.1-2.5-0.1-3.7-0.1c-19.9,0-37.5,9.9-48.1,25l12.7,8.6C33.1,23,46,15.7,63,15.7z" }
];
Then you need an empty <svg> and probably a <g> within it, into which to draw the graphics:
var svg = d3.select("body").append("svg")
.attr("width", 125)
.attr("height", 125);
var gauge = svg.append("g");
Then you use d3 binding to create the segments:
var segments = gauge.selectAll(".segment")
.data(segmentData);
segments.enter()
.append("path")
.attr("fill", function(d) { return d.color; })
.attr("d", function(d) { return d.cmd; });
This just creates the graphic, but doesn't color it based on an integer value. For that, you can define an update function:
function update(value) {
segments
.transition()
.attr("fill", function(d, i) {
return i < value ? d.color : "#ccc";
})
}
Calling update(4) will color all but the last segment. Calling update(0) color none (leaving all of them gray).
In the fiddle, there's also a tick() function that calls update with a new value on a setTimeout basis, but that's just for demo.
Finally, if you wish, you can wrap all that code up and create a reusable component by following the advice in [this article].(http://bost.ocks.org/mike/chart/)
since it is relatively simple picture, I'd use a sprite, with 5 variations.
That would be much easier than using d3 and gives the same result.
(you could use some online tool like http://spritepad.wearekiss.com/ )
If you want to mimic duolingo progress images you can just simply copy their solution with own images. They are using sprites as this one: http://d7mj4aqfscim2.cloudfront.net/images/skill-strength-sprite2.svg not the d3.js approach. This will save you a lot of time and effort.