Grid-lines are being recreated and overlaid on each other - javascript

I'm playing with some d3 code - to create the y axis I do the following :
function renderYAxis(svg) {
var yAxis = d3.svg.axis()
.orient("left")
.scale(_y.range([quadrantHeight(), 0]))
.tickFormat(d3.format("s"));
axisData = _currentData.filter(function(row) {
if ((row['filter1'] === _filter1)) {
return true;
}
}).filter(function(row) {
if ((row['filter2'] === _filter2)) {
return true;
}
}).map(function(d) {
return {
y: +d["Y"]
};
});
var minY2 = d3.min(axisData, function(d) { return d.y });
if (minY2 > 0) {
minY2 = 0;
};
_y.domain([minY2, d3.max(axisData, function(d) { return d.y })])
if (!_axesYG) {
_axesYG = svg
.append("g")
.attr("class", "y axis");
}
_axesYG
.attr("transform", function() {
return "translate(" + xStart() + "," + yEnd() + ")";
})
.transition()
.duration(1000)
.call(yAxis);
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> maybe following needs changing somehow? >>>>>>>>>>>>>>
d3.selectAll("g.y g.tick")
.append("line")
.classed("grid-line", true)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", quadrantWidth())
.attr("y2", 0);
}
The chart has a transition but after transitioning several times some of the grid lines are reproducing and being laid on top of each other - so producing some thicker lines. I've marked above where I think the problem may be, I'm unsure how to change this code - is there a standard approach?
A full working example of the behavior is saved here: http://plnkr.co/edit/JD52TfAddZSpNR3oaMRv?p=preview
If you hit the button several times you will see it is the common grid lines that are shared before and after the transition that are being recreated and overlaid. These two:
Any help much appreciated.

An easy solution is just setting the tick width with a negative value:
.innerTickSize(-quadrantWidth());
That way, you don't have to worry about appending, removing or updating the lines, and you won't have duplicated elements: the axis generator takes care of all that for you.
Here is the updated plunker: http://plnkr.co/edit/BoP4hEkILlwJzRuCJFBD?p=preview
EDIT: you mentioned in your answer that you're having problems with Nick Zhu's approach. That's because your selection is not correct. It should be something like this:
var lines = d3.selectAll("g.y g.tick")
lines.selectAll(".grid-line")
.remove();
lines.append("line")
.classed("grid-line", true)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", quadrantWidth())
.attr("y2", 0);
Here is the respective plunker: http://plnkr.co/edit/189hJBepdVVreLghBgc0?p=preview

Here is a simple fix (hack), since the original code structure is hard to change to follow General Update Pattern correctly:
// remove old ones
d3.selectAll(".grid-line.y-axis")
.remove();
// draw new ones
// add a new class y-axis to avoid deleting the x axis above
d3.selectAll("g.y g.tick")
.append("line")
.classed("grid-line y-axis", true)
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", quadrantWidth())
.attr("y2", 0);
http://plnkr.co/edit/wdQmllRrrILtXsarXqLY?p=preview
The more correct approach is to follow the General Update Pattern: https://bl.ocks.org/mbostock/3808234

just for completeness I thought I'd add the following, which I found in Nick Qi Zhu's book. I think it follows the general update pattern as well as grid-lines can. Although even adding this I still get a reproduction of the grid-lines!
function renderYGridlines() {
var lines = d3.selectAll("g.y g.tick")
.select("grid-line y-axis")
.remove();
lines = d3.selectAll("g.y g.tick")
.append("line")
.classed("grid-line", true)
lines.attr("x1", 0)
.attr("y1", 0)
.attr("x2", quadrantWidth())
.attr("y2", 0);
}

Related

Unable to add guideline to scatter plot in d3 v4

