nvd3 chart strange behavior with date range slider - angularjs

While displaying nvd3 chart with Ajax requests, the charts are getting wired up. So I thought the problem is occurring due to asynchronous call delays (may be the chart is displaying before the full data is loaded, etc). So I have used promises, but still I am getting the same problem. Please see the plunker http://plnkr.co/edit/AcIpmki7GNvcoT6Z38Pm?p=preview.
If you change the date range slider, the main chart won't display properly. I am not sure where the problem is? After searching some of the posts in forum, I have come across some thing like gaps in the time series, is it due to that? If that is the case, how can I fix that time series gap issue? I searched nvd3 website, but I don't find any documentation regarding to fill up the gaps in time series data. Some of the forum posts suggests to use c3.js instead nvd3, but I don't know is it really worth to shift to c3.js? Within my experience I feel nvd3 are the best and I don't feel like leaving nvd3.
If nvd3 website provides more samples with real time series data and documentation on some of the common issues like filling gaps in time series, sorting the data, etc it will be really helpful for the beginners.
As my project release dates are nearing, I am not sure what to do now ? Shifting to c3.js is the worst option for me. I have attached the error screen shot too from the same plunker.
I feel there is no problem with the sorting that I am doing with my json data:
angular.forEach($scope.data, function(
series, index) {
series.values.sort(function(a, b) {
return a.x - b.x;
});
});

Couple of issues for you to look at:
I agree with shabeer90, the data is funky. You have multiple values occurring at the same time.
Your sort is correct, but where you have it in your code doesn't work...try adding it inside the response of the ajax call (right after setting $scope.data = data).
Also, you need to make the changes that I outlined in another question (to nvd3 in the lineWithFocusChart model).
Accessing the xScale is a little more tricky...on this chart you need to go through the lines:
$scope.options = {
chart: {
type: 'lineWithFocusChart',
height: 450,
margin : {
top: 20,
right: 20,
bottom: 60,
left: 40
},
transitionDuration: 500,
lines : { // <-- add this
xScale : d3.time.scale()
},
lines2 : { // <-- add this too
xScale : d3.time.scale()
},
xAxis: {
ticks : d3.time.months, <-- add the time interval
axisLabel: 'X Axis',
tickFormat: function(d){
return d3.time.format('%d/%m/%y')(new Date(d))
}
},
x2Axis: {
tickFormat: function(d){
return d3.time.format('%d/%m/%y')(new Date(d))
}
},
yAxis: {
axisLabel: 'Y Axis',
tickFormat: function(d){
return d3.format(',.2f')(d);
},
rotateYLabel: false
},
y2Axis: {
tickFormat: function(d){
return d3.format(',.2f')(d);
}
}
}
};

Related

ChartJS Line chart causes browser crash

