I started with the default Sunburst example and tried to add an animation on click - clicking on a wedge makes that wedge the center element. This works great except in a few cases - it looks like if the depth goes above 2 (0 based) then some wedges don't animate correctly; they seem to start the animation in the wrong place, but end up in the correct place.
Here is what I think is the relevant code, and I hope it's useful to someone else:
animate = function (d) {
var oldDs = [], topD, includeds;
if (animating)
return;
animating = true;
topD = (d == selected && d.parent) ? d.parent : d;
if (selected == topD) {
animating = false;
return;
}
selected = topD;
svg.selectAll("path").filter(function (f) {
return (!isDescendant(f, topD))
}).transition().duration(1000).style("opacity", "0").attr("pointer-events","none");
includeds = svg.datum(topD).selectAll("path").filter(function (f) {
return isDescendant(f, topD);
});
// step 1 - save the current arc settings
includeds.data().each(function (d) {
oldDs[d.ac_id] = {x: d.x, dx: d.dx, y: d.y, dy: d.dy};
});
// step 2 - recalculate arc settings and animate
includeds.data(partition.nodes) // recalculates
.transition().duration(1000).attrTween("d", function (d) {
var oldD = oldDs[d.ac_id],
interpolator = d3.interpolateObject(oldD, d);
return function (t) {
return arc(interpolator(t));
}
}).style("opacity", "1").attr("pointer-events","all");
setTimeout(function(){ animating = false; }, 1000);
};
Because my code doesn't have anything to do with the depth, I expect the issue is somewhere else, but I haven't been able to find it. The glitch seems to affect all browsers (at least Chrome, Firefox, and IE).
So, my question: what's the issue and how can I fix it?
Related
i'm using a rowchart to show the total of sales by item of a salesman.
Already tried a composite chart unsuccessfully like many posts from the google, but none of the examples uses a rowchart.
I need to do like the image, creating the red lines to represent the sale value target for each item, but i dont know how, can you guys help me? Thanks!
Actually this is my code to plot the rowchart
spenderRowChart = dc.rowChart("#chart-row-spenders");
spenderRowChart
.width(450).height(200)
.dimension(itemDim)
.group(totalItemGroup)
.elasticX(true);
Obviously you need a source for the target data, which could be a global map, or a field in your data.
I've created an example which pulls the data from a global, but it would also take from the data if your group reduction provides a field called target.
Then, it adds a new path element to each row. Conveniently the rows are already SVG g group elements, so anything put in there will already be offset to the top left corner of the row rect.
The only coordinate we are missing is the height of the rect, which we can get by reading it from one of the existing bars:
var height = chart.select('g.row rect').attr('height');
Then we select the gs and use the general update pattern to add a path.target to each one if it doesn't have one. We'll make it red, make it visible only if we have data for that row, and start it at X 0 so that it will animate from the left like the row rects do:
var target = chart.selectAll('g.row')
.selectAll('path.target').data(function(d) { return [d]; });
target = target.enter().append('path')
.attr('class', 'target')
.attr('stroke', 'red')
.attr('visibility', function(d) {
return (d.value.target !== undefined || _targets[d.key] !== undefined) ? 'visible' : 'hidden';
})
.attr('d', function(d) {
return 'M0,0 v' + height;
}).merge(target);
The final .merge(target) merges this selection into the main selection.
Now we can now animate all target lines into position:
target.transition().duration(chart.transitionDuration())
.attr('visibility', function(d) {
return (d.value.target !== undefined || _targets[d.key] !== undefined) ? 'visible' : 'hidden';
})
.attr('d', function(d) {
return 'M' + (chart.x()(d.value.target || _targets[d.key] || 0)+0.5) + ',0 v' + height;
});
The example doesn't show it, but this will also allow the targets to move dynamically if they change or the scale changes. Likewise targets may also become visible or invisible if data is added/removed.
thank you, due the long time to have an answer i've developed a solution already, but, really thank you and its so nice beacause its pretty much the same ideia, so i think its nice to share the code here too.
The difference its in my code i use other logic to clear the strokes and use the filter value of some other chart to make it dynamic.
.renderlet(function(chart) {
dc.events.trigger(function() {
filter1 = yearRingChart.filters();
filter2 = spenderRowChart.filters();
});
})
.on('pretransition', function(chart) {
if (aux_path.length > 0){
for (i = 0; i < aux_path.length; i++){
aux_path[i].remove();
}
};
aux_data = JSON.parse(JSON.stringify(data2));
aux_data = aux_data.filter(venda => filter1.indexOf(venda.Nome) > -1);
meta_subgrupo = [];
aux_data.forEach(function(o) {
var existing = meta_subgrupo.filter(function(i) { return i.SubGrupo === o.SubGrupo })[0];
if (!existing)
meta_subgrupo.push(o);
else
existing.Meta += o.Meta;
});
if (filter1.length > 0) {
for (i = 0; (i < Object.keys(subGrupos).length); i++){
var x_vert = meta_subgrupo[i].Meta;
var extra_data = [
{x: chart.x()(x_vert), y: 0},
{x: chart.x()(x_vert), y: chart.effectiveHeight()}
];
var line = d3.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.curve(d3.curveLinear);
var chartBody = chart.select('g');
var path = chartBody.selectAll('path.extra').data([extra_data]);
path = path.enter()
.append('path')
.attr('class', 'oeExtra')
.attr('stroke', subGruposColors[i].Color)
.attr('id', 'ids')
.attr("stroke-width", 2)
.style("stroke-dasharray", ("10,3"))
.merge(path)
path.attr('d', line);
aux_path.push(path);
}
}
})
And that's how it looks
i have a problem combining manual update and zoom functionality in d3.
Here is a small demo code cut off out of a larger module which creates only a linear scale.
https://jsfiddle.net/superkamil/x3v2yc7j/2
class LinearScale {
constructor(element, options) {
this.element = d3.select(element);
this.options = options;
this.scale = this._createScale();
this.axis = this._createAxis();
this.linearscale = this._create();
}
update(options) {
this.options = Object.assign(this.options, options);
this.scale = this._createScale();
this.axis = this._createAxis();
this.linearscale.call(this.axis);
}
_create() {
const scale = this.element
.append('g')
.attr('class', 'linearscale')
.call(this.axis);
this.zoom = d3.zoom().on('zoom', () => this._zoomed());
this.element.append('rect')
.style('visibility', 'hidden')
.style('width', this.options.width)
.style('height', this.options.height)
.attr('pointer-events', 'all')
.call(this.zoom);
return scale;
}
_createScale() {
let range = this.options.width;
this.scale = this.scale || d3.scaleLinear();
this.scale.domain([
this.options.from,
this.options.to
]).range([0, range]);
return this.scale;
}
_createAxis() {
if (this.axis) {
this.axis.scale(this.scale);
return this.axis;
}
return d3.axisBottom(this.scale);
}
_zoomed() {
this.linearscale
.call(this.axis.scale(d3.event.transform.rescaleX(this.scale)));
let domain = this.axis.scale().domain();
this.element.dispatch('zoomed', {
detail: {
from: domain[0],
to: domain[1],
},
});
}
}
const scale = new LinearScale(document.getElementById('axis'), {
from: 0,
to: 600,
width: 600,
height: 100
});
document.getElementById('set').addEventListener('click', () => {
scale.update({
from: 0,
to: 100
});
});
Zoom x axis out to 0 - 10000 (random numbers)
Click on the "set" button
X axis sets the domain from 0 - 100
Start zooming out again
-> Expected: Zoom starts from the domain 0 - 100
-> Result: Zoom jumps back to the previous zoom level 0 - 10000
I know, that d3 is working with a scale copy and i'm updating the original scale but i don't find a way how to combine them or how to set the zoom level to the original scale.
https://github.com/d3/d3-zoom/blob/master/README.md#transform_rescaleX
Thanks!
You either recreate the zoom behaviour or just reset the current zoom transforms.
You should be able to reset each parameter( zoom and translate) or just reset them all. The following example also triggers the zoom specific events, but as you already have the domain set it should not be a visual problem.
Ex:
Add
this.element.select("rect").call(this.zoom.transform, d3.zoomIdentity);
in your update function.
More information regarding the zoom api in https://github.com/d3/d3-zoom/blob/master/README.md#zoom_transform . Also I found an example using a transition animation at https://bl.ocks.org/mbostock/db6b4335bf1662b413e7968910104f0f .
I also updated the fiddle:
update(options) {
this.options = Object.assign(this.options, options);
this.scale = this._createScale();
this.axis = this._createAxis();
this.linearscale.call(this.axis);
this.element.select("rect").call(this.zoom.transform, d3.zoomIdentity);
}
https://jsfiddle.net/x3v2yc7j/8/
I want to remove the axis from the Parallel coordinate on dragend. Here is my code snippnet.
It works partially as dimensions get removed but the lines corresponding to the dimensions still remain there.
I need to update the xscale.domain. But somehow it doesn't work
if (dragging[d] < 12 || dragging[d] > w() - 12)
{
pc.remove_axis(d, g);
}
pc.remove_axis = function (d, g) {
g_data = pc.getOrderedDimensionKeys();
g_data = _.difference(g_data, [d]);
xscale.domain(g_data);
g.attr("transform", function (p) {
return "translate(" + position(p) + ")";
});
g.filter(function (p) {
return p === d;
}).remove();}
Consider any d3 force-directed animation, such as:
http://bl.ocks.org/mbostock/1062288
Load that graph and let the nodes settle down so that they are not moving. Hover your mouse over an orange node and let the mouse be still. Click and do nothing else. You will see all the nodes move inwards a little and then expand back to normal.
I do not like that unnecessary movement and I need to remove it.
I believe it has to do with the graph being "reheated" (see https://github.com/mbostock/d3/wiki/Force-Layout
), which sets the alpha parameter to 0.1. Specifically, I see the unnecessary movement occur immediately after any of the following.
a new node is added to the graph
a node is deleted from the graph
force.drag() is called.
What exactly causes this "unnecessary movement", and how can I disable it? (without disabling node dragging)
The behavior comes mainly from the alpha. The alpha function within d3:
force.alpha = function(x) {
if (!arguments.length) return alpha;
x = +x;
if (alpha) {
if (x > 0) alpha = x; else alpha = 0;
} else if (x > 0) {
event.start({
type: "start",
alpha: alpha = x
});
d3.timer(force.tick);
}
return force;
};
So when alpha is called, it starts the tick event if greater than 0
Within the tick event, at the very beginning, you can see the cut off that is causing the 'freeze' you mentioned:
if ((alpha *= .99) < .0005) {
event.end({
type: "end",
alpha: alpha = 0
});
return true;
}
Now, if we look at the dragmove, it calls the resume function:
function dragmove(d) {
d.px = d3.event.x, d.py = d3.event.y;
force.resume();
}
which calls the alpha function with a value of 0.1:
force.resume = function() {
return force.alpha(.1);
};
To stop the jump and also the freeze at 0.0005, I don't see myself any public function you can use.
I have an example fiddle that removed the code from the tick function that causes the freezing and also changed the resume function to start at a smaller alpha.
I believe this gives the result your looking for but of course, I wouldn't really advise customizing the library.
Maybe someone knows how to override the tick function without having to modify the source.
in
// Toggle children on click.
function click(d) {
if (!d3.event.defaultPrevented) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
}
You could move the update to the children open only
// Toggle children on click.
function click(d) {
if (!d3.event.defaultPrevented) {
if (d.children) {
d._children = d.children;
d.children = null;
update();
} else {
d.children = d._children;
d._children = null;
}
}
}
so a node without children (orange) will not be updated on click.
Normally, when force.resume() is fired, the alpha value jumps from 0 to 0.1. By easing alpha up more slowly, we can cover up the "unnecessary movement".
I am still open to other ideas and solutions.
old function:
force.resume = function() {
return force.alpha(.1);
};
replacement function:
force.resume = function() {
var alpha = force.alpha()
if( alpha < 0.005 ){ alpha = 0.0055 }
else if( alpha < 0.11 ){ alpha += 0.0006 }
return force.alpha(alpha);
};
I am trying to animate shape continue from from X to Y distance, again return shape from Y to X using kinetic js animate function. eg. move shape from 0px to 200px and again return shape 200px to 0px.
thanks.
You are probably looking for a Tween here rather then an Animation. Check out the kinetic documentation for more information on Tweens, however what you are looking to do will probably look something like this:
var tween = new Kinetic.Tween({
node: myShape,
x: moveToX,
y: moveToY,
duration: 1
});
Now that you have a tween, you can play it, rewind it, pause it, reverse it (again, check out the documentation for all up to date information on Tweens).
So you can now do:
tween.play()
or
tween.reverse()
to accomplish what you are looking for.
Reference: http://www.html5canvastutorials.com/kineticjs/html5-canvas-stop-and-resume-transitions-with-kineticjs/
Update (as per comment below): If you want a looping affect, in an X direction, Y direction, or both. You can do something like:
var yPos = myShape.getAttr('y'),
xPos = myShape.getAttr('x'),
maxX = 1000,
maxY = 1000,
yIncreasing = true,
xIncreasing = true; //lets assume myShape resides somewhere below the max
var anim = new Kinetic.Animation(function(frame) {
/* move the shape back and fourth in a y direction */
if (yPos < maxY && yIncreasing) {
hexagon.setY(yPos++);
} else {
if (yPos < 1) {
yIncreasing = true;
hexagon.setY(yPos++);
} else {
hexagon.setY(yPos--);
yIncreasing = false;
}
}
/* move the shape back and fourth in a x direction */
if (xPos < maxX && xIncreasing) {
hexagon.setX(xPos++);
} else {
if (xPos < 1) {
xIncreasing = true;
hexagon.setX(xPos++);
} else {
hexagon.setX(xPos--);
xIncreasing = false;
}
}
}, layer);
Note: I haven't ran this code, it should work. Using both will cause the shape to move diagonally, but hopefully this snipplet shows a solution to your problem.