I have build a scatter plot in d3 v4 using following link: scatter plot
I have also used tipsy plugin for tooltip.
Now i wanted to add guidelines in this chart that is show guideline when user hovers over a circle and hide guidelines when out of focus. For this i stumbled upon a code which i tried to use but it is not showing anything.
Following is the code which i used:
var circles = svg.selectAll("circle").data(dataset).enter().append("circle");
circles.attr("cx",function(d){
return xScale(d[indicator1]);//i*(width/dataset.length);
})
.attr("cy",function(d){
return yScale(d[indicator2]);
})//for bottom to top
.attr("r", 10);
circles.attr("fill",function(d){
return "#469DDA";
});
circles.attr("stroke",function(d){
return "white";
});
circles.attr("class", "circles");
svg.style('pointer-events', 'all')
// what to do when we mouse over a bubble
var mouseOn = function() {
var circle = d3.select(this);
// transition to increase size/opacity of bubble
circle.transition()
.duration(800).style("opacity", 1)
.attr("r", 16).ease("elastic");
// append lines to bubbles that will be used to show the precise data points.
// translate their location based on margins
svg.append("g")
.attr("class", "guide")
.append("line")
.attr("x1", circle.attr("cx"))
.attr("x2", circle.attr("cx"))
.attr("y1", +circle.attr("cy") + 26)
.attr("y2", h - margin.t - margin.b)
.attr("transform", "translate(40,20)")
.style("stroke", "#ccc")
.transition().delay(200).duration(400).styleTween("opacity",
function() { return d3.interpolate(0, .5); })
svg.append("g")
.attr("class", "guide")
.append("line")
.attr("x1", +circle.attr("cx") - 16)
.attr("x2", 0)
.attr("y1", circle.attr("cy"))
.attr("y2", circle.attr("cy"))
.attr("transform", "translate(40,30)")
.style("stroke", "#ccc")
.transition().delay(200).duration(400).styleTween("opacity",
function() { return d3.interpolate(0, .5); });
// function to move mouseover item to front of SVG stage, in case
// another bubble overlaps it
/* d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
// skip this functionality for IE9, which doesn't like it
if (!$.browser.msie) {
circle.moveToFront();
}*/
};
// what happens when we leave a bubble?
var mouseOff = function() {
var circle = d3.select(this);
// go back to original size and opacity
circle.transition()
.duration(800).style("opacity", 1)
.attr("r", 10).ease("elastic");
// fade out guide lines, then remove them
d3.selectAll(".guide").transition().duration(100).styleTween("opacity",
function() { return d3.interpolate(.5, 0); })
.remove()
};
// run the mouseon/out functions
circles.on("mouseover", mouseOn);
circles.on("mouseout", mouseOff);
$('.circles').tipsy({
gravity: 'w',
html: true,
title: function() {
var d = this.__data__;
return "State: "+d.States+"<br>"+indicator1+" "+d[indicator1]+"<br>"+indicator2+" "+d[indicator2];
}
});
I am getting following result now:
When i checked the browser console i am getting following error:
If you are using d3.v4 , I think problem lays with transition's ease method
You should provide easing constant, instead of plain string
So, instead of using
circle.transition()
.duration(800).style("opacity", 1)
.attr("r", 16).ease("elastic");
You should write
circle.transition()
.duration(800).style("opacity", 1)
.attr("r", 16).ease(d3.easeElastic) // change

