I'm busy plotting a bar chart using D3 in Angular4.
// Enter Phase
this.chart.selectAll('.bar')
.data(this.barData)
.enter()
.append('rect')
.attr('class', 'bar');
// Update Phase
let bars = this.chart.selectAll('.bar').transition()
.attr('x', (d) => {return this.x(this.parseTime(d.date.toUpperCase()));})
.attr('y', (d) => {return this.y(d.point)})
.attr('width', 15)
.attr('height', (d) => {return this.charDimensions.height - this.y(d.point);})
.on("mouseover", function (d) {D3.select(this).style('opacity', 0.5);})
.on("mouseout", function (d) {D3.select(this).style('opacity', 1);})
.on("click", (d) => {this.barClicked.emit(d);});
// Exit phase
this.chart.selectAll('.bar')
.data(this.barData)
.exit().remove();
In my plotting method, when I call animate() I get an error: Error: unknown type: mouseover. Which makes sense, since I'm probably trying to call the on("<event>") on a the transition returned by D3, the transition effect is there, however, everything after the transition breaks, i.e: The animation works, but the plotting is broken ofc.
However when I attempt to do the following:
// Update Phase
let bars = this.chart.selectAll('.bar');
bars.transition();
bars.attr('x', (d) => {return this.x(this.parseTime(d.date.toUpperCase()));})
.attr('y', (d) => {return this.y(d.point)})
.attr('width', 15)
.attr('height', (d) => {return this.charDimensions.height - this.y(d.point);})
.on("mouseover", function (d) {D3.select(this).style('opacity', 0.5);})
.on("mouseout", function (d) {D3.select(this).style('opacity', 1);})
.on("click", (d) => {this.barClicked.emit(d);});
No errors occur, but nothing happens, there is no transition effect to the new data set.
The problem here is a confusion between two different methods that use the same name:
selection.on(typenames[, listener])
transition.on(typenames[, listener])
Since you have a transition selection, when you write...
.on(...
The method expects to see three things (or typenames):
"start"
"end"
"interrupt"
However, "mouseover", "mouseout" or "click" are none of them. And then you get your error...
> Uncaught Error: unknown type: mouseover
Solution:
Bind the event listeners to a regular selection. Then, after that, create your transition selection.
Therefore, in your case, bind all the on listeners to the "enter" selection (which, by the way, makes more sense), removing them from the update selection.
Have a look at this little demo. First, I create a regular, enter selection, to which I add the event listener:
var bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
//some attr here
.on("mouseover", function(d) {
console.log(d)
});
Then, I add the transition:
bars.transition()
.duration(1000)
etc...
Hover over the bars to see the "mouseover" working:
var svg = d3.select("svg");
var data = [30, 280, 40, 140, 210, 110];
var bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("x", 0)
.attr("width", 0)
.attr("y", function(d, i) {
return i * 20
})
.attr("height", 18)
.attr("fill", "teal")
.on("mouseover", function(d) {
console.log(d)
});
bars.transition()
.duration(1000)
.attr("width", function(d) {
return d
})
.as-console-wrapper { max-height: 25% !important;}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Related
I am trying to replicate this example of a multiline chart with dots. My data is basically the same, where I have an object with name and values in the first level, and then a couple of values in the second level inside values. For the most part, my code works, but for some reason, the j index in the anonymous function for the fill returns an array of repeated circle instead of returning the parent of the current element. I believe this may have something to do with the way I created the svg and selected the elements, but I can't figure it out. Below is an excerpt of my code that shows how I created the svg, the line path and the circles.
var svgb = d3.select("body")
.append("svg")
.attr("id","svg-b")
.attr("width", width)
.attr("height", height)
var gameb = svgb.selectAll(".gameb")
.data(games)
.enter()
.append("g")
.attr("class", "gameb");
gameb.append("path")
.attr("class", "line")
.attr("d", function(d) {return line_count(d.values); })
.style("stroke", function(d) { return color(d.name); })
.style("fill", "none");
gameb.selectAll("circle")
.data(function(d) {return d.values;})
.enter()
.append("circle")
.attr("cx", function(d) {return x(d.date);})
.attr("cy", function(d) {return y_count(d.count);})
.attr("r", 3)
.style("fill", function(d,i,j) {console.log(j)
return color(games[j].name);});
j (or more accurately, the third parameter) will always be the nodes in the selection (the array of circles here), not the parent. If you want the parent datum you can use:
.attr("fill", function() {
let parent = this.parentNode;
let datum = d3.select(parent).datum();
return color(datum.name);
})
Note that using ()=> instead of function() will change the this context and the above will not work.
However, rather than coloring each circle independently, you could use a or the parent g to color the circles too:
gameb.append("g")
.style("fill", function(d) { return color(d.name); })
.selectAll("circle")
.data(function(d) {return d.values;})
.enter()
.append("circle")
.attr("cx", function(d) {return x(d.date);})
.attr("cy", function(d) {return y_count(d.count);})
.attr("r", 3);
Here we add an intermediate g (though we could use the original parent with a few additional modifications), apply a fill color to it, and then the parent g will color the children circles for us. The datum is passed on to this new g behind the scenes.
I have a D3 barchart which has 5 bars. When I update it I can see it transitioning to the correct 3 bars but some of the original bars are left visible - how do I make them exit?
This is what it initially looks like:
This is what it ends up looking like:
The dark blue bars are correct. The current code for updating the "rect" objects is the following:
var plot = d3.select("#barChartPlot")
.datum(currentDatasetBarChart);
/* Note that here we only have to select the elements - no more appending! */
plot.selectAll("rect")
.data(currentDatasetBarChart)
.transition()
.duration(750)
.attr("x", function (d, i) {
return xScale(i);
})
.attr("width", width / currentDatasetBarChart.length - barPadding)
.attr("y", function (d) {
return yScale(+d.measure);
})
.attr("height", function (d) {
return height - yScale(+d.measure);
})
.attr("fill", colorChosen);
You only have 3 new bars, so the number of elements on your data has changed.
You need to use the update pattern.
var rects = plot.selectAll("rect")
.data(currentDatasetBarChart);
rects.enter()
.append("rect")
//Code to style and define new rectangles.
//Update
rects.update()
.transition()
.duration(750)
.attr("x", function (d, i) {
return xScale(i);
})
.attr("width", width / currentDatasetBarChart.length - barPadding)
.attr("y", function (d) {
return yScale(+d.measure);
})
.attr("height", function (d) {
return height - yScale(+d.measure);
})
.attr("fill", colorChosen);
// Remove unused rects
rects.exit().remove();
I found no direct answer for this, please forgive me if this has been covered differently in another topic.
I draw a bar chart which appears with a transition. I also want to add a tooltip which displays the value of data on mousehover.
Using the code below I have managed to obtain either the tooltip or the transition, but never the 2 together, which is my objective.
chart.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("fill", function(d) {return colorscale(colorize(d.age));})
.attr("x", function(d) {return xscale(d.name);})
.attr("y", height - 3)
.attr("height", 3)
.attr("width", xscale.rangeBand())
.append("title")
.text(function(d){return d.age;})
.transition()
.duration(1600)
.attr("y", function (d) {return yscale(d.age);})
.attr("height", function (d) {return height - yscale(d.age);}) ;
If I remove
.append("title")
.text(function(d){return d.age;})
Then my transition works fine. If I but those 2 lines back I can see my tooltip but I lose my transition.
Any suggestion would be appreciated!
You can see the result here
Thank you
You need to add the transition to the rect and not the title element:
var sel = chart.selectAll(".bar")
.data(data)
.enter()
.append("rect");
sel.append("title")
.text(function(d){return d.age;});
sel.transition()
.duration(1600)
.attr("y", function (d) {return yscale(d.age);})
.attr("height", function (d) {return height - yscale(d.age);}) ;
This is a very basic question, but how do I access the value of attributes in d3?
I just started learning today, so I haven't figured this out yet
Suppose I have this as part of my code here
http://jsfiddle.net/matthewpiatetsky/nCNyE/9/
var node = svg.selectAll("circle.node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function (d) {
if (width < height){
return d.count * width/100;
} else {
return d.count * height/100;
}
})
.on("mouseover", animateFirstStep)
.on("mouseout",animateSecondStep)
.style("fill", function(d,i){return color(i);})
.call(force.drag);
For my animation the circle gets bigger when you mouse over it, and I want the circle to return to its normal size when you move the mouse away. However, i'm not sure how to get the value of the radius.
i set the value here
.attr("r", function (d) {
if (width < height){
return d.count * width/100;
} else {
return d.count * height/100;
}
I tried to do node.r and things like that, but i'm not sure what the correct syntax is
Thanks!
You can access an attribute of a selection with:
var node = svg.selectAll("circle.node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function (d) { return rScale(d.count); })
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(1000)
.attr('r', 1.8 * rScale(d.count));
})
.on("mouseout", function(d) {
d3.select(this)
.transition()
.duration(1000)
.attr('r', rScale(d.count));
})
.style("fill", function (d, i) {
return color(i);
})
.call(force.drag);
in this context, this points to the DOM element binded with d. Normally, the area of a circle must be proportional to the quantities that you are showing, take a look at the documentation of Quantitative Scales. A fork of your fiddle is here.
I've brought some of my code to match D3 standards. But now I'm having issues with my update function working. I'm trying to follow the General Update Pattern.
When I run the function, I get an error in the console: "TypeError: gbars.enter(...).attr is not a function"
function updateBars()
{
xScale.domain(d3.range(dataset.length)).rangeRoundBands([0, w], 0.05);
yScale.domain([0, d3.max(dataset, function(d) { return (d.local > d.global) ? d.local : d.global;})]);
var gbars = svg.selectAll("rect.global")
.data(dataset);
gbars.enter()
.attr("x", w)
.attr("y", function(d) {return h - yScale(d.global); })
.attr("width", xScale.rangeBand())
.attr("height", function(d) {return yScale(d.global);});
gbars.transition()
.duration(500)
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d.global);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return yScale(d.global);
});
gbars.exit()
.transition()
.duration(500)
.attr("x", -xScale.rangeBand())
.remove();
}
I understand, I probably have a lot of errors with the update function in general, but it's hard to troubleshoot when hung-up at the beginning.
My goal for the update will be to remove/add series from the chart. They can choose to display Global, Local, or both (default). I'd actually prefer that when they hover over the legend it shows only that series. On mouseout, it would go back to default.
Working Fiddle here.
You need to say what you want to do for new nodes. Yes it's almost always append but you still have to tell it:
gbars.enter()
.append("rect")
.attr("class", "global")
.attr("x", w)
Other than that error, your structure for general update pattern looks correct on the surface.