Question

I got this template (default)

<span class="x-legend-item-marker {[values.disabled?'x-legend-inactive':'']}" style="background:{mark};"></span>{name}

that produce this :

enter image description here

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.

Was it helpful?

Solution

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:

Empty serie without CSS

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?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top