I've been trying to convert a nice D3 chart example ( https://jsfiddle.net/thudfactor/HdwTH/ ) to an Angular2 component with the new D3 v4.
I do however get a "cannot read property text of null" exception with the following code:
var textLabels = labelGroups.append("text").attr({
x: function (d, i) {
var centroid = pied_arc.centroid(d);
var midAngle = Math.atan2(centroid[1], centroid[0]);
var x = Math.cos(midAngle) * cDim.labelRadius;
var sign = (x > 0) ? 1 : -1
var labelX = x + (5 * sign)
return labelX;
},
y: function (d, i) {
var centroid = pied_arc.centroid(d);
var midAngle = Math.atan2(centroid[1], centroid[0]);
var y = Math.sin(midAngle) * cDim.labelRadius;
return y;
},
'text-anchor': function (d, i) {
var centroid = pied_arc.centroid(d);
var midAngle = Math.atan2(centroid[1], centroid[0]);
var x = Math.cos(midAngle) * cDim.labelRadius;
return (x > 0) ? "start" : "end";
},
'class': 'label-text'
}).text(function (d, i) { <--------------- exception
return d.data.label;
});
labelgroups is a selection, append should work, so it must be the .attr({}) causing the problem. It does however work fine in the jsfiddle.
Has this syntax changed in D3 v4? How would it be correct?
New answer
Because d3-selection-multi has been deprecated, the adequate solution is just using the regular attr method:
selection.attr("foo", foo)
.attr("bar", bar)
etc...
Previous answer
From D3 v4 onwards you cannot use objects to set attr or style anymore. To do so, you have to reference the mini library D3-selection-multi:
<script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
After doing that, change your code from attr to attrs (yes, like a plural):
var textLabels = labelGroups.append("text").attrs({
//mind the 's' here-------------------------ˆ
});
Do the same for the styles: it should be styles, as a plural, not style.
If you don't want to change all this, simply do as the "regular" way: set x, y, text-anchor and class in separate attr.
Here is the selection-multi documentation: https://github.com/d3/d3-selection-multi/blob/master/README.md#selection_attrs
Related
I'm trying to create a running line chart where it animates changes and the X and Y axis and the line itself.
I've started with the basic animation explanation at:
https://observablehq.com/#d3/learn-d3-animation?collection=#d3/learn-d3
And created my own (see code and link below)
My problem is that as soon as I add the await chartBug.update the Y axis starts flickering.
See animated gif below:
I think I need the await, to wait for the animation to complete before I start the next animation, and control the smooth speed of the animation.
While I'm here I would like also to ask how can I force the axis to draw the start and end ticks.
Thanks for the help
Here's a link to the page on www.observablehq.com
Here's the Animated gif of the problem - the source code is below:
chartBug = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
const zx = x.copy(); // x, but with a new domain.
const zy = y.copy(); // x, but with a new domain.
const line = d3
.line()
.x(d => zx(d.date))
.y(d => y(d.close));
const path = svg
.append("path")
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("stroke-miterlimit", 1)
.attr("d", line(data));
const gx = svg.append("g").call(xAxis, zx);
const gy = svg.append("g").call(yAxis, y);
return Object.assign(svg.node(), {
update(step) {
const partialData = [];
for (let i = 0; i < step; i++) {
partialData.push(data[i]);
}
const t = svg.transition().duration(50);
zx.domain(d3.extent(partialData, d => d.date));
gx.transition(t).call(xAxis, zx);
zy.domain([0, d3.max(partialData, d => d.close)]);
gy.transition(t).call(yAxis, zy);
path.transition(t).attr("d", line(data));
return t.end();
}
});
}
{
replay2;
for (let i = 0, n = data.length; i < n; ++i) {
await chartBug.update(i);
yield i;
}
}
the issue seems to be on your yAxis function, every new update and rescale yAxis is recreated and the domain is redraw and removed, if you don't mind keep it you can remove call(g => g.select(".domain").remove() from it.
You need to await to update after the transition is done. I think this also answer your other question
yAxis = (g, scale = y) => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(scale).ticks(height / 40))
I've been trying to convert a nice D3 chart example ( https://jsfiddle.net/thudfactor/HdwTH/ ) to an Angular2 component with the new D3 v4.
I do however get a "cannot read property text of null" exception with the following code:
var textLabels = labelGroups.append("text").attr({
x: function (d, i) {
var centroid = pied_arc.centroid(d);
var midAngle = Math.atan2(centroid[1], centroid[0]);
var x = Math.cos(midAngle) * cDim.labelRadius;
var sign = (x > 0) ? 1 : -1
var labelX = x + (5 * sign)
return labelX;
},
y: function (d, i) {
var centroid = pied_arc.centroid(d);
var midAngle = Math.atan2(centroid[1], centroid[0]);
var y = Math.sin(midAngle) * cDim.labelRadius;
return y;
},
'text-anchor': function (d, i) {
var centroid = pied_arc.centroid(d);
var midAngle = Math.atan2(centroid[1], centroid[0]);
var x = Math.cos(midAngle) * cDim.labelRadius;
return (x > 0) ? "start" : "end";
},
'class': 'label-text'
}).text(function (d, i) { <--------------- exception
return d.data.label;
});
labelgroups is a selection, append should work, so it must be the .attr({}) causing the problem. It does however work fine in the jsfiddle.
Has this syntax changed in D3 v4? How would it be correct?
New answer
Because d3-selection-multi has been deprecated, the adequate solution is just using the regular attr method:
selection.attr("foo", foo)
.attr("bar", bar)
etc...
Previous answer
From D3 v4 onwards you cannot use objects to set attr or style anymore. To do so, you have to reference the mini library D3-selection-multi:
<script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
After doing that, change your code from attr to attrs (yes, like a plural):
var textLabels = labelGroups.append("text").attrs({
//mind the 's' here-------------------------ˆ
});
Do the same for the styles: it should be styles, as a plural, not style.
If you don't want to change all this, simply do as the "regular" way: set x, y, text-anchor and class in separate attr.
Here is the selection-multi documentation: https://github.com/d3/d3-selection-multi/blob/master/README.md#selection_attrs
On a dc.geoChoroplethChart, I'm setting the radius of geojson points using the pointRadius method of the path:
.geoPath().pointRadius(function(feature, index) {
var v = placeGroup.all().filter(function(item) { return item.key === feature.id; })[0].value;
return (v == 0)? 0 : pointScale(v);
});
I'm finding that it works well, but on redraw() the sizes of the points are not adjusted. They are adjusted on a render(). How do I get them to be adjusted with a redraw() as well?
Here's the full chunk of code for the geo chart, in case it's relevant
var projection = d3.geo.mercator()
.scale(1)
.translate([0, 0]);
var path = d3.geo.path()
.projection(projection);
var width = 280,
height = 200,
b = path.bounds(places), // [[left, top], [right, bottom]]
x_extent = Math.abs(b[1][0] - b[0][0]),
y_extent = Math.abs(b[1][1] - b[0][1]),
s = (.95 / Math.max(x_extent / width, y_extent / height)),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
// Update projection with our actual data
projection
.scale(s)
.translate(t)
;
var mapchart = dc.geoChoroplethChart("#map-chart");
var valueDomain = [0, placeGroup.top(1)[0].value];
var maxPointRadius = Math.min(width, height) / 40,
minPointRadius = maxPointRadius / 2;
var pointScale = d3.scale.linear()
.domain(valueDomain)
.range([minPointRadius, maxPointRadius]);
mapchart.width(width)
.height(height)
.projection(projection)
.dimension(placeDim)
.group(placeGroup)
.colors(d3.scale.quantize().range(['#feb24c','#fd8d3c','#fc4e2a','#e31a1c','#bd0026'])) //first three '#ffffcc','#ffeda0', '#fed976', last one,'#800026'
.colorDomain(valueDomain)
.colorCalculator(function (d) { return d ? mapchart.colors()(d) : '#ccc'; })
.overlayGeoJson(places.features, "placeLayer", function (d) {
return d.id;
}).geoPath().pointRadius(function(feature, index) {
var v = placeGroup.all().filter(function(item) { return item.key === feature.id; })[0].value;
return (v == 0)? 0 : pointScale(v);
});
It looks like the geoChoroplethChart won't redraw the geojson unless the projection has changed. (It isn't expecting you to change the geoPath - as stated in the documentation, that's mostly a convenience method for reading and determining the center.)
https://github.com/dc-js/dc.js/blob/develop/src/geo-choropleth-chart.js#L169-L171
As a workaround, I'd suggest forcing a redraw by resetting the projection each time the chart redraws. Something like:
mapchart.on('preRedraw', function() {
mapchart.projection(projection);
});
https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#basemixinon--basemixin
I have a normal sequence sunburst jsfiddle as you can it is working normally.
I am trying to improve this sunburst to get a zooming technique.
with the following code I can et one step zooming,
var path = vis.data([json]).selectAll("path")
.
.
.on("click", click)
.each(stash);
the click function is:
function click(d)
also the code has the arctween, stash and reposition functions as below
function arcTween(a){
var i = d3.interpolate({x: a.x0, dx: a.dx0}, a);
return function(t) {
var b = i(t);
a.x0 = b.x;
a.dx0 = b.dx;
return arc(b);
};
}
function stash(d) {
d.x0 =0;// d.x;
d.dx0 =0;// d.dx;
}
function reposition(node, x, k) {
node.x = x;
if (node.children && (n = node.children.length)) {
var i = -1, n;
while (++i < n) x += reposition(node.children[i], x, k);
}
return node.dx = node.value * k;
}
this is the final version of my sunburst with zooming JSfiddle
the Question is: How can I make the sequences sunburst works with zooming approach?
and how to stop zoomin and o back as [bilevel or zoomable sunburst]
I was trying out the code from this site: http://bl.ocks.org/bycoffe/3230965
I keep having the error that "n is undefined".
Upon further scaling down, i have come to the conclusion that the problem is with these lines:
(function() {
var width = 800;
var height = 700;
var padding = 10;
var k;
var node;
var pixelLoc = d3.geo.mercator();
pixelLoc.scale(2000);
svg = d3.select('#map')
.append('svg:svg')
.attr('width', width)
.attr('height', height);
d3.json('coordinates.json', function(coordinates) {
var coords = [];
var xs = [];
var ys = []
for (alias in coordinates) {
coords.push(coordinates[alias]);
xs.push(coordinates[alias][0]);
ys.push(coordinates[alias][1]);
}
var minX = d3.min(xs);
var maxX = d3.max(xs);
var xScale = d3.scale.linear().domain([minX, maxX]).range([-50, -30]);
var minY = d3.min(ys);
var maxY = d3.max(ys);
var yScale = d3.scale.linear().domain([minY, maxY]).range([-20, -10]);
d3.json('medals.json', function(medals) {
var pointScale = d3.scale.sqrt().domain([0, 80]).range([0, 75]);
nodes = []
for (i=0; i<medals.length; i++){
node = medals[i];
node.coordinates = coordinates[node.alias];
node.cx = xScale(pixelLoc(node.coordinates)[0]);
}
})
The issue arises with the last line:
node.cx = xScale(pixelLoc(node.coordinates)[0]);
However, I still have no idea what do they mean by "n is undefined". Anybody able to help?
If you are using d3.min :
Change the library from d3.min.js to d3.js you will probably find out that the error changes to
TypeError: array is undefined
Which means the functions min and max don't work propperly .
var minX = d3.min(xs);
//Change this for actual min value and max for actual max value and it should work.