Why is this animation so slow in FireFox? - javascript

I'm using D3.js to manipulate some SVG elements. I learned (the hard way) that newer versions of FireFox don't really handle D3's force layout well. So I switched to a simple rotation and it's STILL running crappy in Firefox. In the following code, group1 is an array of 200 <circle> svg elements which I create dynamically:
function orbit( target, first ) {
/* Other easing options here: https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease */
var ease = ( first ) ? 'sin-in' : 'linear';
target
.transition()
.duration(40000)
.ease( ease )
.attrTween("transform", rotTween)
.each('end', function(){ orbit( group1, false ); } );
function rotTween() {
var i = d3.interpolate(0, 360);
return function(t) {
return "rotate(" + i(t) + ","+width/2+","+height/2+")";
};
}
}
orbit( group1, true );
It's perfect smooth in Chrome but it chugs along like a Choo Choo train in Firefox.
As requested, here is how group1 is selected:
var makeNode = function(coeficient, x, y) {
coeficient = coeficient || 1;
return {
radius: (Math.random() * coeficient ).toFixed(2),
cx: function() { return x || Math.round(Math.random()*width) },
cy: function() { return y || Math.round(Math.random()*height) }
}
};
var nodes1 = d3.range(300).map( function(){ return makeNode(1.9); } );
var nodes2 = d3.range(700).map( function(){ return makeNode(.6); } );
// var nodes2 = [];
var svg = d3.select('#sky_svg');
var group1 = svg.append('g').attr("class", "group1");
var group2 = svg.append('g').attr("class", "group2");
var addNodes = function(group, nodes) {
for (var i=0; i<nodes.length; i++){
var node = nodes[i];
var circle = group.append('circle');
circle
.attr("r", node.radius )
.attr("cx", node.cx )
.attr("cy", node.cy )
.attr("stroke-width", 8 )
.attr("stroke", "transparent")
.style("fill", "#FFFFFF");
}
}
addNodes( group1, nodes1 );
addNodes( group2, nodes2 );

I also, consistently have problems with FireFox in rendering svg transformations that IE/Chrome handle with no problem. Follow the Posts:
Google Search: "Looking for SVG that was/is slow in Firefox"
You can also search on: Firefox's Gecko rendering engine+SVG, and see that Firefox has a poor rep for responsive SVG rendering.
My suggestion is to keep pressure on FireFox to fix this poor performance in dynamic SVG.

Related

D3 only render to certain depth

For d3, given an array of nested objects.
Is it possible to only render a certain amount of depth?
I am basing off of some sunburst examples online like :
https://github.com/Nikhilkoneru/react-d3-zoomable-sunburst/blob/master/src/index.js
Using the .selectAll method from d3, can I only limit the sunburst to render 'X' depths, instead of rendering the entire array of nested objects?
I'm trying to render a really large array, and it causes a really laggy experience.
This is the selectAll that I'm using from the github example.
svg.selectAll('path')
.data(partition(root)
.descendants())
.enter()
.append('path')
.style('fill', (d) => {
let hue;
const current = d;
if (current.depth === 0) {
return '#33cccc';
}
if (current.depth <= 1) {
hue = hueDXScale(d.x0);
current.fill = d3.hsl(hue, 0.5, 0.6);
return current.fill;
}
if(current.depth <= 2){
console.log("depth > 2: ", d)
current.fill = current.parent.fill.brighter(0.5);
const hsl = d3.hsl(current.fill);
hue = hueDXScale(current.x0);
const colorshift = hsl.h + (hue / 4);
return d3.hsl(colorshift, hsl.s, hsl.l);
}
})
.attr('stroke', '#fff')
.attr('stroke-width', '1')
.on('click', d => click(d, node, svg, x, y, radius, arc))
.on('mouseover', function (d) {
if (props.tooltip) {
d3.select(this).style('cursor', 'pointer');
tooltip.html(() => {
const name = utils.formatNameTooltip(d);
return name;
});
return tooltip.transition().duration(50).style('opacity', 1);
}
return null;
})
.on('mousemove', () => {
if (props.tooltip) {
tooltip
.style('top', `${d3.event.pageY - 50}px`)
.style('left', `${props.tooltipPosition === 'right' ? d3.event.pageX - 100 : d3.event.pageX - 50}px`);
}
return null;
})
.on('mouseout', function () {
if (props.tooltip) {
d3.select(this).style('cursor', 'default');
tooltip.transition().duration(50).style('opacity', 0);
}
return null;
});
As you can see, from the current.depth, i'm able to filter out what depth is more than 2.
Is it possible to add display: none to anything more than depth of 2, or is there a better way to not render anything more than depth of 2 from current

