I have been working on a D3 Zoombable Sunburst based on the following example: https://observablehq.com/#d3/zoomable-sunburst.
From the modifications I have made, the diagram is near to my requirements. However, I am having issues in forcing the first inner ring to have a fixed thickness. As I understand it, the sizing is calculated based on the JSON data.
I have looked at the following StackOverflow post as a starting point: D3 Sunburst. How to set different ring\level widths but the section of code I need to modify differs as the Zoomable Sunburst is using different arc variables. Being new to D3's I am not to sure why these differ.
The code I need to rework is where the inner and outer radius's are getting set:
var arc = d3.arc()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.padAngle(d => Math.min((d.x1 - d.x0) / 2, 0.005))
.padRadius(radius * 2.5)
.innerRadius(d => d.y0 * radius)
.outerRadius(d => Math.max(d.y0 * radius, d.y1 * radius - 1));
On a final point - How can I ensure these calculations are retained when the user clicks through different layers? Based on the various tweaks I was testing in an attempt to fix the thickness of a ring, the original calculations are wiped.
Any help is appreciated.
Related
I've made a chart similar to the one pictured here.
Of course d3 can create the y axis, but it turns out that d3 can also give us the logic for making these arcs. Example snippet below:
var dScale = d3.scaleLinear()
.domain([-90,90])
.range([0,Math.PI]);
let arc = d3.arc()
.innerRadius(0)
.outerRadius(function(d) {return yScale(d.y)})
.startAngle(function(d) {return dScale(d.top)})
.endAngle(function(d) {return dScale(d.bottom)});
However, creating an "axis" for these degrees (dScale()) in my snippet seems way harder -- to the point where it doesn't feel like d3 anymore.
Question
Unless I'm mistaken, we'd have to get real hacky at this point and hardcode lines, ticks and axis labels using dScale, but is anyone able to help me see a more "d3-ic" (analog of pythonic?) way of achieving a degree scale?
I'm just starting out with D3 and am quickly understanding that it's a pretty low level tool.
I'm using D3 to produce a Marimekko chart using this great example by Mike Bostock in
b.locks, which is in all honestly a way too advanced place to start for me, but I started using D3 because I need a Marimekko chart, so here I am.
The x-axis here has ticks, 0 to 100% with 10% intervals. If my understanding of these code excerpts is correct...
Set the x axis to a linear scale
var x = d3.scale.linear().range([0, width - 3 * margin]);
Give the x-axis 10 ticks
var xtick = svg.selectAll(".x").data(x.ticks(10))
In my usage case , I'd like to have the x-axis ticks at the irregular intervals inherent to a Marimekko chart, and the axis labels to be the category, rather than a percentage.
The desired behaviour, as far as x-axis labelling, can be illustrated by this b.locks example by 'cool Blue'
I've got as far as understanding that I need a ordinal axis rather than a linear one (as in this excerpt of cool Blue's code)
var padding = 0.1, outerPadding = 0.3,
x = d3.scale.ordinal().rangeRoundBands([0, width], padding, outerPadding);
How can I modify Mike Bostock's code to give me an example where the x-axis ticks label the column (ideally centrally), as opposed to providing a %age of the width?
I wouldn't say that D3 is that low level, since it has a lot of abstractions. However, D3 is not a charting tool (and, in that sense, it is low level...), which makes things hard for a beginner.
However, you're lucky: the changes needed here are minimal. First, you'll pass the correct data to the selection that generates the axis...
var xtick = svg.selectAll(".x")
.data(segments)
//etc..
... and then use the same math for the translate, but adding half the sum:
.attr("transform", function(d, i) {
return "translate(" + x((d.offset + d.sum / 2) / sum) + "," + y(1) + ")";
});
Of course, you'll print the key, not a percentage:
xtick.text(function(d) {
return d.key
});
Here is the updated bl.ocks: https://bl.ocks.org/anonymous/09a8881e5bab2b12e7fd46c90a63b3fd/fd7b1a7b20f8436666f1544b6774778e748934ba
I can take a set of triplets [X,Y,Z] and immediately generate a (smooth) contour plot using Python and matplotlib with a single call to tricontour(). One can also generate contours 'easily' using plot.ly, but I find it to be unacceptably slow. (Also, I'm not interested in the MATLAB solution, which is similar to the Python)
I'm looking for similar functionality using d3.js. I would settle for a "surface plot" instead of contours, or a "heat map" without contour lines.
I can see how to generate a colored Delaunay triangulation and/or a colored Voronoi Tesselation, but the question of how to generate a contour plot in d3 from irregular data points seems to still be an open one (even though the question on this was prematurely closed!).
So far, all I've seen are approaches "by hand", using Radial basis functions (gaussian blur) or grid interpolation using Barycentric interpolation.
I'd even be willing to 'live with' Gouraud-shading or Coon-gradients on a Delaunay triangulation, but apparently "advanced shading methods" like Gourand or Coon gradients are not in "regular" SVG but are proposed for SVG2...not sure where that leaves me with d3 & (regular) SVG. It seems like doing this SVG gradient-shading by hand would be a major pain.
Is there a "better" package-y way to do this, i.e. something that doesn't require so much 'custom' code? (Maybe via some multidimensional Bezier routine I haven't found yet?)
I'll post a Fiddle with my starting point: a colored Voronoi tesselation: https://jsfiddle.net/k2v2jy7s/1/. Can you help me take this from "blocky" to "smooth" (and maybe even show contour lines)?
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var npoints = 1000;
var sites = d3.range(npoints)
.map(function(d) { return [Math.random() * width, Math.random() * height]; });
// values at data points / colors being mapped = "zvals"
var kx = 3.14159/(width*0.5);
var ky = 3.14159/(height*0.5);
var zvals = d3.range(npoints)
for (i = 0; i < npoints; i++) {
zvals[i] = (1.0 + Math.cos(kx*sites[i][0]) * Math.cos(ky*sites[i][1]))/2.0;
zvals[i] *= zvals[i];
}
var g = svg.append("g")
.attr("transform", "translate(" + 0+ "," + 0 + ")");
var voronoi = d3.voronoi()
.extent([[-1, -1], [width + 1, height + 1]]);
var polygon = svg.append("g")
.attr("class", "polygons")
.selectAll("path")
.data(voronoi.polygons(sites))
.enter().append("path")
.style('fill', function(d,i){ return d3.hsl( zvals[i]*310, 1, .5); })
.call(redrawPolygon);
function redrawPolygon(polygon) {
polygon
.attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; });
}
</script>
Update: Also found this blocks.org post on "Gradient Heatmaps", which as I mentioned is the sort of result I'd be willing to live with, but again that's a large quantity of custom code. Would really prefer a compact 'stock' solution, a la tricontour().
5 ½ years, and no answers to this question!
Well, I've also been looking into how to generate contours from a series of [X,Y,Z] points in Javascript, but have not yet found the best or most complete solution. A lot of solutions I came across via Googling (such as d3-contour) are designed for an evenly spaced grid of values, not an irregular series of points as you might obtain from a land survey.
d3-tricontour
The d3-tricontour library looks perhaps the most promising, though, so I might have a play around with it.
Here's an example of what it can generate:
(The labels are optional.)
Apparently it uses the delaunay and meandering triangles algorithms to convert arbitrary points into triangles and then contour geometry. The algorithm works in O(n) where n is the number of edges, meaning it's very fast and scales perfectly well.
To learn more you can visit their:
Github repository
Many examples on Observable
Alternatives
Otherwise, there might be other ways to do this. If working with one of the grid-based libraries, I think the general process would be to:
Convert arbitary [X,Y,Z] points into a grid — the Delaunay algorithm is probably a great place to start (see d3-delaunay or other delaunay libraries)
Find the Z value for each point in the grid using some kind of interpolation (the maths for that, I'm not sure about)
Then feed that result into one of the grid-based contouring libraries
Constraining Contours
Also take note that creating contours from real world terrain also requires "constraining" some edges so that contours don't crossover ridgelines where they shouldn't.
CDT-JS is a library web app (with no separate library available as yet) that calculates constrained Delaunay triangulation, which might be useful for this case.
Otherwise, in theory, you might be able to create this kind of functionality by injecting additional [X,Y,Z] points along your lines of contraint prior to rendering. But I haven't tested this approach.
I'm doing a data visualisation with d3. To give you some context,
the graph contains about 400 nodes (all data is loaded from multiple
json files) that are connected to each other
they are all mapped by year in a timeline (x axis)
the position in the y axis is completely randomized
the nodes have all different sizes
Now my question:
How can I distribute the nodes in the y axis so that they don't overlap?
You can checkout the full sourcecode on the GitHub Repository (work in progress - currently on the real-database branch).
This is a screenshot of how it currently looks:
Basically, in the tick() function, reset the nodes array x values to what you want them to be (presumably some scale to do with year), and the node and links will be drawn at those x values, and subsequent force calculations will start again from those values too
force.on("tick", function() {
// Adjust to what you want nodePos to be, here I'm just doing it by index
graph.nodes.forEach (function(nodePos,i) {
nodePos.x = i * 15;
//nodePos.x = xscale (data[i].year); // or whatever
})
// then normal node/link layout
I've forked this standard force-directed example by blt909 to show one way it could be done -->
http://jsfiddle.net/vztydams/
PS If you have a lot of items and very few discrete x values, best to give them a bit of wiggle room at first (i.e. a range in x they're contained to rather than a value), then slowly narrow that range down. Otherwise nodes will get 'stuck' behind each other.
Edit 02/03/16:
Hi Alvaro, essentially the graph.nodes is your linked data, as these are the objects that are attached to the displayed nodes as the data.
So if I set up a scale, and stick in a random year per datum:
var dom = [1994,2014];
var xscale = d3.scale.linear().domain(dom).range([20,400]);
graph.nodes.forEach (function(datum) {
datum.year = dom[0] + Math.floor (Math.random() * (dom[1] - dom[0]));
});
...
We can then restrict the x position of each node's datum like this:
graph.nodes.forEach (function(d,i) {
//d.x = i * 15;
d.x = xscale(d.year);
})
(As I say, if you have a lot of nodes and few years, you'd be better restricting to a range and then narrowing that range down on each subsequent tick)
http://jsfiddle.net/vztydams/2/
I'm a newcomer to D3 and I'm trying to make a world globe with some points ("pins") on it. Demo here: http://bl.ocks.org/nltesown/66eee134d6fd3babb716
Quite commonly, the projection is defined as:
var proj = d3.geo.orthographic()
.center([0, 0])
.rotate([50, -20, 0])
.scale(250)
.clipAngle(90)
.translate([(width / 2), (height / 2)]);
the clipAngle works well for the svg paths, but not the pins (which are svg circles). As you can see on the demo, the pin that sits between Iceland and Greenland should be hidden (it's Taiwan).
So I suppose the problem comes from these lines, but I can't understand why:
.attr("transform", function(d) {
return "translate(" + proj([ d.lng, d.lat ]) + ")";
});
It is not sufficient to just set the clipping radius via clipAngle() to get the desired behavior. The projection alone will not do the clipping, but just calculate the projected coordinates without taking into account any clipping. That is the reason, why Taiwan gets rendered, although you expected it to be hidden.
But, thanks to D3, salvation is near. You just need to re-think the way you are inserting your circles representing places. D3 has the mighty concept of geo path generators which will take care of the majority of the work needed. When fed a projection having a clipping angle set, the path generator will take this into account when calculating which features to actually render. In fact, you have already set up a proper path generator as your variable path. You are even correctly applying it for the globe, the land and the arcs.
The path generator will operate on GeoJSON data, so all you need to do is convert your places to valid GeoJSON features of type Point. This could be done with a little helper function similar to that used for the arcs:
function geoPlaces(places) {
return places.map(function(d) {
return {
type: "Point",
coordinates: [d.lng, d.lat]
};
});
}
With only minor changes you are then able to bind these GeoJSON data objects to make them available for the path generator which in turn takes care of the clipping:
svg.selectAll(".pin") // Places
.data(geoPlaces(places))
.enter().append("path")
.attr("class", "pin")
.attr("d", path);
Have a look at my fork of your example for a working demo.