I am updating a Line chart using HTTP get through ngResource and a rest API.
My technique is to get the JSON dataset and create a new chart every time a user is clicking on a button.
It works great, but at one time, it causes the browser crash. I have tested on Chrome, Firefox on both Windows and Linux.
In my controller :
$scope.labels = $scope.dataFromREST;
$scope.series = ['Series A'];
$scope.data = [$scope.dataFromREST2];
$scope.onClick = function (points, evt) {
console.log(points, evt);
};
$scope.datasetOverride = [{ yAxisID: 'y-axis-1' }];
$scope.options = {
scales: {
yAxes: [
{
id: 'y-axis-1',
type: 'linear',
display: true,
position: 'left'
}
],
xAxes: [{
responsive: true,
ticks: {
autoSkip: true,
maxTicksLimit: 20
}
}]
}
};
In my index.html :
<canvas id="line" class="chart chart-line" chart-data="data"
chart-labels="labels" chart-series="series" chart-options="options"
chart-dataset-override="datasetOverride" chart-click="onClick">
</canvas>
Is there a way to just update or refresh the Line Chart with the $scope.dataFromREST data received and not create a new Chart object every time? (Because I think, the crash come from creating a new chart every time) I see the ".update()" function, but I can't seem to get it to work.
I have also tried the ".destroy()" and I am still getting the browser wind up to crash.
How can I get rid of that crash? Please help!
Yes, there is a way to simply update the underlying chart.js chart data without having to re-instantiate the chart each and every time. You just need to use the update(duration, lazy) function from the API.
Here is an example that I use in one of my apps (modified for your specific case). Note, chart is my chart.js object (what was returned from new Chart()...:
assembledData = {};
assembledData.data = // data from api call in an acceptable data format that chart.js understands
assembledData.colors = // new color definition for your data so it will update correctly
assembledData.labels = // new label definition for your data so it will update correctly
chart.data.datasets[0].data = assembledData.data;
chart.data.datasets[0].backgroundColor = assembledData.colors;
chart.data.labels = assembledData.labels;
chart.update();
Depending on how your chart behaves you may not have to re-define colors and labels on each update.

Event Handling in nvd3

I am using nvd3 and trying to add event handling while clicking on the chart. But I face some problems while implementing it and I am not able to get a solution due to lack of documentation of nvd3. I followed this link, but I still get the following error in the console:
ReferenceError: chart is not defined
Can anyone tell me what I am missing or any best solution for the event handling in nvd3.
Probably your problem is the name of the graph variable;
I make a fiddle for you.
In my example, "chart" is the name of the graph variable. Check out that also in your case it's the same.
var chart = nv.models.multiBarChart();
Anyway, with the fiddle you can see the full code. If you click on an item, you can see the messages in console.
http://jsfiddle.net/g952qb5c/
As suggested by Giordano, I looked for the name of the graph and the dispatch events. And then I have added these event listener in the options like this.
Note: This solution worked for the nvd3 and angular.
$scope.options2 = {
chart: {
type: 'pieChart',
height: 500,
x: function(d){return d.key;},
y: function(d){return d.y;},
showLabels: true,
duration: 500,
labelThreshold: 0.01,
labelSunbeamLayout: true,
legend: {
margin: {
top: 5,
right: 35,
bottom: 5,
left: 0
}
},
pie:{
dispatch: {
elementClick : function(e){
console.log('element: ' + e.value);
console.log(e);
}
}
}
}
};
Hope this will help to others also.

Chart library for Angular with two Y axis

Is there any library for Angular where I can plot two Y axis on the same graph (one on the left and the other on the right for example)?
I tried libraries by chinmaymk and jtblin, but haven't found suitable method.
Thanks.
var chartData = {
"type":"mixed",
"title":{
"text":"Multiple Y Axis"
},
"scale-x":{
},
"scale-y":{
"values":"0:50:5"
},
"scale-y-2":{
"values":"0:40000:5000",
"guide":{
"visible":false
}
},
"series":[
{
"values":[8,31,12,40,24,20,16,40,9],
"type":"bar",
"scales":"scale-x,scale-y",
"hover-state":{
"visible":false
}
},
{
"values":[11254,26145,17014,2444,17871,25658,34002,26178,20413],
"type":"line",
"scales":"scale-x,scale-y-2"
}
]
};
zingchart.render({
id: "chart",
data: chartData,
width: "100%"
});
#chart {
width: 100%;
}
<script src="http://cdn.zingchart.com/zingchart.min.js"></script>
<div id="chart"></div>
ZingChart supports multiple y-axes with the scale-y-n objects. There's also an easy to use Angular directive on Github.
There's a simple demo in the snippet and here's a more real world example. You can right click the chart itself and select "View Source" to check out the JSON.
If you have any questions about implementation feel free to reach out - I'm on the ZC team.
I've found n3-charts library that solves my problem.

nvd3, lineWithFocusChart y-axis ticks are missing

I am trying to work with nvd3 lineWithFocusChart for time series data. But surprisingly y axis ticks are appearing as 000.00. The json data is in correct format as given in nvd3 website. I have placed my reference plunker at http://plnkr.co/edit/QwMbTL4co0wMVKaQurxq?p=preview.
In order to sort the time series data, I have used the following function. With this, data is displayed properly except y-axis ticks issue. However tool tip is appearing fine with its correct values. What shall I do, to fix the appearance of y-axis ticks as 000.00
angular.forEach($scope.data, function(
series, index) {
series.values.sort(function(a, b) {
return a.x - b.x;
});
});
Your chart width is hiding the values.
Add a width : 700,
And change the margins
margin : {
top: 20,
right: 20,
bottom: 60,
left: 100
},
UPDATE : You could also completely remove width and the margin , by default it will take the div size and re-sieze automatically.
Hope it helps
You just need to not format the Y axis :
yAxis: {
axisLabel: 'Y Axis',
tickFormat: function(d){
return d;
},
rotateYLabel: false
},
Updated plunker.

