I have a histogram created from datasource.
this.histogramDataSource = d3.layout.histogram()
.value (function(d) { return d.score; })
.bins(binThresholds)
(self.datasource);
That I render thusly
histogramRects = histogramGroup.selectAll('.svg_container rect')
.data(histogram.histogramDataSource)
.enter().append("rect")
.attr({
x: function(d) { return histogram.x(d); },
width: function(d) { return histogram.width(d); },
y: function(d) { return histogram.y(d); },
height: function(d) { return histogram.height(d); }
})
.attr("fill", function(d) { return scarpa.ConnectivityScoreHistogram.fillColor(); });
Based on user input I want to filter the input datasource, recalculate the histogram and re-render. I thought I could simply do something like this:
this.histogramDataSource(filtered_data_source);
But that generates errors. What have I missed here?
I think you need to keep a reference to the original histogram builder:
this.histogramLayout = d3.layout.histogram()
.value (function(d) { return d.score; })
.bins(binThresholds);
this.histogramDataSource = this.histogramLayout(self.datasource);
Then
this.histogramDataSource = this.histogramLayout(filtered_data_source);
However, keep in mind that this is recalculating the entire histogram from scratch so it might get a bit slow with somewhat larger data sets. If you really want interactive filtering you might want to look at Crossfilter.
Related
This is ugly:
I need something that is not ugly. Time series are very usual, but I not see how to build a "plug and play" chart with ISO dates.
Perhaps an equivalent question is "How to use d3-scalelinear/Non-numeric range/Date with NVD3?" (or d3-scaletime)
Notes
This is the main code:
nv.addGraph(function () {
var chart = nv.models.discreteBarChart()
//.x( d=> d.label ) // very Ugly!
.x( d=> {
let dd = new Date(d.label);
return d3.time.format('%b %d')(dd)
}) // Ugly, because need to jump some days before show next
.y( d=> d.value )
.staggerLabels(true) // not solved
d3.select('#chart svg')
.datum(data)
.transition().duration(500)
.call(chart);
nv.utils.windowResize(chart.update); // necessary?
return chart;
}); // \nv.add
I try some variations, no one work fine (all ugly).
My data is something as
[ {
key: "ExampleData",
color: "#ff7f0e",
values: [
{ label: "2019-03-28", value: 7.8389242307 },
{ label: "2019-03-29", value: 9.4185632435 },
{ label: "2019-03-30", value: 7.3553138346 },
{ label: "...", value: ... }
]
} ];
The values Array have ~100 items.
Problematic workarounds
This (not elegant) solution loss the tooltip, as illustrated,
var chart = nv.models.discreteBarChart()
.x( d=> d.label )
.y( d=> d.value )
.staggerLabels(true);
chart.xAxis
.showMaxMin(false)
.tickFormat(function(d) {
let dd = new Date(d)
return (dd.getDay()==1)? d3.time.format('%x')(aux): '';
}); // each 7 days format, else empty string
This other solution, http://jsfiddle.net/ee2todev/3Lu46oqg/
seems good and elegant, using d3.scale.ordinal().domain(valuesForXAxis).rangePoints([0,width]), but is not NVD3, is pure D3...
The NVD3 Live Line Plus Bar Chart seems good (!), but the input data is not ISO date, is something very strange (unix time?) that is a ordinal domain and is automatically displayed with non-ugly algorithm.
You can try using historicalBarChart which can have time series on x axis.
var chart = nv.models.historicalBarChart()
.x(function(d) { return d[0]})
.y(function(d) { return d[1]})
.useInteractiveGuideline(true);
...
chart.xAxis
.showMaxMin(false)
.tickFormat(function(d) {
return d3.time.format('%x')(new Date(d))
});
Working example is here.
I have a force simulation graph using d3 v4. Each node is bound to some data, which I use for example to determine the radius of each node.
The underlying bound data is updated periodically, and for some nodes it changes, and for others it stays the same.
I want to be able to select just those DOM elements for which the bound data changes, so that I can highlight these elements on my graph.
For example, suppose that initially my data (which is bound to the forceSimulation nodes) is:
data = [{id: 1, type: 0}, {id: 2, type: 1}]
and it is then updated to:
data = [{id: 1, type: 1}, {id: 2, type: 1}]
I'd like to be able to select just the DOM element that corresponds to id=1 so that I can for example make the colour change temporarily.
The update selection contains both id=1 and id=2 - I could maintain an internal mapping of previous data values and compare, but this seems inefficient.
Thanks,
Adam
If a single datum attribute can be checked to see if the bound data has changed, one method would be to track that attribute as a property using selection.property and a custom property such as type. When appending the data you could define the property fairly easily:
.append("circle")
.property("type",function(d) { return d.type; });
Then, when updating, you could filter based on which data are matching or not matching the property:
circles.data(newdata)
.filter(function(d) {
return d.type != d3.select(this).property("type")
})
This filter will return those elements that have changed their type. Now we can re-assign the property to reflect the new type and transition those filtered elements.
The snippet below should demonstrate this, the datum is just a number one or two (represented by blue and orange), and is used to set the property type. Click on the svg to update the data, only those circles which change their datum will temporarily change their radius, while also changing their color to reflect their new datum.
var svg = d3.select("body")
.append("svg")
.attr("width",400)
.attr("height",400);
var circles = svg.selectAll("circle")
.data(data())
.enter("circle")
.append("circle")
.attr("cy",function(d,i) {
return Math.floor(i/5) * 40 + 20;
})
.attr("cx", function(d,i) {
return i%5 * 40 + 20
})
.attr("r", 8)
.attr("fill",function(d) { return (d) ? "steelblue" : "orange"})
.property("type",function(d) { return d; });
// update on click:
svg.on("click", function() {
circles.data(data())
.filter(function(d) {
return d != d3.select(this).property("type") // filter out unchanged data
})
.property("type",function(d) { return d; }) // update to reflect new data
.transition()
.attr("r", 20)
.attr("fill","crimson")
.duration(500)
.transition()
.attr("fill",function(d) { return (d) ? "steelblue" : "orange" })
.attr("r",8)
.duration(500);
})
function data() {
var output = [];
d3.range(20).map(function(d) {
output.push(Math.round(Math.random()));
})
return output;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
I'm creating a pie chart from a JSON file. I wonder if there is a way I can take some names from the JSON file and assign them as class names of the arcs created by d3.layout.pie().
Here is an example I created: http://blockbuilder.org/jinlong25/532d889e01d02cef2d24
Essentially, I want to do something like the last line of code below:
var data = [
{
'name': 'apple',
'value': 250
},
{
'name': 'banana',
'value': 100
},
{
'name': 'orange',
'value': 150
}
];
var arcs = svg.selectAll('g.arc')
.data(pie(data.map(function(d) { return d.value; })))
.enter().append('g')
.attr('transform', 'translate(70, 70)')
.attr('class', function(d) { return d.name; };
but since the data has been transformed by pie(), I wonder if there is anyway to add class names to the data generated by pie().
thanks!
d3's layouts helpfully provide a .value() accessor which allows you to specify how get the value of the datum, instead of doing the data.map() operation. So, you could do:
var pie = d3.layout.pie().value(function(d) { return d.value; })
That way, your original datum is preserved in d.data.
So using that definition of pie, your code would change to this:
var arcs = svg.selectAll('g.arc')
.data(pie(data))
.enter().append('g')
.attr('transform', 'translate(70, 70)')
.attr('class', function(d) { return d.data.name; };
edit: added link the relevant documentation.
Some D3 layouts mutate the original dataset but others create a new dataset (like voronoi). In those cases, you can use the array position from the original dataset when working with the new dataset. So from your example:
var arcs = svg.selectAll('g.arc')
.data(pie(data.map(function(d) { return d.value; })))
.enter().append('g')
.attr('transform', 'translate(70, 70)')
.attr('class', function(d,i) { return data[i].name; };
Apologies for such a basic question. Is it possibly to fill the area between two lines?
For example, I have historical high/low temperature data. I'd like to create a shaded area between these two timeseries.
I've tried using the area feature of a lineChart like this:
return [
{
values: tsData.normal,
key: 'Historical Normal',
classed: 'dashed'
},
{
area: true,
values: tsData.hi,
key: 'Historical Max',
color: '#0000ff'
},
{
area: true,
values: tsData.low,
key: 'Historical Min',
color: '#ffffff',
fillOpacity: 1
}
];
Which results in this image:
Note that the gridlines below the Historical Min line are hidden by the filled areas. This solution is a bit hacky and ugly. Is there a more direct way to do this?
I achieved a better solution by drawing an area using d3.
First I created an array (I called areaData) that merges tsData.hi and tsData.low. For each data point I push for eg:
{x: "The x value", y0:"The y value from tsData.hi", y1:"The y value from tsData.low"}
Then I defined the x and y scale based on the chart's scales:
var x = chart.xScale();
var y = chart.yScale();
Then I added an area definition as
var area = d3.svg.area()
.x(function (d) { return x(d.x); })
.y0(function (d) { return y(d.y0); })
.y1(function (d) { return y(d.y1); });
Next I drew the area using:
d3.select('.nv-linesWrap')
.append("path")
.datum(areaData)
.attr("class", "area")
.attr("d", area)
.style("fill", "#AEC7E8")
.style("opacity", .2);
This achieves a better looking solution. Because nvd3 updates the chart when the window is resized. I wrapped the drawing of the area in a function. So that I can call the function when the window is resized while removing the previously drawn area as follows:
var drawArea = function () {
d3.select(".area").remove();
d3.select('.nv-linesWrap')
.append("path")
.datum(areaData)
.attr("class", "forecastArea")
.attr("d", area)
.style("fill", "#AEC7E8")
.style("opacity", .2);
}
drawArea();
nv.utils.windowResize(resize);
function resize() {
chart.update();
drawArea();
}
I also disabled switching the series on and off using the legend as I wasn't handling that case:
chart.legend.updateState(false);
The result:
I'm just starting out with D3.js. I've created a simple enough donut chart using this example. My problem is, if I have an array of objects as my data source - data points for ex. would be a1.foo or a1.bar - and I want to switch between them, how would i go about doing this? My current solution looks ugly and it can't be the proper way of doing it - code below.
//Call on window change event
//Based on some parameter, change the data for the document
//vary d.foo to d.bar and so on
var donut = d3.layout.pie().value(function(d){ return d.foo})
arcs = arcs.data(donut(data)); // update the data
Is there a way I can set the value accessor at run time other than defining a new pie function?
Generally to switch the data that is being displayed you would create a redraw() function that would then update the data for the chart. In the redraw you'll need to make sure to handle the three cases - what should be done when data elements are modified, what should be done when new data elements are added, and what should be done when data elements are removed.
It usually looks something like this (this example changes the data set through panning, but it doesn't really matter). See the full code at http://bl.ocks.org/1962173.
function redraw () {
var rects, labels
, minExtent = d3.time.day(brush.extent()[0])
, maxExtent = d3.time.day(brush.extent()[1])
, visItems = items.filter(function (d) { return d.start < maxExtent && d.end > minExtent});
...
// upate the item rects
rects = itemRects.selectAll('rect')
.data(visItems, function (d) { return d.id; }) // update the data
.attr('x', function(d) { return x1(d.start); })
.attr('width', function(d) { return x1(d.end) - x1(d.start); });
rects.enter().append('rect') // draw the new elements
.attr('x', function(d) { return x1(d.start); })
.attr('y', function(d) { return y1(d.lane) + .1 * y1(1) + 0.5; })
.attr('width', function(d) { return x1(d.end) - x1(d.start); })
.attr('height', function(d) { return .8 * y1(1); })
.attr('class', function(d) { return 'mainItem ' + d.class; });
rects.exit().remove(); // remove the old elements
}