Dojo Chart Legend drawing before StoreSeries returned

92 Views Asked by At

I've got stuck trying to add a legend to a Dojo chart with multiple StoreSeries. The legend is being drawn before the store series have returned and assigned a symbol.

I'm using JsonRest as the store, connecting to ASP.NET WebApi2 site to access data in SQL Server. The chart draws fine, only the legend is failing to draw correctly.

I've tracked the issue down to the legend module, in the refresh function. The array of series is populated, but none of them have been assigned a symbol at the point the legend is drawn:

var s = this.series || this.chart.series;
// AT THIS POINT s CONTAINS ALL MY SERIES
if( s.length == 0 ) {
    return;
}
if ( s[0].chart.stack[0].declaredClass == "dojox.charting.plot2d.Pie" ) {
    //Pie chart stuff
}
else {
    arr.forEach( s, function( x ) {
        // AT THIS POINT x.dyn IS STILL EMPTY = legend with labels but no symbol
        this._addLabel( x.dyn, x.legend || x.name );
    }, this );
}

Is there something I can do to make the legend wait for all the store series to have returned?

I've tried using all/when on various properties of the chart but they all seem to return instantly.

EDIT: Here is the code used to make the chart, I'm pretty sure I just need to find a way to check that the chart has finished rendering before adding the legend, but I haven't found the right thing to check for:

init: function() {
// Create the chart content
this._chart = new Chart( this.chartNode );

// Add plots
this._chart.addPlot( 'default', {
    type: Lines,
    hAxis: 'x',
    vAxis: 'y'
} );

this._chart.addPlot( 'secondary', {
    type: StackedAreas,
    hAxis: 'x',
    vAxis: 'y'
} );

// Add axis
var xAxis = {
    minorTicks: false,
    titleGap: 8,
    titleFont: "normal normal normal 12px sans-serif",
    titleOrientation: 'away'
};

var yAxis = {
    vertical: true,
    includeZero: true,
    titleGap: 8,
    titleFont: "normal normal normal 12px sans-serif",
    titleOrientation: 'axis'
};

this._chart.addAxis( 'x', xAxis );
this._chart.addAxis( 'y', yAxis );

var lineStore = new ObservableStore( JsonRest( { target: this.lineUri } ) );

// Add series
var opts = {};

var plot = { plot: 'default' };
this._chart.addSeries( "Target", new StoreSeries( lineStore, opts, 'total'), plot );
this._chart.addSeries( "Threshold", new StoreSeries( lineStore, opts, 'target'), plot );
this._chart.addSeries( "YTD", new StoreSeries( lineStore, opts, 'ytd'), plot );

plot = { plot: 'secondary' };
// areaList is [{ name: 'hello', id: 0 }]
array.forEach( this.areaList, lang.hitch( this, function( area ) {
    var store = new StoreSeries( new ObservableStore( JsonRest( { target: this.areaUri + '/' + String( area.id ) } ) ), {}, 'total');
    this._chart.addSeries( area.name, store, plot );
}));

// Add Grid
this._chart.addPlot( 'grid', { type: Grid,
    hAxis: 'x',
    vAxis: 'y',
    hMajorLines: true,
    hMinorLines: false,
    vMajorLines: true,
    vMinorLines: false,
    majorHLine: { color: '#ACACAC', width: 0.5 },
    majorVLine: { color: '#ACACAC', width: 0.5 }
} );

// Apply theme
this._chart.setTheme( MyChartTheme );

// Draw!
this._chart.render();

// Legend
new Legend( { chart: this._chart, horizontal: false, title: 'Legend' }, this.legendNode );
},
1

There are 1 best solutions below

0
AEgman On

I found a solution to the legend being created before all series were ready.

Using the deferred query results of a cache store and a memory store instead of the JsonRest store, we can wait until all series are ready before creating the legend.

  1. Instead of using the JsonRest in the StoreSeries, create a cache of the JsonRest to a Memory store.
  2. Populate the cache by calling query() on the cache store manually.
  3. Wait for the query to finish (use "when"), then use the cached memory store when adding the StoreSeries.
  4. Finally resolve the deferred, and continue creating the chart (e.g. render and add legend).

This method only requires one call to the rest service, so is much faster than connecting the store series directly to the rest store (which queries once per series). There is still a delay before the chart appears on the page, and there may be issues using a memory store with large datasets. I have used this method with ~15000 records without any issues.

Corrected code:

init: function() {
// Create the chart content
this._chart = new Chart( this.chartNode );

// Add plots
this._chart.addPlot( 'default', {
    type: Lines,
    hAxis: 'x',
    vAxis: 'y'
} );

this._chart.addPlot( 'secondary', {
    type: StackedAreas,
    hAxis: 'x',
    vAxis: 'y'
} );

// Add axis
var xAxis = {
    minorTicks: false
};

var yAxis = {
    vertical: true,
    includeZero: true
};

this._chart.addAxis( 'x', xAxis );
this._chart.addAxis( 'y', yAxis );

// Add the series as a deferred!
when( this.addSeries(), lang.hitch( this, function() {
    // Apply theme
    this._chart.setTheme( MyChartTheme );

    // Draw!
    this._chart.render();

    // Legend
    new Legend( { chart: this._chart, horizontal: false, title: 'Legend' }, this.legendNode );
}));
},

addSeries: function() {
var def = new Deferred();
var lineStore = new JsonRest( { target: this.lineUri } );
var memStore = new Memory();
var cacheStore = new Cache( lineStore, memStore );

// Manually populate the cached memory store (so each series doesn't do it!)
when( cacheStore.query(), lang.hitch( this, function(){
    // Add series
    var opts = {};

    var plot = { plot: 'default' };
    this._chart.addSeries( "Target", new StoreSeries( memStore, opts, 'total'), plot );
    this._chart.addSeries( "Threshold", new StoreSeries( memStore, opts, 'target'), plot );
    this._chart.addSeries( "YTD", new StoreSeries( memStore, opts, 'ytd'), plot );

    plot = { plot: 'secondary' };
    // areaList is [{ name: 'hello', id: 0 }]
    array.forEach( this.areaList, lang.hitch( this, function( area ) {
        var store = new StoreSeries( new ObservableStore( memStore, {}, 'total');
        this._chart.addSeries( area.name, store, plot );
    }));

    def.resolve(true);
}));

return def.promise;
},