Legend Template - Chart

I got this template (default)
<span class="x-legend-item-marker {[values.disabled?'x-legend-inactive':'']}" style="background:{mark};"></span>{name}
that produce this :
I want to have the same template with every of it's functionnality. But, I need one more if-clause to it. I don't want an item to be 'legendarize' if it's value is 0.
Here is the complete code
{
xtype: 'container',
title: 'Chart',
iconCls: 'chart',
itemId: 'chart_Tab',
layout: {
type: 'fit'
},
items: [
{
xtype: 'polar',
itemId: 'pie',
colors: [
'#115fa6',
'#94ae0a',
'#a61120',
'#ff8809',
'#ffd13e',
'#a61187',
'#24ad9a',
'#7c7474',
'#a66111',
'#222222',
'#115ea6',
'#94cc0a',
'#b61120',
'#dd8809',
'#11d13e',
'#a68887',
'#94df9d',
'#7f74f4',
'#112341',
'#abcdef1'
],
store: 'relativedata',
series: [
{
type: 'pie',
label: {
textBaseline: 'middle',
textAlign: 'center',
font: '9px Helvetica'
},
labelField: 'strName',
labelOverflowPadding: 0,
xField: 'numValue'
}
],
interactions: [
{
type: 'rotate'
}
],
listeners: [
{
fn: function(element, eOpts) {
var relStore = Ext.getStore('relativedata');
var eleStore = Ext.getStore('element');
var relModel;
var eleModel;
relStore.removeAll();
//Convert to CO2 qty
for(var i = 0; i< eleStore.getCount();i++)
{
eleModel = eleStore.getAt(i);
relModel = Ext.create(APPNAME + '.model.RelativeElement');
relModel.set('strName',eleModel.get('strName'));
relModel.set('numValue', eleModel.get('numValue')*eleModel.getFactor());
relStore.add(relModel);
}
relStore.sync();
//Hide arrows-legend
this._series[0]._label.attr.hidden=true;
},
event: 'painted'
}
],
legend: {
xtype: 'legend',
docked: 'bottom',
itemId: 'pie_legend',
itemTpl: [
'<span class="x-legend-item-marker {[values.disabled?\'x-legend-inactive\':\'\']}" style="background:{mark};"></span>{name}'
],
maxItemCache: 100,
store: 'element'
}
}
]
}
I ask for help because i'm not that good with templates. I would not dare say I understand everything of the default one actually.
I'm back! Yet, nobody's calling me slim shaddy for that... Unluckily!
So, to answer your initial question, the template you need would be something like the following:
// Configuration of the chart legend
legend: {
// Finally, we can use the value field to customize our templates.
itemTpl: [
'<tpl if="value != 0">', // <= template condition
'<span class="x-legend-item-marker {[values.disabled?\'x-legend-inactive\':\'\']}" style="background:{mark};"></span>{name}',
'</tpl>'
]
// ...
}
Unfortunately, as I've said in my previous comment, quick debugger inspection shows that this value variable, or any equivalence, is not available at the time this template is applied.
Now I'm going to give you a detailed explanation about how I was able to overcome this vexation. In part because this is such an involved hack that you'd better know what you're doing if you decide to apply it, and in part because you'll learn a lot more by witnessing the fishing techniques than by being given the fish right away -- in this case, the fish is not available for retail anyway. And also in a large part, I must confess, because I like to be lyrical about things I've put some energy in, and it's late, and my defenses against self congratulation have gotten a bit weak...
So, looking at Ext.chart.Legend's code shows that there's nothing to be done there, it's just a somewhat lightweight extension of Ext.dataview.Dataview. As such it must have a store bounded to it, which, obviously (and unfortunately), is not the one bound to the chart to provide its data.
Another judicious breakpoint (in the Legend's setStore method) shows that this store comes from Ext.chart.AbstractChart, and in the code of this class we can see two things: a dedicated legend store is created in the constructor, and chart series implement a method to feed this store, namely provideLegendInfo.
We're getting closer to our goal. What we need to do is add a value field to the legend store, and have our serie provide the data for this field. Great!
The wise approach now would be to implement these modifications with the minimal amount of replication of Ext's code... But after having spent an inconsiderate amount of time trying to do that with no luck, I'll just settle for wildly overriding these two methods, and giving the advice to put a big bold warning to check that the code of these methods doesn't change with the next versions of Touch:
if (Ext.getVersion().isGreaterThan('2.2.1')) {
// Give yourself a big warning to check that the overridden methods' code
// bellow has not changed (see further comments).
}
With that out of the way, let's go to the point without any further consideration for future generations.
That is, first we add a value field to the legend store:
/**
* Adds a value field to legend store.
*/
Ext.define(null, {
override: 'Ext.chart.AbstractChart'
// Berk, what a lot of code replication :( Let's just hope that this method's code
// won't change in the future...
,constructor: function() {
var me = this;
me.itemListeners = {};
me.surfaceMap = {};
me.legendStore = new Ext.data.Store({
storeId: this.getId() + '-legendStore',
autoDestroy: true,
fields: [
'id', 'name', 'mark', 'disabled', 'series', 'index'
// Adding my value field
,'value'
]
});
me.suspendLayout();
// For whatever reason, AbstractChart doesn't want to call its superclass
// (Ext.draw.Component) constructor and, by using callSuper, skips directly to
// Ext.Container's one. So well... I respect, but I must do it old school since
// callSuper would go to Ext.draw.Component from here.
Ext.Container.prototype.constructor.apply(this, arguments);
// me.callSuper(arguments);
me.refreshLegendStore();
me.getLegendStore().on('updaterecord', 'onUpdateLegendStore', me);
me.resumeLayout();
}
}, function() {
// Post-create functions are not called for overrides in touch as they are
// in ExtJS? Hmm... That would have been the perfect place to issue a big
// warning in case the version has changed, but we'll live with it :(
});
And, second, we make our chart serie feed that value. From your code, I can deduce that you're working with a pie chart, so I'm only giving the code for that, as a matter of illustration... But, if you've followed until here, it should be trivial to implement it for other kind of series. Anyway, here's the code:
/**
* Overrides `provideLegendInfo` to add the value to the legend records.
*
* Here again, let us all cross our fingers very hard, hoping for Sencha's team to not decide
* to add their own extra fields too soon...
*/
Ext.define(null, {
override: 'Ext.chart.series.Pie'
,provideLegendInfo: function(target) {
var store = this.getStore();
if (store) {
var items = store.getData().items,
labelField = this.getLabelField(),
field = this.getField(),
hidden = this.getHidden();
for (var i = 0; i < items.length; i++) {
target.push({
name: labelField ? String(items[i].get(labelField)) : field + " " + i,
mark: this.getStyleByIndex(i).fillStyle || this.getStyleByIndex(i).strokeStyle || 'black',
disabled: hidden[i],
series: this.getId(),
index: i
// Providing actual data value to the legend record
,value: items[i].get(field)
});
}
}
}
});
Let's sum it up. We've got two overrides and a custom template. We could hope that we'd be done by now. But here's what we get:
So, the DataView is adding some markup of its own around the itemTpl's markup. Well, well, well... At this point, I'm tired of tracking Ext's internals and, fortunately (for once!), I envision a quick patch for this. So that is without an hesitation that I'm throwing this CSS rule in:
.x-legend-item:empty {
display: none;
}
And finally we're done. I guess my line of thought and code might be a little tricky to replicate, so let me provide you with a definitive proof that this all works.
In this demo, there is a "metric four" that has a value of 0.
{
'name': 'metric four',
'data': 0
}
But you won't see it. Because that was the point of all this, wasn't it?

Resources