d3-attrTween with custom function.(What did I misunderstand about tween function?)

I have a question about attrTween (sometimes tween()).
I understood custom tween function as
after " attrTween('d' " argument,
I define the custom function.
So, I wrote the custom function as below.
d3.selectAll('circle#circles1')
.transition()
.attrTween('d',function(){
let interpolator=d3.interpolateArray(sdata.vader,sdata1.vader);
return function(t){
return d3.select(this).attr('cy',interpolator(t))
}
})
What I intended is
For All the circles I drew, makes a transition. The transition
is attrTween. The changes is based on data array tied into the
circles. Original data array is sdata and the cy value in the
sdata is sdata.vader. And the transition is heading toward
sdata1.and cy value for sdata1 is sdata1.vader.
To access all the cy value for every single circle, I used
d3.select(this).attr('cy')
However, no error message is shown but no animation was made either.
What did I misunderstand for the custom tween function?
Can anyone help me to fix this code?
Thank you inadvance.
Full code is in the following link.
https://codepen.io/jotnajoa/pen/WNQeEBE
There are multiple problems in the example code, which is not minimal. Providing a minimal, reproducible example would really help solve the problems.
usage of HTML Id to multiple elements.
In HTML, and id attribute must be unique. Here, ids are assigned to groups of circles. A class attribute should be used for this purpose, not an id.
.attr('id','circles1')
should be:
.attr('class','circles1')
Accordingly, the attrTween should lookup the circles with class circle1, rather than the unique circle with id #circle1
d3.selectAll('circle#circles1')
should be
d3.selectAll('.circles1')
Id (or class) assigned in the wrong place.
The circles1 class is assigned before the creation of the circle, hence the instructions applies to an empty selection. The class attribute should be set right after circles have been created.
.attr('id','circles1')
.enter()
.append('circle')
should be
.enter()
.append('circle')
.attr('class','circles1')
Wrong attribute tweened
The attribute to transition is the circle's cy attribute, not a path's d attribute. Hence
.attrTween('d',function(){
should be
.attrTween('cy',function(){
Wrong data interpolated
sdata.vader and sdata1.vader do not exist, sdata and sdata1 seem to be arrays of objects, which in turn do have a vader property.
You probably want d.vader, and the corresponding .vader in sdata1, which would be sdata1[i].vader, in case items are the same order in both arrays.
Interpolating original measures instead of coordinates.
cy is originally defined as:
height-yscale(d.vader)
In the interpolator function, the scale function should also be used.
The attrTween function calls becomes:
.attrTween('cy',function(d, i){
//console.log( i, height-yscale(d.vader), height-yscale(sdata1[i].vader))
let interpolator=d3.interpolateArray(height-yscale(d.vader), height-yscale(sdata1[i].vader));
return function(t) { return interpolator(t)}
})
Using attrTween where not needed.
Simply transitioning the circles with attr is sufficient for this use case, there is no need to define an interpolator.
d3 will move the position of circles from the original position to the destination, interpolating implicitly.
d3.selectAll('.circles1')
.transition()
.duration(2000)
.attr('cy',function(d, i){
return height-yscale(sdata1[i].vader)
})
I added a long duration for demo purpose, to make obvious that the circles move to the correct location. Once in their final position, they disappear, because they are under the pink circles.
P.S. Same set of corrections is applicable to circles2 set whenever relevant.
Demo of the solution in the snippet below, as codepen does not allow to save modifications without creating an account.
var svg;
var xscale;
var yscale;
var sdata;
var xAxis;
var yAxis;
var width=1500;
var height=500;
var margin=50;
var duration =250;
var vader ='vader'
var textblob='textblob'
var delay =5000;
var tbtrue=false;
var areas
var circles1,circles2;
var sdata1,sdata2
d3.csv('https://raw.githubusercontent.com/jotnajoa/Javascript/master/tweetdata.csv').then(function(data){
svg=d3.select('body').append('svg').attr('width',width).attr('height',height)
var parser = d3.timeParse("%m/%d/%y")
// data를 처리했고, date parser 하는 법 다시한번 명심하자.
sdata = data;
sdata.forEach(function(d){
d.vader = +d.vader;
d.textblob= + d.textblob;
d.date=parser(d.date)
})
// scale을 정해야 함. 나중에 brushable한 범위로 고쳐야함. nice()안하면 정렬도안되고, 첫번째 엔트리 미싱이고
// 난리도 아님.
xscale=d3.scaleTime()
.domain(d3.extent(sdata, function(d) {return d.date }))
.range([0,width*9/10])
.nice()
yscale =d3.scaleLinear()
.domain(d3.extent([-1,1]))
.range([height*4/5,height*1/5])
.nice()
//yaxis는 필요 없을 것 같은데.
//캔버스에 축을 그려야 함 단, translate해서 중간에 걸치게 해야함.
svg.append('g').attr('class','xaxis')
.call(d3.axisBottom(xscale))
.attr('transform','translate('+margin+','+height*1/2+')')
//sdata plotting
var circles = svg.append('g').attr('class','circles')
var area = svg.append('g').attr('class','pathline')
firststage();
//generator로 데이터를 하나씩 떨어뜨리도록 한다.
function firststage(){
function* vaderdropping(data){
for( let i=0;i<data.length;i++){
if( i%50==0) yield svg.node();
let cx = margin+xscale(data[i].date)
let cy = height-yscale(data[i].vader)
circles.append('circle')
.attr('cx',cx)
.attr('cy',0)
.transition()
.duration(duration)
.ease(d3.easeBounce)
.attr('cy',cy)
.attr('r',3)
.style('fill','rgba(230, 99, 99, 0.528)')
}
yield svg.node()
}
//generator 돌리는 부분
let vadergen = vaderdropping(sdata);
let result = vadergen.next()
let interval = setInterval(function(){
if(!result.done) {
vadergen.next();
}
else {
clearInterval(interval)
}
}, 100);
setTimeout(secondstage,5000)
}
function secondstage(){
function* textblobdropping(data){
for( let i=0;i<data.length;i++){
if( i%50==0) yield svg.node();
let cx = margin+xscale(data[i].date)
let cy = height-yscale(data[i].textblob)
circles.append('circle')
.attr('cx',cx)
.attr('cy',0)
.transition()
.duration(duration)
.ease(d3.easeBounce)
.attr('cy',cy)
.attr('r',3)
.style('fill','rgba(112, 99, 230, 0.528)')
}
yield svg.node()
}
//generator 돌리는 부분
let textblobgen = textblobdropping(sdata);
let tresult = textblobgen.next()
let tinterval = setInterval(function(){
if(!tresult.done) {
textblobgen.next();
}
else {
clearInterval(tinterval)
}
}, 100);
setTimeout(thirdstage,2500)
}
function thirdstage(){
//진동을 만들기 위해서,
//베이다와 텍스트 블랍 값을 플립한거다 (제발 워크 아웃하길...)
//그 다음 트윈으로 sdata 와 sdata1을 왔다갔다 하게하면 되지않을까?
sdata1 = sdata.map(function(x){
var y={};
y['date']=x.date;
y['vader']=x.textblob;
y['textblob']=x.vader;
return y});
sdata2 = sdata.map(function(x){
var y={};
y['date']=x.date;
y['vader']=0;
y['textblob']=0;
return y});
d3.selectAll('circle').transition()
.duration(3500)
.style('fill','rgba(1, 1, 1, 0.228)')
//areas는 일종의 함수다, 에리아에다가 데이터를 먹이면,
//에리아를 그리는 역할을 하는것임.
areas = d3.area()
.x(function(d){return margin+xscale(d.date)})
.y0(function(d){return height-yscale(d.vader)})
.y1(function(d){return height-yscale(d.textblob)})
.curve(d3.curveCardinal)
//이렇게 하지말고, sdata2도 만들었으니까 2->1->0 반복하는
// 무한반복 on('end','repeat') loop를 만들어보자.
var uarea=area.append('path')
setTimeout(repeat,500)
function repeat(){
uarea
.style('fill','rgba(112, 99, 230, 0.4)')
.attr('d', areas(sdata))
.transition()
.duration(500)
.attrTween('d',function(){
var interpolator=d3.interpolateArray(sdata,sdata1);
return function(t){
return areas(interpolator(t))
}
})
.transition()
.duration(500)
.attrTween('d',function(){
var interpolator=d3.interpolateArray(sdata1,sdata2);
return function(t){
return areas(interpolator(t))
}
})
.transition()
.duration(500)
.attrTween('d',function(){
var interpolator=d3.interpolateArray(sdata2,sdata);
return function(t){
return areas(interpolator(t))
}
})
.on('end',repeat)
}
setTimeout(fourthstage,500)
}
function fourthstage(){
// console.log(d3.selectAll('circle#circles1').node())
circles1=svg.append('g').selectAll('circle').data(sdata)
.enter().append('circle').attr('class','circles1')
.attr('cx',function(d){return margin+xscale(d.date)})
.attr('cy',function(d){return height-yscale(d.vader)})
.style('fill','green')
.attr('r',3)
circles2=svg.append('g').selectAll('circle').data(sdata)
.enter().append('circle').attr('class','circles2')
.attr('cx',function(d){return margin+xscale(d.date)})
.attr('cy',function(d){return height-yscale(d.textblob)})
.style('fill','pink')
.attr('r',3)
d3.selectAll('.circles1')
.transition()
.duration(5000)
.attr('cy',function(d, i){
return height-yscale(sdata1[i].vader)
})
// d3.selectAll('circle#circles2')
// .transition()
// .attr('cy',function(d){return 0})
//tween 팩토리를 정의해야한다.
//주의사항, 리턴을 갖는 함수여야한다는 것.
//왜 꼭 return function(){}을 해야하나?
/*
function movey(d2){
let y1 = this.attr('cy')
let y2 = d2.vader
let interpolate=d3.interpolate(y1,y2);
interpolate;
} 하면 안되나??
*/
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Trying to get two circles to transition simultaneously

Trying to get two circles (one red, one blue) to move to center of screen from opposite directions. Can only get the second circle to do it - unsure as to why.
I tried everything from switching up the order of functions being called to switching variable names
var svg = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 200);
var circles = svg.append('circle')
.attr('cx',50).attr('cy',50).attr('r',10).style('fill','red');
var circlesTwo = svg.append('circle')
.attr('cx',50).attr('cy',50).attr('r',10).style('fill','blue');
animation();
animationTwo();
function animation() {
svg.transition()
.duration(750)
.tween("precision", function() {
var area = d3.interpolateRound(0, 300);
return function(t) {
minArea = area(t);
render();
};
})
}
function animationTwo() {
svg.transition()
.duration(750)
.tween("precision", function() {
var area = d3.interpolateRound(600, 300);
return function(t) {
minArea = area(t);
renderTwo();
};
})
}
function render() {
circles.attr('cx',minArea);
}
function renderTwo() {
circlesTwo.attr('cx',minArea);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Expected results are two circles coming to their respective positions (from off screen originally).
Actual results are I am only getting my blue circle to work.
Applying the transitions to the SVG selection is not a very idiomatic D3: you should apply them to the elements that are moving (i.e., the circles). That, by the way, is the cause of the problem you're facing: one transition is cancelling the other.
This happens because since your transitions have no name, null is used (link):
selection.transition([name]) <>
Returns a new transition on the given selection with the specified name. If a name is not specified, null is used.
Then, because all of them have the same name (null), the last one cancels the first one:
The starting transition also cancels any pending transitions of the same name on the same element that were created before the starting transition.
Therefore, to apply multiple transitions to the same element, you have to name them:
function animation() {
svg.transition("foo")
//etc...
}
function animationTwo() {
svg.transition("bar")
//etc...
}
Here is your code with that change:
var svg = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 300);
var circles = svg.append('circle')
.attr('cx', 50).attr('cy', 50).attr('r', 10).style('fill', 'red');
var circlesTwo = svg.append('circle')
.attr('cx', 50).attr('cy', 50).attr('r', 10).style('fill', 'blue');
animation();
animationTwo();
function animation() {
svg.transition("foo")
.duration(750)
.tween("precision", function() {
var area = d3.interpolateRound(0, 300);
return function(t) {
minArea = area(t);
render();
};
})
}
function animationTwo() {
svg.transition("bar")
.duration(750)
.tween("precision", function() {
var area = d3.interpolateRound(600, 300);
return function(t) {
minArea = area(t);
renderTwo();
};
})
}
function render() {
circles.attr('cx', minArea);
}
function renderTwo() {
circlesTwo.attr('cx', minArea);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Again, have in mind that I'm simply answering your question here: my advice is that you should refactor that transition code completely.

Stroke-dasharray tween on multiple paths

I am projecting multiple line paths on a map and hoping to use stroke-dash interpolation on them to show path movements.
I want to use the same Tween function on all the paths, but it seems as though the Tween is returning the same value for every path. All of the lines seem to be projecting fine; it's only the Tween that's causing some issue.
It causes this issue, where the paths show stroke-dashes:
It should instead look like the following:
My lines are projected using the following standard process for a leaflet x/y conversion:
var svg = d3.select(map.getPanes().overlayPane).append("svg");
var g = svg.append("g").attr("class", "leaflet-zoom-hide");
var transform = d3.geo.transform({
point: projectPoint
});
var d3path = d3.geo.path().projection(transform);
var toLine = d3.svg.line()
.interpolate("linear")
.x(function(d,i) {
return applyLatLngToLayer(d).x
})
.y(function(d,i) {
return applyLatLngToLayer(d).y
});
g.selectAll(".lineConnect").remove();
var linePath = g.selectAll(".lineConnect")
.data(series)
.enter()
.append("path")
.attr("class", "lineConnect")
linePath.attr("d", toLine)
And here you see the function that calls the Tween:
function transition() {
linePath.transition()
.duration(700).attrTween("stroke-dasharray", tweenDash);
}
function tweenDash() {
return function(t) {
var l = linePath.node().getTotalLength();
interpolate = d3.interpolateString("0," + l, l + "," + l);
return interpolate(t);
}
}
Your code assumes that you have only a single element in the selection (linePath.node()) -- you're getting the length of the first element only. You could use for example .each() to make it work for every line:
function transition() {
d3.select(this).transition()
.duration(700).attrTween("stroke-dasharray", tweenDash);
}
function tweenDash() {
var that = this;
return function(t) {
var l = that.getTotalLength();
interpolate = d3.interpolateString("0," + l, l + "," + l);
return interpolate(t);
}
}
linePath.each(transition);

how to do a simple looping carousel of svg:g elements with D3.js

I am trying to make a scrollable list that is part of a bigger d3.js generated chart. clicking on list item acivates changes in the rest of the chart.
How would you make a simple animated looping carousel of svg:g elements with D3.js using the reusable chart pattern ?
Wired to jQuery-mousewheel or some forward and back buttons for example.
http://jsbin.com/igopix/2/edit
NameLoop = function () {
var scroll = 0;
function chart(selection) {
selection.each(function (data) {
var len = data.length,
scroll = scroll % len + len; // keep scroll between 0 <-> len
var nameNodes = d3.select(this).selectAll("g")
.data(data, function(d){return d.ID;} );
var nameNodesEnter = nameNodes.enter().append("g")
.attr("class", "nameNode")
.attr("transform", function(d, i) {
// implement scroll here ??
return "translate(30," + (i+1)*20 + ")";
})
.append("text")
.text(function(d){return d.ID;});
});
return chart;
}
chart.scroll = function(_) {
if (!arguments.length) return scroll;
scroll = _;
return chart;
};
return chart;
};
data = [{ID:"A"},{ID:"B"},{ID:"C"},{ID:"D"},{ID:"E"}];
var svg = d3.select("body").append("svg")
.attr("width", 200)
.attr("height",200)
.attr("id","svg");
var nameloop = NameLoop();
d3.select("#svg").datum(data).call(nameloop);
var body = d3.select("body").append("div");
body.append("span").on("click",function(d){ scroll(1) }).html("forward ");
body.append("span").on("click",function(d){ scroll(-1) }).html("back");

Categories