d3js group and line chart animation - legend toggling [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I am trying to build a line chart equilvant of this group chart - with legend toggling. I am not sure I've got the animation correct - and want to essentially make the charts sisters in terms of structure.
//group chart
http://jsfiddle.net/0ht35rpb/259/
//line chart
http://jsfiddle.net/0ht35rpb/262/
g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) {
return "translate(" + x0(d.State) + ",0)";
})
.selectAll("rect")
.data(function(d) {
return keys.map(function(key) {
return { key: key, value: d[key]};
});
})
.enter().append("rect")
.attr("x", function(d) {
return x1(d.key);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("width", x1.bandwidth())
.attr("height", function(d) {
return height - y(d.value);
})
.attr("fill", function(d, i) {
return z(d.key);
});
-- so the line one looks like this - but I think I am missing enter() parts
// define the line
var valueline = d3.line()
.curve(d3.curveBasis)
.x(function(d) {
return x(parseTime(d.date));
})
.y(function(d) {
return y(d.temperature);
});
g.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city")
.append("path")
.attr("class", "line")
.attr("d", function(d){
return valueline(d.values);
})
.style("stroke", function(d) {
return z(d.id);
});
also when it comes to toggling the legends how do I fix the line chart to animate the lines - and fix the domains - as one is now a timescale. Also in reference to both charts - should I place the "make bars" "make lines" code seen above - into an actual function - that gets reused during the update function method - for each chart?
Here's a fiddle that has the animations as per your requirement.
Relevant code:
y.domain([
d3.min(cities, function(c) {
if(filtered.indexOf(c.id) === -1) {
return d3.min(c.values, function(d) {
return d.temperature;
});
}
}),
d3.max(cities, function(c) {
if(filtered.indexOf(c.id) === -1) {
return d3.max(c.values, function(d) {
return d.temperature;
});
}
})
]);
g.select(".axis.axis--y").transition().duration(500)
.call(d3.axisLeft(y));
g.selectAll('.city path').transition().duration(500).attr('d', function(d) {
if(filtered.indexOf(d.id) > -1) {
return null;
} else {
return valueline(d.values);
}
});
Your code was missing a lot of things:
X axis is a time scale, and in your case, you don't need to update the x-axis in the update function as your toggling based on the city names and not the dates.
Event if you had a changing X-axis, you wouldn't change the domain that way. Time scale looks for dates and it seems like you were setting that to the city names. ("newKeys")
Setting the y-domain is to be based on the "cities" array as you are using that to render the chart. But in the update function, you seem to be using "data" array to set the y domain and hence the y-axis issue.
Added transition to the y-axis too.
var paths = svg.selectAll(".line").selectAll("path") is not what you want as you have the class "line" for the path itself. A relevant version would be: svg.selectAll("path.line")
Anyway, the filtering of the paths wouldn't work as the selection was wrong and as far as the filtering is concerned, the paths are calling the "line" function whereas the line function in your code is defined as "valueline"
Filtering of the paths in a similar fashion as yours would be correct in this way:
g.selectAll('.city path').transition().duration(500)
.attr('d', function(d) {
if(filtered.indexOf(d.id) > -1) {
return null;
} else {
return valueline(d.values);
}
});
Hope this helps. :)

D3.js: Attributes after enter().append() not setting

Like most people, I'm struggling with D3's data join mechanics. I have read every article on the subject, good and (mostly) bad. Christian Behrens' guest-and-chair party analogy is probably the best, though I warn readers that he neglects us about 2/3 through, beginning with "Now our update() function performs two different sets of actions" -- he doesn't clarify here that, apparently, DOM construction (append/remove) calls are specifically ignored by the update selection, while attribute calls are processed by all three selection types, despite all appearing seamlessly in one method chain. (For his part, Mike Bostock's several efforts at explaining data joins and method chaining range from mildly condescending to entirely complicating matters.)
I still have a problem understanding the membrane between data() and enter(), specifically when to save a variable and how calls in a given chain operate on what objects, and also which chain return value my variable saves, and how I should know that (clearly, attr does not affect the variable, but in a chain that includes a series of selects, data, and enters, which is returned?); hence my mild criticism of the otherwise excellent Behrens essay, because it has so much promise there.
Below, I have a force layout (could be any layout) that displays two nodes on startup, and if you click any of the nodes, a third should be added.
var graph = {
"nodes":[ {"name":"1" }, {"name":"2" } ],
"links":[ {"source":0,"target":1} ]
}
var width = 500, height = 400;
var force = d3.layout.force()
.size([width, height]);
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
var container = svg.append("g");
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(graph.links)
.enter()
.append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var nodes = container.append("g")
.attr("class", "nodes");
function update() {
var n = nodes.selectAll(".node").data(graph.nodes);
ne = n.enter()
.append("g")
.attr("class", "node")
.attr("cx", function(d) { return d.x; }) // sets on initial enter() but not on click
.attr("cy", function(d) { return d.y; }); // sets on initial enter() but not on click
ne.append("rect")
.attr("width", "20")
.attr("height", "20")
.attr("fill", "red");
return n;
} // end update()
var node = update();
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
node.on("click", function(d) {
graph.nodes.push({ "name":"3" });
update();
}); // end .on("click")
I made an update() function to economize, following Behrens. Setting cx and cy works fine initially but not when you click on a node. New nodes stay at [0,0]. I think there must be a problem in the way I manage the arguments and returns on update(). A better way to do this, one that actually works?
You're just missing two small things. First, you need to restart the force layout so that the node positions are updated, and you need to update node which you're using inside the tick handler function:
node.on("click", function(d) {
graph.nodes.push({ "name":"3" });
node = update();
force.start();
});
Complete demo here.
I ended up solving this on my own with some experimentation; it seems to have had to do with the fact that I needed to put the force.on("tick"...) and node.on("click") functions inside the update() function. If I didn't have the tick processor in there, it would not create a transform for the new node, which appears to conform to what I was seeing. In addition, I must call force.start() again after updating. I will post the working code at my next opportunity: it is on an offline laptop right now.

How to modify D3js graph depending on change of value?

I have a graph which I build using d3js and I want to draw red line or other kind of indicator on the graph to indicate when the graphs blue line's value hits 0(in this case its 6th of june). The example of a graph http://jsfiddle.net/wRDXt/81/
My starting point is:
if(dataset.value = 0){
//draw line
}
But I have no clue where to go from here. Any help would be greatly appreciated.
There are a few different ways to do this. One way would be to filter the dataset to create a new array that only had the entries with a value of 0.
var zeroData = dataset.filter(function(d){
if (d.value == 0) return d;
});
This new array could be used to draw lines at each zero point with a separate svg.selectAll(".redlines") using the new array as the data.
// plot lines
svg.selectAll(".redline")
.data(zeroData)
.enter()
.append("line")
.attr("class", "redline")
.attr("x1", function(d) { return xScale(d.date); })
.attr("x2", function(d) { return xScale(d.date); })
.attr("y1", 0)
.attr("y2", 250)
.style("stroke", "#ff0000");
http://jsfiddle.net/wRDXt/85/
Another way is to start by appending a "g" element for each data point instead of a circle then adding a circle and a line for each point. Styling can be used to make only the value == 0 points visible.
// plot data points as g elements
var datapts = svg.selectAll(".data-point")
.data(dataset);
var dataptsEnter = datapts
.enter()
.append("g")
.attr("class", "data-point")
.attr("transform", function(d) {
return "translate(" + xScale(d.date) + "," + yScale(d.value) + ")";
});
dataptsEnter
.append("circle")
.attr("class", "data-point-circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 5);
dataptsEnter
.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", -50)
.style("stroke", "#ff0000")
.style("opacity", function(d) {
if (d.value == 0) return 1;
return 0;
});
http://jsfiddle.net/wRDXt/82/
In the second approach, there is a red line for each datapoint but opacity is used to make only the zero value visible.
The first approach is the one I'd use.

select a specific class with d3js to animate

var data1 = [150,350,550]
var data2 = [100,300,500]
var sampleSVG = d3.select("body")
.append("svg")
.attr("width", 800)
.attr("height", 800);
var circles1 = sampleSVG
.append("g")
.attr("class", "circles1")
.selectAll(".circle1")
.data(data1)
.enter()
.append("circle")
.attr("class", "circle1")
.on("mousedown", animateFirstStep);
var circleAttributes1 = circles1
.attr("cx", function (d) { return d;})
.attr("cy", 200)
//.attr("class", function (d) { return "circle" + d;})
.attr("r", function(d) { return d/10;})
.style("fill", function(d){
var color;
if (d === 150){ color = "yellow";
} else if (d === 350) { color = "orange";
} else if (d === 550) { color = "red";
} return color;
})
function animateFirstStep(){
d3.selectAll(...??...)
.data(data1,function(d, i) { return d; })
.transition()
.delay(0)
.duration(2000)
.attr("r", 400)
.style("opacity", 0)
.each("end", animateSecondStep);
};
I have 3 circles and i want to click on one of them. When I click on one I want that one to grow bigger and disappear. the other 2 circles should also disappear but should NOT grow any bigger. Right now I name the class of each circle simply "circle1". But is also made a option(which are commented out) that gives each circle its own class based on the data. I have a function which animate the circles. But I don't know how to select a specific circle with a mouseclick and let that one grow bigger and disappear while the others don't grow but simply disappear. Can anyone help me out please??
You're on the right track, but instead of selecting elements by their class in the transition, I'd just bind the onclick event to each circle using the .on("click", ...) operator. You will then have access to each individual circle using the d3.select(this). Here's an example of what you can do with the circles1.on("click", ...) function (here I'm choosing how to animate the circles by their index i in the original data, but you can also filter by the value of d):
.on("click", function(d, i){
if (i == 0){
d3.select(this).transition()
.delay(0)
.duration(2000)
.attr("r", d)
.style("opacity", 0);
}
else{
d3.select(this)
.transition()
.delay(0)
.duration(2000)
.style("opacity", 0);
}
});
Complete working JSfiddle here.
Late to the party, but I think this is what you want: Fiddle
To "remember" the selected circle and the unselected circles, you need something like the following:
var grow;
var disappear;
Then modifying #mdml's answer a bit:
.on("click", function (d, i) {
// This is an assumption, I thought you wanted to remember
// so that you can toggle those states.
if (grow && disappear) {
disappear.transition()
.delay(0)
.duration(2000)
.style("opacity", 1);
grow.transition()
.delay(0)
.duration(2000)
.style("opacity", 1)
.attr("r", d / 10);
grow = null;
disappear = null;
} else {
var g = d3.selectAll("circle");
disappear = g.filter(function (v, j, a) {
return i !== j;
});
grow = g.filter(function (v, j, a) {
return i === j;
});
disappear.transition()
.delay(0)
.duration(2000)
.style("opacity", 0);
grow.transition()
.delay(0)
.duration(2000)
.attr("r", d)
.style("opacity", 0);
}
});
As you explained in the comments in the other answer, you wanted to select a circle and have that circle grow AND disappear. The other two circles will fade away. You also wanted to remember which was selected and which were not.
The Fiddle demo enables you to click on a circle, it will grow AND disappear, the others will fade. Click on it again and it will return to normal size, while the others will reappear.

Categories