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.
Related
I have been trying to append a set of SVGs inside SVG. So, in inside SVG, I want to create a function to make plot. However, this doesn't work in the way I expected as the inside SVGs don't have the dimension according to my specification. So, I'd like to know what went wrong.
svg_g_g = svg_g.append("g")
.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + xScale(d.key) + "," + lift + ")"
})
.append("svg")
.attr("width", function(d) { return xScale(d.key) })
.attr("height", height)
.append("line")
.style("stroke", "black")
.attr("x1", function(d) { return xScale(d.key) })
.attr("y1", height-100)
.attr("x2", function(d) { return xScale(d.key) + 50 } )
.attr("y2", height-100);
This is the output.
While if I bind data to g (without appending svg), the result looks more promising.
svg_g_g = svg_g.append("g");
svg_g_g.selectAll("line")
.data(data)
.enter()
.append("line")
.style("stroke", "black")
.attr("x1", function(d) { return xScale(d.key) })
.attr("y1", height-100)
.attr("x2", function(d) { return xScale(d.key) + 50 } )
.attr("y2", height-100);
and this is the result. (notice: the lines were shown in this case)
Any help will be appreciated.
edit: what I intended to do was I wanted to create a box-plot, I used "iris" dataset here as I can compared it against other data visualisation libraries. The current state I tried to create a SVG with an equal size for each category which I would use to contain box-plot. In other words, after I can create internal SVGs successfully, I will call a function to make the plot from there. Kinda same idea Mike did here.
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>
I am trying to create a very basic Object Oriented structure for creating D3 charts. Here is the code.
Without classes and function, the same code worked fine and built the chart. However, when I organize it in the above way, the values this.width and this.height get converted into SVGAnimatedLength, so I am unable to perform normal operations on them(such as this.height - yScale(x)).
I noticed that, this.width and this.height remain numbers just before the code which adds columns is encountered.
this.chart.selectAll(".bar") ...
To reproduce, if you put a breakpoint and debug the above variables, they show up as numbers until the above code(line 38) is executed. Then suddenly, they turn into SVGAnimatedLength.
Any help is appreciated. Thanks.
The value of this has changed within the function.
so instead of doing in line 38:
this.chart.selectAll(".bar")
.data(this.data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return xScale(d.letter); })
.attr("width", xScale.rangeBand())
.attr("y", function(d) { return yScale(d.frequency); })
.attr("height", function(d) {
return this.height - yScale(d.frequency);//value of this is diffrent.
});
Do like this:
var me = this;//store the value of this in me.
this.chart.selectAll(".bar")
.data(this.data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return xScale(d.letter); })
.attr("width", xScale.rangeBand())
.attr("y", function(d) { return yScale(d.frequency); })
.attr("height", function(d) {
return me.height - yScale(d.frequency);//use me variable
});
}
}
working code here
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);}) ;
Here's the code I am running http://jsfiddle.net/a7as6/14/
I know that I can use this code to change node to image:
node.append("svg:image")
.attr("class", "circle")
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("x", "-8px")
.attr("y", "-8px")
.attr("width", "16px")
.attr("height", "16px");
But when I use it and my nodes are still not images. Any idea why?
And I am wondering how to change each nodes with different images?
Thx.
You've got the right idea for appending the images, but you need to operate on node.enter() as in:
node.enter().append("image")
.attr("class", function (d) {
return "node " + d.id;
})
.attr("xlink:href", "https://github.com/favicon.ico")
.attr("width", "16px")
.attr("height", "16px");
You then need to get your tick function to place the images, as in:
function tick() {
node.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y;
})
And here's the working fiddle. Not that you'll need to move the images around so they look right, you can use the dx and dy properties to bump them.