I overlay a leaflet map with the d3 library. The points get displayed, as well as the map. However, the Colorbrewer does not work...
Its supposed to color the dots on the map according to their value, instead, they stay black. I could hardcode that with something like if value == 0.1 but thats not what I want...
Thats my code, the structure of cities.json can be seen here, the colorbrewer is this one
...
// add colorbrewer
var colorScale = d3.scale.quantize()
.domain([extent[0], extent[1]])
.range(colorbrewer.YlGn[n]);
// uses d3 data join method
// for each data point a "path" is created
var feature = g.selectAll("path")
.data(collection.features)
.enter()
.append("path")
.style("fill", function(d) {
colorScale(d.properties.pop_max);
});
...
Any ideas what is going wrong?!
There are negative values in my d.properties.pop_max. Could that be the problem?
You're missing a return in the fill function.
...
...
.style("fill", function(d) {
// add a 'return' here.
return colorScale(d.properties.pop_max);
});
Also, you can just write .domain(extent) directly when initializing your colorScale since d3.extent returns a two element [min, max] array
var colorScale = d3.scale.quantize()
.domain(extent) // instead of .domain([extent[0], extent[1]])
.range(colorbrewer.YlGn[n]);
Related
I was trying to plot a time-pressure line chart.
The data is an array of objects, named "res"
[
{Time: ,
Psi:
},
...
]
I defined the x, y axis, and line function like these
var x = d3.scaleTime().domain(d3.extent(res, d => d.Time)).range([0, width]),
y = d3.scaleLinear().domain([0,d3.max(res, d=>d.psi)]).range([height, 0]),
var line = d3.line()
.x(function(d) { return x(d.Time) })
.y(function(d) { return y(d.psi) });
Every thing was very standard set-up.
When I insert line element to the chart, I found two ways to insert them.
Method 1, with "datum"
svg.append("path")
.datum(res) //"datum"
.attr("class", "line")
.attr("d", line);
Method 2, with by line(res)
svg.insert("path")
.attr("class", "line")
.attr("d", line(res)); //line(res), like a function
Both methods work, just wondering are there any difference between these two methods?
Thanks,
The difference between the two methods is that by method one, you have assigned res as the "datum" object of the node. That means that if you were to store it in a variable - or I think even if you would re-select them (not sure though) - you should be able to reliably access the current value using .attr('...', function(d) { });. That can be useful if you want to do stuff to it, like animations or styling, and the value might update often - so it's a hassle to carry it around.
Other than that, there is no real difference. One of the things I like to use .datum() for is when I have a container for every shape and I want to add a node to every container, then it might be useful to use container.select('text').datum((d) => d) to feed the datum object from the container to its text child.
I'm sure this has been covered in another question on here however, after trying many different examples on my code i cannot seem to get this to work.
I am trying to make the nodes on my force-directed graph a different shape depending on the name of the node, for example, if the node is named 'Switch' it should be displayed as a square.
I have worked out the set up for changing the colour of the nodes based on the data and would like a similar thing for the shape but can't get it to work using d3.v4.
Any help?
var color = d3.scaleOrdinal(d3.schemeCategory10);
var shape = d3.symbolTypes;
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 15)
.attr("fill", function(d) { return color(d.group); })
.attr("d", d3.symbol()
.type(function (d) { return shape(d.name);}))
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
Many Thanks
Faye
You can't use:
var shape = d3.symbolTypes;
....
shape(d.name);
since d3.symbolTypes is not defined in d3 v4 (it was in some versions of v3), in v4 use d3.symbols which is an array not a function.
But, as with color, you could create an ordinal scale for shape:
var color = d3.scaleOrdinal(d3.schemeCategory20);
var shape = d3.scaleOrdinal(d3.symbols);
Now all you have to do is append that shape:
.append('path')
.attr("d", d3.symbol().type( function(d) { return shape(d[property]);} ) );
Since you were appending circles, and are now appending paths, you'll need to change .append('circle'), and as circles have cx cy elements, you need to change to a transform where you set their position.
Here is a bl.ock which should show this in practice, based on MBostock's force directed graph (here)
Keep in mind there are only seven shapes in the d3.symbols array.
Edit:
If you want to specify which shapes get displayed for each node based on a property (rather than letting the ordinal scale set the shape), you could add a property to your data which contains the name of a shape (eg: d3.symbolCross), or create a function which takes in a data value and outputs the name of a symbol. But an ordinal scale is easiest.
In Mike Bostock’s ‘Towards Reusable Charts’ why the initial data link to <p> element is made with datum(data):
d3.csv("sp500.csv", function(data) {
var formatDate = d3.time.format("%b %Y");
d3.select("#example")
.datum(data)
.call(timeSeriesChart()
.x(function(d) { return formatDate.parse(d.date); })
.y(function(d) { return +d.price; }));
});
while further link of data to <svg> element inside the chart() function is made with data([data]):
// Select the svg element, if it exists.
var svg = d3.select(this).selectAll("svg").data([data]);
// Otherwise, create the skeletal chart.
var gEnter = svg.enter().append("svg").append("g");
gEnter.append("path").attr("class", "area");
gEnter.append("path").attr("class", "line");
gEnter.append("g").attr("class", "x axis");
In his own answer Mike says that these two approaches are interchangeable, except the former doesn’t compute a join. So why to use data([data]) here?
Also I don’t quite get what happens if there is already an <svg> element as the comment line suggests. For me the enter selection is empty in this case and no further append will work…
I must be misunderstanding something…
Thanks for your help!
The advantage of using .data([data]) in the second case is that if there's no SVG, the handling of the enter selection adds it. If the SVG exists, the code is exactly equivalent to .datum(data) -- the data bound to the element is changed and nothing else happens.
I'm a complete noop to D3 and partly SVG, so I got a few basic questions.
First off, my code in question can be viewed at http://dotnetcarpenter.github.io/d3-test/ and I've used Simple Pie Chart example with D3.js and Pie Chart Update, II as examples to get a running start.
As you can see, the animation gets skewed in the end when the low path values switch to the higher values. This is obviously not what I want. I think I'm getting the order of calculations wrong but I'm not sure what to do. I'm using the code from the last example:
function change() {
//...
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
// where arcTween is
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
Another issue is placing labels on the sectors. I've put the update stuff in the change function and is able to read out and only render them if the value is between 0 and 100. I can't however place them in any way. Looking at the first example, I figure that I could do something like this:
text.data(data)
.text(setText)
.attr("transform", function (d) {
// we have to make sure to set these before calling arc.centroid
d.innerRadius = 0;
d.outerRadius = radius;
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle") //center the text on it's origin
Where text is a d3 selection and arc is: d3.svg.arc().outerRadius(radius)
But I get "Unexpected value translate(NaN,NaN) parsing transform attribute." warning in Firefox and the labels are written on top of each other.
I appreciate any help and hints. Thanks!
I finally figured it out.
Maintain sector order throughout an animation.
You'd be forgiven for thinking that object contancy had something do with it. I did. But it turns out to be much simpler than that.
Every pie chart is by default sorted by value. If you don't want to sort by value but e.g. by data list order, you just have to disable sorting.
var pie = d3.layout.pie() // get a pie object structure
.value(function(d) { // define how to get your data value
return d.value; // (based on your data set)
})
.sort(null); // disable sort-by-value
Positioning labels according to your chart
Basically, you need to calculate your label positions depending on the type of chart or graph, your trying to connect them to. In my case, it's a pie chart. So if I want d3 to help with the calculations, I need to tell centroid the inner and outer radius and, most importantly to my issue, the start and end angles. The latter was missing from my code. Getting these values is as simple as, calling our pie layout above with our dataset and then do a transform.
Note that you don't have to call .data() again if you created the SVG with d3 and already supplied your data wrapped in .pie() structure. That is, that you didn't select any existing SVG from your page.
var svg = d3.select("svg")
// do stuff with your svg
var pie = d3.layout.pie()
// set stuff on your layout
var text = svg.selectAll("text")
.data(pie(dataset)) // where dataset contains your data
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
});
I have to give credit to Philip Pedruco for helping me along the way.
Bonus info
Use viewBox if you want to position your SVG cross browser, not transform/translate.
// move pie to center
d3.select("svg").attr("viewBox", -radius + ","+ -radius +"," + size + "," + size)
I'm learning D3.js and trying to get my head around data keys used with streamgraphs. I would like to adapt the official streamgraph example:
...so that each path has an explicit data key, and so that the mouseover logs the data key.
The official example adds paths as follows:
var area = d3.svg.area()
.x(function(d) { console.log('x', d.data); return d.x * w / mx; })
.y0(function(d) { return h - d.y0 * h / my; })
.y1(function(d) { return h - (d.y + d.y0) * h / my; });
vis.selectAll("path")
.data(data0)
.enter().append("path")
.style("fill", function() { return color(Math.random()); })
.attr("d", area);
I tried adapting the code as follows, but I'm not sure how to change the structure of data0 (currently an array of arrays) to achieve what I want:
vis.selectAll("path")
.data(data0, function(d) { return d.name }) // Add key function
.enter().append("path")
.style("fill", function() { return color(Math.random()); })
.attr("d", area)
.on("mouseover", function (d,i) {
console.log("mouseover", d.name); // Log name property on mouseover
});
As it stands, without my having made any changes to the structure of data0, it unsurprisingly does not work. How can I add a name property to data0 without also messing up the area and .data() functions?
UPDATE: To be a bit clearer: the D3 docs say that the area function is expecting a two-dimensional array. So if I change data0 from a two-dimensional array, to an array of objects, each with a name key and a data key, how can I also change what I pass to area?
The data in the example doesn't have a "name" property, so you would need to add that to the data to use it. The data keys you refer to are used when merging/updating data, i.e. you have drawn some paths already and then update (some of them). The .data() function will try to figure out what data is updated and what data is new. If that doesn't work for you, you can use the data key to help it, i.e. in your case tell it that things with the same name are the same data.
If what you mean by data keys are "data legends", then you might want to take a look at the following examples where I've completely separated the placement of magnitudes, legend bullets and legend text in different areas of the charts.
Multiple D3 Pie Charts Mixed In With HTML Layout Constructs
Multiple D3 Horizontal Bar Charts Mixed In With HTML Layout Constructs
In each of the examples, you'll clearly see how the data is labeled, structured, passed in, and used.
I also tied them together through mouseover and mouseout events so that mousing over or out of any element causes all elements in a set to change color simultaneously.
I hope this helps.
Frank