I have a simple D3 scatterplot that I switch among displaying several different attributes of my data, but while I can get the data points to change (and to transition as I want them to), and can change the labels to the figure's axes, I cannot get the axes themselves to update (let alone transition).
I suspect I'm doing something in the wrong order, or am missing a step, but I can't figure out from the documentation or examples I'm working from what I'm missing.
How do I get my axes to update along with my data?
The mystery arises from the behavior at the end of the linked code:
d3.select("#distancefig").on("click", function () {
d3.event.preventDefault();
updatePlot('distancefig', false);
});
d3.select("#speedfig").on("click", function () {
d3.event.preventDefault();
updatePlot('speedfig', false);
});
d3.select("#distspeedfig").on("click", function () {
d3.event.preventDefault();
updatePlot('distspeedfig', false);
});
updatePlot('distancefig', true);
Here the final, explicit updatePlot updates everything as expected (and changing the argument changes everything — axes, labels, points — as it should), but the calls invoked by clicking on the links change only the data points and labels; they do not update the axes.
I'm not familiar with how you structured your code, but I would basically put everything that happens with the database inside the d3.csv callback function, so the final part, regarding the functionality of the text, would have the update of the x and y axis with the updated domain, like:
d3.csv{
//select the text then add the onclick event
.on("click" function () {
x.domain(d3.extent(dataset, function (d) { return /* your updated value here */); })).nice();
//select the x-axis and then add this:
.transition()
.duration(1500)
.call(xAxis);
//then do the same for the y axis
};}
The critical step is to make sure that you select the axes correctly.
In each of the click handlers you are passing "false" as the 2nd argument. In the last statement, you are passing "true". Could this be the cause?
Related
I'm creating a similar graph to this:
https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172
However when I'm zooming in to a certain section, I would like to scale the Y axis to the local (displayed) maximum, instead of the global one. For example when I zoom to the data between 2009 and 2010 there is a lot of empty white space at the top.
Basically what I would like to achieve is get the range to which I've zoomed, and get the maximum value within that.
The other possibility would be adding another brush bar on the side, but that would be very inconvenient on the long run for the users.
You just need to change the y scale domain for that.
First, let's create a global variable named globalData and associate the data array to it. Note: this is not the correct way to do this, but I'll do it simply because the brushed and zoomed functions lie outside d3.csv, which is asynchronous, and refactoring it takes some work... so, it will be your job refactoring it.
Then, in both the brushed and zoomed functions, we filter the data according to the brush:
var filteredData = globalData.filter(function(d){
return d.date > x.domain()[0] && d.date < x.domain()[1]
});
After that, we calculate the new y domain:
y.domain([0, d3.max(filteredData, function(d){
return d.price
})]);
Don't forget to call the axis again.
This is the updated bl.ocks: https://bl.ocks.org/GerardoFurtado/17fd6b82324e355c768992e78140fe9a/33b9a6c58265454864a9d921df032e708fad5237
I'm creating a simple bar chart and trying to make it respond to user clicks. The clicked bar is supposed to disappear. All seems to be working except clicking on the first bar makes a bar at the end disappear. I'm completely at a loss to why this is the case and would really appreciate any help.
Complete Code on Plunkr:
https://plnkr.co/edit/H8K0ISdhGrb5HirrX2MG?p=preview
I call the update function when a user clicks on a bar. I created a removefromarray function to return the data object minus data bound to the clicked bar. :
d3.tsv("CantTouchThis.tsv",function(d,i){
d.FieldGoals = +d.FieldGoals;
return d;
}, function(error,data){
if (error) throw error;
y.domain([0,d3.max(data, function(d){return d.FieldGoals})]);
x.domain(data.map(function(d){return d.Player}));data used as the x attribute
function update(indx){
var selection = g.selectAll(".bars")
.data(data.removefromarray(indx), function(d){console.log('d');console.log(d); return d}) //printing d shows the previous bars and new bars are being returned, I suspect this may be causing the problem, but not sure
selection.enter().append("rect")
.attr("class","bars")
.attr("width",function(d){return x.bandwidth()})
.attr("x",function(d){return x(d.Player)})
.attr("height",function(d){return height - y(d.FieldGoals)})
.attr("y",function(d){return y(d.FieldGoals)})
.on("click",function(d,i){update(i);});
console.log(selection.enter())
console.log(selection.exit())
selection.exit().remove()
}
Solution 1: You're missing an "update" selection:
selection.attr("width",function(d){return x.bandwidth()})
.attr("x",function(d){return x(d.Player)})
.attr("height",function(d){return height - y(d.FieldGoals)})
.attr("y",function(d){return y(d.FieldGoals)})
.on("click",function(d,i){update(i);});
Here is your updated plunker: https://plnkr.co/edit/qJY5KgY9FBvuJ8krddYm?p=preview
Solution 2: another very simple solution (that correctly addresses your question, "why doesn't first bar disappear using exit method?"): use a proper key in the data binding selection:
var selection = g.selectAll(".bars")
.data(data.removefromarray(indx), function(d){ return d.Player});
// this is the proper key function ---^
However, have in mind that this "solution" will not work for all clicks. That happens because your method for removing the data object (using splice in an extended prototype) is not working correctly.
Here is another updated plunker: https://plnkr.co/edit/rVTinxx45nmBvFiWwqgg?p=preview
PS: there are way easier ways to do what you want (and more adequate to a D3 code also).
I am working on a sunburst viz based off of Mike Bostock's Zoomable Sunburst example.
I want to be able to change the underlying data using a whole new JSON (which has the same structure but different 'size' values), and have the sunburst animate a transition to reflect the updated data.
If I change the data of the path elements using .data(), and then attempt to update in the following fashion:
path.data(partition.nodes(transformed_json))
.transition()
.duration(750)
.attrTween("d", arcTween(transformed_json));
(..which is pretty much the exact same code as the click fn)
function click(d) {
path.transition()
.duration(750)
.attrTween("d", arcTween(d));
}
..I find that the sunburst does correctly change to reflect the new data, but it snaps into place rather than smoothly transitioning, like it does when you zoom in.
http://jsfiddle.net/jTV2y/ <-- Here is a jsfiddle with the issue isolated (the transition happens one second after you click 'Run')
I'm guessing that I need to create a different arcTween() fn, but my d3 understanding is not there yet. Many thanks!
Your example is quite similar to the sunburst partition example, which also updates data with a transition. The difference is that in this example it's the same underlying data with different value accessors. This means that you can't save the previous value in the data (as that will be different), but need to put it somewhere else (e.g. the DOM element).
The updated tween function looks like this:
function arcTweenUpdate(a) {
var i = d3.interpolate({x: this.x0, dx: this.dx0}, a);
return function(t) {
var b = i(t);
this.x0 = b.x;
this.dx0 = b.dx;
return arc(b);
};
}
This requires, as in the original example, to save the original x and dx values:
.enter().append("path")
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
Complete example here. This one has a kind of weird transition which is cause by the different order of the data in the layout. You can disable that by calling .sort(null), see here.
I'm using Highcharts to represent groups of time series. So, data points collected from the same individual are connected by lines, and data points from individuals that belong to the same group share the same color. The Highcharts legend displays each individual time series instead of groups, and I have over a hundred time series, to it's ugly and impractical to hide and show data that way.
Instead I made buttons and used jQuery to associate them with functions that would search for matching colors among the time series and toggle the visibility of each matching series.
Here is an example with a small dataset: http://jsfiddle.net/bokov/VYkmg/6/
Here is the series-hiding function from that example:
$("#button").click(function() {
if ($(this).hasClass("hideseries")) {
hs = true;
} else {
hs = false;
}
$(chart.series).each(function(idx, item) {
if (item.color == 'green') {
if (hs) {
item.show();
} else {
item.hide();
}
}
});
$(this).toggleClass("hideseries");
});
The above works. The problem is, my real data can have over a hundred individual time series and it looks like checking the color of each series is really slow. So, can anybody suggest a more efficient way to solve this problem? Are there some built-in Highcharts methods that already do this? Or, can I give jQuery a more specific selector?
I tried digging into the <svg> element created by Highcharts but I can't figure out which child elements correspond to the series in the chart.
Thanks.
The issue here is that Highcharts is redrawing the chart after every series change. I checked the API to see if there was a param you could pass to defer that, but that doesn't appear to be the case.
Instead, you can stub out the redraw method until you are ready, like so:
var _redraw = chart.redraw;
chart.redraw = function(){};
//do work
chart.redraw = _redraw;
chart.redraw();
Check out the full example here. For me, it was about 10 times faster to do it this way.
Rather than calling show() or hide() for each series, call setVisible(/* TRUE OR FALSE HERE */, false);. This second parameter is the redraw parameter, and you can avoid causing a redraw (which is slow) for each series.
Then, after you're done changing visibilities, call chart.redraw() once.
http://api.highcharts.com/highcharts#Series.setVisible
I'm using Highcharts to represent groups of time series. So, data points collected from the same individual are connected by lines, and data points from individuals that belong to the same group share the same color. The Highcharts legend displays each individual time series instead of groups, and I have over a hundred time series, to it's ugly and impractical to hide and show data that way.
Instead I made buttons and used jQuery to associate them with functions that would search for matching colors among the time series and toggle the visibility of each matching series.
Here is an example with a small dataset: http://jsfiddle.net/bokov/VYkmg/6/
Here is the series-hiding function from that example:
$("#button").click(function() {
if ($(this).hasClass("hideseries")) {
hs = true;
} else {
hs = false;
}
$(chart.series).each(function(idx, item) {
if (item.color == 'green') {
if (hs) {
item.show();
} else {
item.hide();
}
}
});
$(this).toggleClass("hideseries");
});
The above works. The problem is, my real data can have over a hundred individual time series and it looks like checking the color of each series is really slow. So, can anybody suggest a more efficient way to solve this problem? Are there some built-in Highcharts methods that already do this? Or, can I give jQuery a more specific selector?
I tried digging into the <svg> element created by Highcharts but I can't figure out which child elements correspond to the series in the chart.
Thanks.
The issue here is that Highcharts is redrawing the chart after every series change. I checked the API to see if there was a param you could pass to defer that, but that doesn't appear to be the case.
Instead, you can stub out the redraw method until you are ready, like so:
var _redraw = chart.redraw;
chart.redraw = function(){};
//do work
chart.redraw = _redraw;
chart.redraw();
Check out the full example here. For me, it was about 10 times faster to do it this way.
Rather than calling show() or hide() for each series, call setVisible(/* TRUE OR FALSE HERE */, false);. This second parameter is the redraw parameter, and you can avoid causing a redraw (which is slow) for each series.
Then, after you're done changing visibilities, call chart.redraw() once.
http://api.highcharts.com/highcharts#Series.setVisible