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 am working on a line graph in d3.js and am unsure how to to iterate through each country and update my graph's points. I want to draw each country on my map. In my code I have only hard coded the first country and the output shown in the following images. Have attached my csv file to show the column names. I am unsure whether I need to alter my csv file to do so.
any help is appreciated
function init(){
var w = 600;
var h = 600;
var barPadding = 20;
var dataset;
var rowConverter = function(d){
return {
year: parseFloat(d.year),
Afghanistan: (d.Afghanistan),
Albania: (d.Albania),
Algeria: (d.Algeria),
Andorra: (d.Andorra),
Angola: (d.Angola)
};
}
d3.csv("hello.csv", rowConverter, function(data){
dataset = data;
if (data==null){
alert("Error, data has not been loaded!");
}
else{
draw(dataset);
console.log(dataset);
}
});
function draw(){
var xScale = d3.scaleLinear()
.domain([d3.min(dataset,function(d){
return d.year;
}),
d3.max(dataset,function(d){
return d.year;
})])
.range([barPadding,w-barPadding]);
var yScale = d3.scaleLinear()
.domain([0,100])
.range([h-barPadding,barPadding*2]);
var xAxis = d3.axisBottom()
.scale(xScale)
.ticks(5);
var yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
var valueline = d3.line()
.x(function(d) { return xScale(d.year); })
.y(function(d) { return yScale(d.Afghanistan); });
var svg = d3.select("#chart")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d.year);
})
.attr("cy", function(d) {
return yScale(d.Afghanistan);
})
.attr("r", 5)
.attr("fill","slategrey")
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
return d.year + "," + d.Afghanistan;
})
.attr("x", function(d) {
return xScale(d.year);
})
.attr("y", function(d) {
return yScale(d.Afghanistan);
})
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "blue");
svg.append("path")
.data([dataset])
.attr("class", "line")
.attr("d", valueline);
svg.append("g")
.attr("class","axis")
.attr("transform", "translate(0," + (h - barPadding) + ")")
.call(xAxis);
svg.append("g")
.attr("class","axis")
.attr("transform", "translate(" + barPadding + ",0)")
.call(yAxis);
}
}
window.onload=init;
As a selectAll(null).data(dataArray).enter() uses a data array to enter an element for each item in the data array, we need to create an array for each line we wish to enter. Currently you have an array for each year, but we want to enter a path for each data series/country. So we need to create an array where each item in that array represents a path.
This requires altering the structure of our data from:
[
{year: 2000, series1: number, series2: number... },
{year: 2001, series1: number, series2: number... },
....
]
To an array with an item for each line:
[
{ year: 2000, series1: number },
{ year: 2001, series1: number },
...
{ year: 2000, series2: number },
{ year: 2001, series2: number },
...
]
I'm using this approach because it is commonly seen in d3 cannonical examples such as this.
This is relatively easy to do. After we parse in our csv/tsv/dsv with d3, we can access the columns of the dataset with dataset.columns. The first column isn't a series we want to plot, it represents the x axis, so we can slice it off with dataset.columns.slice(1). Ok, with the remaining columns we can iterate through each series and create the data array above:
I'm using csvParse in the snippet, which replicates d3.csv except that it doesn't use a callback function for the returned data, letting me define the dataset with var dataset = d3.csvParse(... rather than d3.csv("file.csv", function(error, dataset) {...})
var csv = "year,series1,series2,series3\n"+
"2000,5,2,8\n"+
"2001,6,4,7\n"+
"2002,9,3,5\n"+
"2003,10,6,7\n"+
"2004,9,7,8"
var data = d3.csvParse(csv);
var series = data.columns // get the columns
.slice(1) // drop the first column(years)
.map(function(series) { // for each series:
return { // return a new object:
series: series, // name it
values: data.map(function(d) { // get the data:
return { year: d.year, value: d[series] };
})
}
});
console.log(series);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
Now we have an item in the data array for each series we want to draw a line for. Now we're cooking with gas. So we can now use selectAll().data(series) to enter a line for each item in the data array, creating a line for each series.
In keeping with Mike Bostock's example I linked to above, I've created an property which identifies which series each item represents, as well as a property which holds the arrays of year/value pairings.
Here's a quick demo:
var csv = "year,series1,series2,series3\n"+
"2000,5,2,8\n"+
"2001,6,4,7\n"+
"2002,9,3,5\n"+
"2003,10,6,7\n"+
"2004,9,7,8"
var data = d3.csvParse(csv);
var series = data.columns
.slice(1)
.map(function(series) {
return {
series: series,
values: data.map(function(d) {
return { year: d.year, value: d[series] };
})
}
});
var x = d3.scaleLinear().domain([2000,2004]).range([0,500]);
var y = d3.scaleLinear().domain([0,10]).range([300,0]);
var color = d3.scaleOrdinal().range(d3.schemeCategory20);
var line = d3.line()
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.value); });
d3.select("svg")
.selectAll("path")
.data(series)
.enter()
.append("path")
.attr("fill","none")
.attr("stroke", function(d,i) { return color(i) })
.attr("d",function(d) { return line(d.values) });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
How can I invert the xAxis for the NVD3 MultiBar chart? The data is the same (except I had to convert an {x: ... , y: ... } object for the values property to a paired array for the stacked area chart. The data is displaying correctly on the lineChart as well.
FYI, the date data is ordered from latest to earliest in the array but that shouldn't matter for a time scale, right?
// Stacked Area Chart
var renderStackedAreaChart = function(data){
var newData = convertData(data);
nv.addGraph(function() {
var chart = nv.models.stackedAreaChart()
.x(function(d) { return d[0]; })
.y(function(d) { return d[1]; })
.clipEdge(true);
chart.xAxis
.tickFormat(function(d) { return d3.time.format('%b')(new Date(d)); });
chart.yAxis
.tickFormat(d3.format('$,.2f'));
d3.select('#graph svg')
.datum(newData)
.transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
};
// Stacked MultiBar Chart
var renderStackedMultiBar = function(data) {
nv.addGraph(function() {
var chart = nv.models.multiBarChart();
chart.xAxis
.tickFormat(function(d) { return d3.time.format('%b')(new Date(d)); });
chart.yAxis
.tickFormat(d3.format('$,.2f'));
d3.select('#graph svg')
.datum(data)
.transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
}
This isn't supported by NVD3 -- you would have to modify the source code. A quick and dirty workaround is (as you've suggested in the comments) to use an ordinal scale instead of a time scale. This way, the order of the values you specify as domain matters and you can simply pass it in in reverse order.
Note that you'll lose the advantages of a time scale this way though, e.g. automatic label format depending on the time range shown.
How can I set my domain to [0,400] with nvd3? Here is my code:
var chart;
nv.addGraph(function() {
chart = nv.models.multiBarHorizontalChart().x(function(d) {
return d.label
}).y(function(d) {
return d.value
}).margin({
top : 30,
right : 20,
bottom : 50,
left : 175
}).barColor(d3.scale.category20().range()).transitionDuration(250).stacked(true)
chart.yAxis.tickFormat(d3.format(',.2f'));
d3.select('#chart1 svg').datum(long_short_data).call(chart);
nv.utils.windowResize(chart.update);
chart.dispatch.on('stateChange', function(e) {
nv.log('New State:', JSON.stringify(e));
});
return chart;
});
You could use xDomain or yDomain depending on you axis requirement and finally
chart.xDomain([0,400])
If you want to play around with the ranges on the yAxis you could try
chart.forceY([0, 400]); or chart.forceX([0, 400]);
Hope it helps.
Just to add one note that forcing axis values work at the time of chart instantiation. Axis values do not set if I do it after chart instantiation.
I'm trying to draw a simple line chart using nvd3/d3. Things are fine when the graph loads for the first time. But whenever I load new data and update the graph, the transition that occurs on the datapoints during 'mouseover' is lost. The tooltip is shown though. How to fix this?.
have a look at this jsFiddle demo.
adding the code :
//js :
var n = 0;
var data = function (start) {
var line1 = [],
line2 = [];
for (var i = start; i < start + 50; i++) {
line1.push({
x: i,
y: 2 * i
});
line2.push({
x: i,
y: 3 * i
});
}
return [{
values: line1,
key: 'y = 2 * x',
color: '#ff7f0e'
}, {
values: line2,
key: 'y = 3 * x',
color: '#2ca02c'
}];
}
var drawGraph = function (start) {
var chart = nv.models.lineChart();
chart.xAxis.axisLabel('Time (ms)')
.tickFormat(d3.format(',r'));
chart.yAxis.axisLabel('Voltage (v)')
.tickFormat(d3.format('.02f'));
d3.select('#chart svg')
.datum(data(start))
.transition().duration(500)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
}
nv.addGraph(drawGraph(n));
$("button").click(function () {
n += 50;
nv.addGraph(drawGraph(n));
});
the html page :
<div id="chart">
<svg></svg>
</div>
<button>Change Graph</button>
The code you need is actually much simpler than what you have. You only need to create the chart object once and don't need to call nv.addGraph() either. I've updated the jsfiddle here; the relevant code is also below.
var chart = nv.models.lineChart();
chart.xAxis
.axisLabel('Time (ms)')
.tickFormat(d3.format(',r'));
chart.yAxis
.axisLabel('Voltage (v)')
.tickFormat(d3.format('.02f'));
var drawGraph=function(start) {
d3.select('#chart svg')
.datum(data(start))
.transition().duration(500)
.call(chart);
nv.utils.windowResize(chart.update);
}
drawGraph(n);
$("button").click(function(){
n += 50;
drawGraph(n);
});
All the actual drawing work is done in the line .call(chart).