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.
Related
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
Here is the sample fiddle
Below code is to create bar
svg.selectAll("rect")
.data(dataset, key)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d.value);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return yScale(d.value);
})
.attr("fill", function(d) {
return "blue";
})
//Tooltip
.on("mouseover", function(d) {
d3.select(this).style("fill","red");
})
.on("mouseout", function() {
d3.select(this).style("fill","blue");
}) ;
On mouseover bar gets red color, and on mouseout it gets back to blue color,
I want it to continuously get red color one by one means first bar red then second bar, then third, after moving ahead previous bar should restore its origin color, there will be only one red bar at a time. and it should be like when it reach to end, it should again start from beginning
Here is the result: http://jsfiddle.net/DavidGuan/f07wozud/4/
Code I added:
function reRenderColor() {
svg.selectAll("rect")
.transition()
.delay(function(d, i){ return i* 500 })
.duration(200)
.style('fill', 'red')
.transition()
.delay(function(d, i){ return i* 500 + 400 })
.duration(200)
.style('fill', 'blue')
}
reRenderColor();
setInterval(reRenderColor, svg.selectAll("rect").size() * 500 + 500)
I gave .attr("id", function(d,i){return "rect"+i;}); to your rect elements in order to select them. Then, I used a recursive setTimout function to solve this with d3 transition property.
var z = 0;
var timeoutFunc = function(){
setTimeout(function(){
if(z < 20){
d3.select("#rect"+ z).transition().duration(350).attr("fill","red")
.transition().delay(550).attr("fill","blue");
z++;
timeoutFunc();
}else if(z == 20){
z = 0;
timeoutFunc();
}
},500);
};
Here's an updated fiddle.
Note that durations could be changed for a better color visualization but this will give you an idea.
http://jsfiddle.net/51Lsj6ym/5/
Hope this is what you want
//Tooltip
.on("mouseover", function(d) {
d3.selectAll("rect").style("fill","blue");
d3.select(this).style("fill","red");
})
.on("mouseout", function() {
}) ;
fiddle
Working through the excellent Interactive Data Visualization for the Web book and have created (a monstrosity of a) script to create an interactive bar chart that:
Adds a new bar to the end when clicking on the svg element
Generates a new set of 50 bars when clicking on the p element
I have added a mouseover event listener to change the color of the bars when hovering over. The problem is that bars added via 1. above are not changing color. As far as I can tell, the bars are getting selected properly, but for whatever reason, the mouseover event is never being fired for these bars:
svg.select(".bars").selectAll("rect")
.on("mouseover", function() {
d3.select(this)
.transition()
.attr("fill", "red");
})
Thanks in advance for your help, it is always greatly appreciated.
Here is the full code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Interactive Data Visualization for the Web Program-Along</title>
<style>
/* axes are made up of path, line, and text elements */
.axis path,
.axis line {
fill: none;
stroke: navy;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
/* color is CSS property, but need SVG property fill to change color */
fill: navy;
}
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<p>Click on this text to update the chart with new data values.</p>
<script type="text/javascript">
var n = 50;
var domain = Math.random() * 1000;
function gen_data(n, domain) {
var d = [];
for (var i = 0; i < n; i++) {
d.push(
{ id: i, val: Math.random() * domain }
);
}
return d;
}
// define key function once for use in .data calls
var key = function(d) {
return d.id;
};
var dataset = gen_data(n, domain);
// define graphic dimensions
var w = 500, h = 250, pad = 30;
// get input domains
var ylim = d3.extent(dataset, function(d) {
return d.val;
});
// define scales
var x_scale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w - pad], 0.15);
var y_scale = d3.scale.linear()
.domain([ylim[0], ylim[1] + pad]) // could have ylim[0] instead
// range must be backward [upper, lower] to accommodate svg y inversion
.range([h, 0]); // tolerance to avoid clipping points
var color_scale = d3.scale.linear()
.domain([ylim[0], ylim[1]])
.range([0, 255]);
// create graphic
var svg = d3.select("body").append("div").append("svg")
.attr("width", w)
.attr("height", h);
svg.append("g")
.attr("class", "bars")
.selectAll(".bars rect")
.data(dataset)
.enter()
.append("rect")
.attr({
x: function(d, i) {
return x_scale(i) + pad;
},
y: function(d) {
return y_scale(d.val);
},
width: x_scale.rangeBand(), // calculates width automatically
height: function(d) { return h - y_scale(d.val); },
opacity: 0.6,
fill: function(d) {
return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
}
});
// add axes
var yAxis = d3.svg.axis()
.scale(y_scale) // must be passed data-to-pixel mapping (scale)
.ticks(3) // optional (d3 can assign ticks automatically)
.orient("left");
// since function, must be called
// create <g> to keep things tidy, to style via CSS, & to adjust placement
svg.append("g")
.attr({
class: "axis",
transform: "translate(" + pad + ",0)"
})
.call(yAxis);
// add event listener for clearing/adding all new values
d3.select("p")
.on("click", function() {
// generate new dataset
dataset = gen_data(n, domain);
// remove extra bars
d3.selectAll(".bars rect")
.data(dataset, function(d, i) { if (i < 50) { return d; }})
.exit()
.transition()
.attr("opacity", 0)
.remove();
// update scales
x_scale.domain(d3.range(dataset.length))
.rangeRoundBands([0, w - pad], 0.15);
ylim = d3.extent(dataset, function(d) {
return d.val;
});
y_scale.domain([ylim[0], ylim[1] + pad]);
// update bar values & colors
d3.selectAll(".bars rect")
.data(dataset)
.transition()
.duration(500)
.attr("x", function(d, i) { return x_scale(i) + pad; })
.transition() // yes, it's really this easy...feels like cheating
.delay(function(d, i) { return i * (1000 / dataset.length); }) // set dynamically
.duration(1000) // optional: control transition duration in ms
.each("start", function() {
// "start" results in immediate effect (no nesting transitions)
d3.select(this) // this to select each element (ie, rect)
.attr("fill", "magenta")
.attr("opacity", 0.2);
})
.attr({
y: function(d) { return y_scale(d.val); },
height: function(d) { return h - y_scale(d.val); }
})
.each("end", function() {
d3.selectAll(".bars rect")
.transition()
// needs delay or may interrupt previous transition
.delay(700)
.attr("fill", function(d) {
return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
})
.attr("opacity", 0.6)
.transition()
.duration(100)
.attr("fill", "red")
.transition()
.duration(100)
.attr("fill", function(d) {
return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
});
});
// update axis (no need to update axis-generator function)
svg.select(".axis")
.transition()
.duration(1000)
.call(yAxis);
});
// extend dataset by 1 for each click on svg
svg.on("click", function() {
// extend dataset & update x scale
dataset.push({ id: dataset.length, val: Math.random() * domain });
x_scale.domain(d3.range(dataset.length));
// add this datum to the bars <g> tag as a rect
var bars = svg.select(".bars")
.selectAll("rect")
.data(dataset, key);
bars.enter() // adds new data point(s)
.append("rect")
.attr({
x: w,
y: function(d) {
return y_scale(d.val);
},
width: x_scale.rangeBand(), // calculates width automatically
height: function(d) { return h - y_scale(d.val); },
opacity: 0.6,
fill: function(d) {
return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
}
});
// how does this move all the other bars!?
// because the entire dataset is mapped to bars
bars.transition()
.duration(500)
.attr("x", function(d, i) {
return x_scale(i) + pad;
});
});
// add mouseover color change transition using d3 (vs CSS)
svg.select(".bars").selectAll("rect")
.on("mouseover", function() {
d3.select(this)
.transition()
.attr("fill", "red");
})
.on("mouseout", function(d) {
d3.select(this)
.transition()
.attr("fill", function() {
return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
})
.attr("opacity", 0.6);
})
// print to console when clicking on bar = good for debugging
.on("click", function(d) { console.log(d); });
</script>
</body>
</html>
UPDATE:
Thanks to Miroslav's suggestion, I started playing around with different ways to resolve the issue and came across Makyen's answer to this related SO post.
While I imagine there is a more performant way to handle this, I have decided to rebind the mouseover event listener each time the mouse enters the svg element using the following code:
svg.on("mouseover", mouse_over_highlight);
// add mouseover color change transition using d3 (vs CSS)
function mouse_over_highlight() {
d3.selectAll("rect")
.on("mouseover", function () {
d3.select(this)
.transition()
.attr("fill", "red");
})
.on("mouseout", function (d) {
d3.select(this)
.transition()
.attr("fill", function () {
return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
})
.attr("opacity", 0.6);
})
// print to console when clicking on bar = good for debugging
.on("click", function (d) {
console.log(d);
});
}
Reason your event fires only for first bar and not the dynamic ones is because of the way you added your event listener.
Your way only puts events on elements already present on the page (they are in DOM structure). Any new elements will not have this event listener tied to them.
You can make a quick check for this by putting your code in function like
function setListeners() {
svg.select(".bars").selectAll("rect").on("mouseover", function() {
d3.select(this)
.transition()
.attr("fill", "red");
})
}
After you add any new bars on the screen, add this function call and see if it works for all elements. If this is indeed the case, you need to write your event listener in a way it works for all elements, including dynamically added ones. The way to do that is to set the event to some of the parent DOM nodes and then checking if you are hovering on exactly the thing you want the event to fire.
Example:
$(document).on(EVENT, SELECTOR, function(){
code;
});
This will put the event on the body and you can check then selector after it's triggered if you are over correct element. However it was a while since I worked with D3 and I'm not sure how D3, SVG and jQuery play together, last time I was doing it they had some troubles.
In this case event should be mouseover, selector should be your rect bars and function should be what ever you want to run.
If everything else fails in case they won't cooperate, just use the function to set event listeners and call it every time after you dynamically add new elements.
I'm drawing a little clickable graph data browser.
Example:
First, I load a few movies, and I see this:
Then, after I click on one of the nodes (Hellraiser, in this case), I use ajax to load additional related information properties and values, and end up with this:
The lines and circles of the newly added nodes are obviously drawn after the originally clicked node was.
Here is the draw method that gets called every time new data is ready to be added to the graph:
function draw() {
force.start();
//Create edges as lines
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 2)
.on("mouseover", lineMouseover)
.on("mouseout", lineMouseout);
//create the nodes
var node = svg.selectAll(".node")
.data(dataset.nodes)
.enter()
.append("g")
.attr("class", "node")
.on("click", callback)
.attr("r", function(d, i) { //custom sizes based on datatype
if(d.datatype && (d.datatype in _design) ) {
return _design[d.datatype].size;
} else {
return _design["other"].size;
}
})
.call(force.drag);
//create fancy outlines on the nodes
node.append("circle")
.attr("r", function(d,i) { //custom sizes based on datatype
if(d.datatype && (d.datatype in _design) ) {
return _design[d.datatype].size * r;
} else {
return _design["other"].size * r;
}
})
.style("stroke", "black")
.style("stroke-width", 3)
.style("fill", function(d, i) { //custom color based on datatype
if(d.datatype && (d.datatype in _design) ) {
return _design[d.datatype].color;
} else {
return _design["other"].color;
}
})
.attr("class","circle");
//Add text to each node.
node.append("text")
.attr("dx", 0)
.attr("dy", ".25em")
//.attr("class", "outline")
.attr("fill", "black")
.text(function(d, i) {
return d.name;//d.name
});
};
How do I go about drawing those lines underneath the clicked node?
You can group the different kinds of elements below g elements that you can create at the beginning in the required order. This way, anything you append to them later will be ordered correctly:
var links = svg.append("g"),
nodes = svg.append("g"),
labels = svg.append("g");
// ...
var edges = links.selectAll("line")
.data(dataset.edges)
.enter()
.append("line");
var node = nodes.selectAll(".node")
.data(dataset.nodes)
.enter()
.append("g")
// etc.
I am trying to make tooltip like: http://jsfiddle.net/6cJ5c/10/ for my graph and that is the result on my realtime graph: http://jsfiddle.net/QBDGB/52/ I am wondering why there is a gap between the circles and the graph and why at the beginning there is a vertical line of circles? When it starts the circles are close to the curve but suddendly they start to jump up and down !! I want the circles to move smooothly and stick on the surface of the curve. I think the problem is that they are not moving with the "path1" and so it does not recognize the circles and thats why they are moving separetly or maybe the value of tooltipis are different of the value of the curve so they do not overlap!. That is how the data is generated ( value and time) and the tooltip:
var data1 = initialise();
var data1s = data1;
function initialise() {
var arr = [];
for (var i = 0; i < n; i++) {
var obj = {
time: Date.now(),
value: Math.floor(Math.random() * 90)
};
arr.push(obj);
}
return arr;
}
// push a new element on to the given array
function updateData(a) {
var obj = {
time: Date.now(),
value: Math.floor(Math.random() * 90)
};
a.push(obj);
}
var formatTime = d3.time.format("%H:%M:%S");
//tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var blueCircles = svg.selectAll("dot")
.data(data1s)
.enter().append("circle")
.attr("r", 3)
.attr("cx", function(d) { return x(d.time); })
.attr("cy", function(d) { return y(d.value); })
.style("fill", "white")
.style("stroke", "red")
.style("stroke-width", "2px")
.on("mousemove", function(d ,i) {
div.transition()
.duration(650)
.style("opacity", .9);
div.html(formatTime(new Date(d.time)) + "<br/>" + d.value)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d ,i ) {
div.transition()
.duration(650)
.style("opacity", 0);
});
blueCircles.data(data1s)
.transition()
.duration(650)
.attr("cx", function(d) { return x(d.time); })
.attr("cy", function(d) { return y(d.value); });
Please kindly tell me your opinions since I really need it :(
As I said maybe I should add "mouseover and mouse move functions" to the "path" to make it recognize the tooltip. something like following. but I am nor really sure :(
var path1 = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.data([data1])
.attr("class", "line1")
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseout", mouseout);
I think your problem lies in the interpolation of your paths. You set the interpolation between points on your var area to "basis", which I found is a B-spline interpolation. This means the area drawn does not go through the points in your dataset, as shown in this example:
The path your points move over, though, are just straight lines between the points in your dataset. I updated and changed the interpolation from basic to linear, to demonstrate that it will work that way. I also set the ease() for the movement to linear, which makes it less 'jumpy'. http://jsfiddle.net/QBDGB/53/