Related
I have a chart in NVD3 with a date on the X axis and a float on the Y axis.
It displays fine, but when I hover over the chart to make the tooltip pop up, it doesn't show it for the dataset I'm currently hovering over. Here's a GIF to make it more clear, hopefully:
This is the code I've used:
<script>
var data = function() {
return [
{
values: [
{x:"2018-09-08", y:19.98},{x:"2018-09-07", y:11.99},{x:"2018-09-06", y:9.98},{x:"2018-09-05", y:4.99},{x:"2018-09-03", y:9.98},{x:"2018-09-02", y:14.99}, ],
key: 'Turnover'
}
];
}
nv.addGraph(function() {
var chart = nv.models.lineChart()
.useInteractiveGuideline(true)
.xScale(d3.time.scale())
.x( function(d){return d3.time.format('%Y-%m-%d').parse(d.x);} );
;
chart.xAxis
.axisLabel('Date')
.tickFormat(function(d) {return d3.time.format("%Y-%m-%d")(new Date(d))});
;
chart.yAxis
.axisLabel('Sales')
.tickFormat(d3.format('.02f'))
;
chart.showLegend(false);
d3.select('#nvd3 svg')
.datum(data())
.transition().duration(500)
.call(chart)
;
nv.utils.windowResize(chart.update);
return chart;
});
</script>
Edit 1: When I do not use the .useInteractiveGuideline(true) function, it does work and the tooltip is presented on the correct set of data. However, I do want to use this function. So any help here?
Looking at the examples of the NVD3 site they work with a Linear Scale for time axis.
Converting the code to this too shows the requested behavior.
You have to set the tick positions yourself because the automatic ticks for a linear scale are not on dates
var data = function() {
return [
{
values: [
{x:"2018-09-02", y:14.99},
{x:"2018-09-03", y:9.98},
{x:"2018-09-05", y:5.99},
{x:"2018-09-06", y:9.98},
{x:"2018-09-07", y:11.99},
{x:"2018-09-08", y:19.98}
],
key: 'Turnover'
}
];
};
var formatDate = d3.time.format("%Y-%m-%d");
nv.addGraph(function () {
var chart = nv.models.lineChart()
.useInteractiveGuideline(true)
;
var mydata = data();
mydata[0].values.forEach(e => { e.x = Date.parse(e.x); });
chart.xAxis
.axisLabel('Date')
.tickFormat(function(d) {return formatDate(new Date(d))})
.tickValues(mydata[0].values.map( d => d.x ))
;
chart.yAxis
.axisLabel('Sales')
.tickFormat(d3.format('.02f'))
;
chart.showLegend(false);
d3.select('#nvd3 svg')
.datum(mydata)
.transition().duration(500)
.call(chart)
;
nv.utils.windowResize(chart.update);
return chart;
});
I am having a great deal of trouble getting my pie/donut chart to update data dynamically. I have it configured so that the user slides a range input to select which month of data he/she wants to see, then the data is passed to my d3 visual. For the sake of simplicity I have hard-coded the data in my example. You may view the snippet below:
var width = 450;
var height = 350;
var margins = { left: 0, top: 0, right: 0, bottom: 0 };
var svg = d3.select("body")
.append("svg")
.attr("width", width+margins.right)
.attr("height", height+margins.top);
var period = ['JAN 2016','FEB 2016','MAR 2016', 'APR 2016', 'MAY 2016', 'JUN 2016',
'JUL 2016', 'AUG 2016', 'SEP 2016', 'OCT 2016', 'NOV 2016', 'DEC 2016'];
d3.select('#timeslide').on('input', function() {
update(+this.value);
});
function update(value) {
document.getElementById('range').innerHTML=period[value];
create_pie(period[value]);
}
var pie_data = {
'JAN2016': [16,4,1,30],
'FEB2016': [17,4,0,30],
'MAR2016': [16,5,1,29],
'APR2016': [17,4,1,29],
'MAY2016': [17,4,1,29],
'JUN2016': [17,4,2,28],
'JUL2016': [18,3,2,28],
'AUG2016': [18,3,2,28],
'SEP2016': [18,3,2,28],
'OCT2016': [18,3,3,27],
'NOV2016': [18,3,3,27],
'DEC2016': [18,3,3,27]
}
function create_pie(month) {
var radius = Math.min(width, height) / 4;
var color = d3.scale.ordinal().range(['darkblue','steelblue','blue', 'lightblue']);
var arc = d3.svg.arc()
.innerRadius(radius - 50)
.outerRadius(radius - 20);
var sMonth = String(month).replace(' ','');
var pData = pie_data[sMonth];
var pie = d3.layout.pie()
.padAngle(.05)
.value(function(d) { return d; })
.sort(null);
var pieG = svg.append('g')
.attr('transform', 'translate(' + 100 +',' + 75 + ')');
var Ppath = pieG.datum(pData).selectAll(".pie")
.data(pie);
Ppath
.enter().append("path").attr('class','pie');
Ppath
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
});
Ppath
.transition()
.duration(650)
.attrTween("d", arcTweenUpdate);
Ppath
.exit().remove();
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);
};
}
}
create_pie('JAN 2016');
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="//d3js.org/d3.v3.min.js"></script>
</head>
<body>
<div id='sliderContainer'>
<input id='timeslide' type='range' min='0' max='11' value='0' step='1'/><br>
<span id='range'>JAN 2016</span>
</div>
The good news is the pie chart is getting the new data each time the month is updated, because it looks like the pie chart is indeed moving. The bad news is the pie chart is looking very jerky and it seems my .transition().duration(650) is not working at all. Actually I am started to think that the pie chart is being drawn again and again on top of itself, because the pie chart looks more blurry with each update of the data. I'm not sure why that would be since I was extra careful to include the Ppath.exit().remove(); at the presumably correct place. Ultimately, I'm left feeling like my understanding of how to dynamically update pie data is fundamentally flawed.
I soon realized I wasn't the first to have some issues with the pie transitions. The trickiest thing seems to be the arcTween part. I looked at http://stackoverflow.com/questions/22312943/d3-sunburst-transition-given-updated-data-trying-to-animate-not-snap and followed along as closely as I could. I used that implementation of the arcTweenUpdate, but unfortunately my situation did not improve. You may notice from the snippet that the colored arcs are moving around but the empty spaces that "divide" or "slice" up the pie are static. That's not what I want, it should all be transitioning, nice and smooth. There should not be static parts or awkwardly transitioning parts like it is currently.
Question: How can I keep the dynamic nature of the visual (access pie_data in its current format via my function create_pie) but also have a smooth, clean looking transition like M. Bostock's classic donut?
Note: M. Bostock's block uses a change() function to update the pie chart. I prefer an answer that corrects/augments/adds to my existing code structure (i.e. Ppath.enter()... Ppath.transition() ... Ppath.exit().remove()) However, I would be willing to accept a change() function similar to M. Bostock's orginial if someone can explain explicitly why my method as per this post is impossible / highly impracticle.
Edit
Another unforseen issue when I try to update the data dynamically is concerning radio buttons. As per Karan3112's formatData() function:
function formatData(data) {
if (document.getElementById("radioButton1").checked) {
return data[Object.keys(data)[0]].slice(0,4).map(function(item, index) {
let obj = {};
Object.keys(data).forEach(function(key) {
obj[key] = data[key][index] //JAN2016 : [index]
})
return obj;
})
}
else if (document.getElementById("radioButton2").checked) {
return data[Object.keys(data)[0]].slice(5,8).map(function(item, index) {
let obj = {};
Object.keys(data).forEach(function(key) {
obj[key] = data[key][index] //JAN2016 : [index]
})
return obj;
})
}
}
Basically, in my real data, each month has an array of length 8 like this:
'JAN2016': [17,4,1,29,7,1,1,42],
So depending on which radio button is checked, I want to have the pie be drawn according to either the first 4 items in the array for radioButton1 or the last 4 items in the array for radioButton2.
I initially omitted this part of the task for my OP because I figured it would be simple enough to adapt, but after trying for a good while with little progress, I have reconsidered. My slices don't seem to be working. I think it is because the formatData function is only called once. I tried putting a formatData call inside the change() function, but that didnt work either.
Following the example by Mike Bostock have updated your code as follows.
Added a data format function which will return the data in a {label : value} format.
Updated the code logic from loading/redrawing the data onUpdate to updating the pie value.
var width = 450;
height = 350;
radius = Math.min(width, height) / 4;
var color = d3.scale.ordinal().range(['darkblue','steelblue','blue', 'lightblue']);
var pie = d3.layout.pie()
.value(function(d) { return d['JAN2016']; })
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 50)
.outerRadius(radius - 20);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr('transform', 'translate(' + 100 +',' + 75 + ')');
var period = ['JAN 2016','FEB 2016','MAR 2016', 'APR 2016', 'MAY 2016', 'JUN 2016',
'JUL 2016', 'AUG 2016', 'SEP 2016', 'OCT 2016', 'NOV 2016', 'DEC 2016'];
var pie_data = {
'JAN2016': [16,4,1,30],
'FEB2016': [17,4,0,30],
'MAR2016': [16,5,1,29],
'APR2016': [17,4,1,29],
'MAY2016': [17,4,1,29],
'JUN2016': [17,4,2,28],
'JUL2016': [18,3,2,28],
'AUG2016': [18,3,2,28],
'SEP2016': [18,3,2,28],
'OCT2016': [18,3,3,27],
'NOV2016': [18,3,3,27],
'DEC2016': [18,3,3,27]
};
var path = svg.datum(formatData(pie_data)).selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) { this._current = d; }); // store the initial angles
d3.select('#timeslide').on('input', function() {
change(this.value);
});
function change(key) {
var value = period[key].replace(' ','');
document.getElementById('range').innerHTML=period[key];
pie.value(function(d) { return d[value]; }); // change the value function
path = path.data(pie); // compute the new angles
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
function formatData(data){
return data[Object.keys(data)[0]].map(function(item, index){
let obj = {};
Object.keys(data).forEach(function(key){
obj[key] = data[key][index] //JAN2016 : [index]
})
return obj;
})
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id='sliderContainer'>
<input id='timeslide' type='range' min='0' max='11' value='0' step='1'/><br>
<span id='range'>JAN 2016</span>
</div>
I am using NVD3js with lineChart graph,
My dataset contains timestamps and values for 24 hour view, each point at xx:30,
example:
(00:30 - val_X, 01:30 - val_Y, 02:30 - val_Z, ...,23:30 - val_W)
[{"values":[{"x":1471552200000,"y":64262.58061338499},{"x":1471555800000,"y":62678.866737321965},{"x":1471559400000,"y":61750.00767023745},{"x":1471563000000,"y":61326.61449656117},{"x":1471566600000,"y":60561.389682317065},{"x":1471570200000,"y":60144.93124866629},{"x":1471573800000,"y":59785.83442165563},{"x":1471577400000,"y":69062.50859207465},{"x":1471581000000,"y":75910.99616145018},{"x":1471584600000,"y":75444.86937388242},{"x":1471588200000,"y":74308.98109650609},{"x":1471591800000,"y":73645.18914600794},{"x":1471595400000,"y":72899.18083184483},{"x":1471599000000,"y":72500.14363895029},{"x":1471602600000,"y":71881.80897423408},{"x":1471606200000,"y":71608.16164435333},{"x":1471609800000,"y":71560.75929676845},{"x":1471613400000,"y":71693.39707357567},{"x":1471617000000,"y":71662.43123936957},{"x":1471620600000,"y":70112.4723063366},{"x":1471624200000,"y":67639.43208386854},{"x":1471627800000,"y":65567.79922797237},{"x":1471631400000,"y":64098.448319984534},{"x":1471635000000,"y":62591.10055950839},{"x":1471638600000,"y":61341.82034098393},{"x":1471642200000,"y":60428.577018083095}],"key":"System Score","color":"#325c80","area":true}]
When I display it on a lineChart graph I see x domain from 00:30 to 23:30, I wanted to present 00:00 - 23:59, so I added 23:30 the previous day and 00:30 the next day and then I set the xDomain to [00:00, 23:59], However, the points are a bit off from its correct location althought the data is correct.
Is there anoter way to solve that problem?
Graph init:
nv.addGraph(function() {
score_graph = nv.models.lineChart()
.xScale(d3.time.scale())
.useInteractiveGuideline(true)
.showLegend(false)
;
score_graph.xAxis
.axisLabel('')
;
score_graph.yAxis
.axisLabel('y axis')
.ticks(5)
.tickFormat(d3.format('.3s'))
;
return score_graph;
});
Graph update data:
startTime is 00:00 and endTime is 23:59 of same day,
dataset has also 23:30 of previous day and 00:30 of next day points
var tooltip_obj = score_graph.interactiveLayer.tooltip;
tooltip_obj.headerFormatter(function(d, i) {
return graphLocales.timeFormat(time_format)(new Date(+d));
});
score_graph.xAxis
.tickFormat( function(d) { return graphLocales.timeFormat(time_format)(new Date(+d)); } );
var line_chart_data = d3.select('#score-history-block svg').datum(parsed_data_system);
var y_max = d3.max(parsed_data_system[0].values, function(d) { return +d.y; }) * 1.2;
if (y_max < 100) y_max = 100;
score_graph.yDomain([0, y_max] );
score_graph.xDomain([startTime.toDate(), endTime.toDate()]);
line_chart_data.transition().duration(500)
.call(score_graph)
;
I am new to Javascript and couldn't find error in my code.
I am using NVD3 charts here. It is a time series based chart with date and closing prices of a particular stock. Data ranges from 2005 till now.
Here is the code
var data= JSON.parse("Data.JSON")
nv.addGraph(function() {
var chart = nv.models.lineChart()
.margin({top: 70, right: 70, bottom: 70, left: 70})
.useInteractiveGuideline(true)
.transitionDuration(100)
.showYAxis(true)
.showXAxis(true)
;
//Chart x-axis settings
chart.xAxis
.axisLabel('date')
.tickFormat(function(d) {return new Date((data.Date - (25567 + 1))*86400*1000);
chart.yAxis //Chart y-axis settings
.axisLabel('close')
.tickFormat(d3.scale.linear(data.Close));
d3.select('#Charts svg') //Selecting the <svg> element where i want to render the chart in.
.datum(data) //Populating the <svg> element with chart data...
.call(chart); //Finally, rendering the chart!
//Update the chart when window resizes.
})
;
//Data
{
"Date": [13089, 13094, 13095, 13096, 13097, 13098, 13101, 13103, 13104, 13105, 13108, 13109, 13110]
"Close": [ 2419.1, 2461.6, 2492.7, 2489.1, 2500.7, 2548.7, 2558.7, 2582.8, 2603.9, 2620.1, 2602.5, 2572.8]
}
The number of array elements in "Close" are less compared to "Date".
Here is a possible solution that you might be looking for:
nv.addGraph(function () {
var chart = nv.models.lineChart();
chart.xAxis.axisLabel('date')
.tickFormat(d3.format(''));
chart.yAxis.axisLabel('close')
.tickFormat(d3.format(''));
d3.select('#dateChart')
.datum(chartData())
.transition().duration(500)
.call(chart);
nv.utils.windowResize(function () {
d3.select('#dateChart').call(chart)
});
return chart;
});
function chartData() {
var myData = {
"Date": [13089, 13094, 13095, 13096, 13097, 13098, 13101, 13103, 13104, 13105, 13108, 13109, 13110],
"Close": [2419.1, 2461.6, 2492.7, 2489.1, 2500.7, 2548.7, 2558.7, 2582.8, 2603.9, 2620.1, 2602.5, 2572.8, 2588.8]
//The number of array elements in Close were less compared to Date. Hence added 2588.8 as the last element
};
var result = [];
for (var i = 0; i < myData.Date.length; i++) {
result.push({
x: myData.Date[i],
y: myData.Close[i]
});
}
return [{
values: result,
key: 'Date Chart',
color: '#ff7f0e'
}];
}
JS Fiddle: https://jsfiddle.net/m7oaxjue/3/
I am trying to create realtime linechart based on notifcations that i am getting. I have looked in examples - http://jsfiddle.net/kaliatech/4TMMD/
however i cannot make it work, the rate is being updated and data is inserted into the array, but i am getting in the plot only one point which is always the first one
this.data = [{
key: "New Incidents Rate",
values: getData()
}];
function getData() {
return that.newIncidentRate;
}
redraw();
function redraw() {
nv.addGraph(function() {
that.chart = nv.models.lineChart()
.x(function(d) { return d.x })
.y(function(d) { return d.y });
//.color(d3.scale.category10().range());
that.chart.xAxis
.axisLabel('Time')
.tickFormat(function(d) {
return d3.time.format('%x')(new Date(d))
});
that.chart.yAxis
.axisLabel('Rate')
.tickFormat(d3.format(',r'));
d3.select('#chart svg')
.datum(that.data)
//.transition().duration(500)
.call(that.chart);
nv.utils.windowResize(that.chart.update);
return that.chart;
});
}
amplify.subscribe("newIncident", function (rate) {
$scope.newRate = rate;
$scope.$apply();
if (that.newIncidentRate.length > 20) {
that.newIncidentRate.shift();
}
var currentTime = new Date();
that.newIncidentRate.push({
x: new Date(currentTime.getTime()),
y: rate
});
redraw();
});
i have tested it more and it seems to be related to the number of points, when i take the sample all is ok (adding 30 point - fresh every time) same when adding 2 points , so i tested to keep all the points in container and always copy it to new one - falied, it seems there is a problem to increase the number of points
function getData() {
var arr = [];
var theDate = new Date(2012, 01, 01, 0, 0, 0, 0);
newIncidentRate.push({x: new Date(theDate.getTime()), y: Math.random() * 100});
theDate.setDate(theDate.getDate() + moveDate);
++moveDate;
for (var d in newIncidentRate) {
arr.push(d) ;
}
return arr;
}
full code - http://jsfiddle.net/lirazrom/Wx5bG/