1. #1
    Sencha User Daniil's Avatar
    Join Date
    Jun 2010
    Location
    Saint-Petersburg, Russia
    Posts
    975
    Vote Rating
    112
    Daniil is a name known to all Daniil is a name known to all Daniil is a name known to all Daniil is a name known to all Daniil is a name known to all Daniil is a name known to all

      1  

    Default Chart CategoryAxis groups its values by uniqueness

    Chart CategoryAxis groups its values by uniqueness


    Initial problem

    We faced the problem that a CategoryAxis doesn't group its value by uniqueness.

    Example of Problem
    Code:
    <!DOCTYPE html>
    <html>
    <head>
        <title>Chart CategoryAxis grouping - problem</title>
    
        <link rel="stylesheet" href="../resources/css/ext-all.css" />
    
        <script src="../ext-all-dev.js"></script>
    
        <script>
            Ext.onReady(function () {
                Ext.create("Ext.chart.Chart", {
                    renderTo: Ext.getBody(),
                    height: 400,
                    width: 400,
                    store: {
                        fields: [ "category", "value" ],
                        data: [{
                            category: "category 1",
                            value: 20
                        }, {
                            category: "category 2",
                            value: 50
                        }, {
                            category: "category 1",
                            value: 5
                        }, {
                            category: "category 2",
                            value: 70
                        },]
                    },
                    axes: [{
                        type: "category",
                        position: "bottom",
                        title: "Category",
                        fields: [ "category" ]
                    }, {
                        type: "numeric",
                        position: "left",
                        title: "Value",
                        fields: [ "value" ]
                    }],
                    series: [{
                        type: "scatter",
                        xField: "category",
                        yField: "value"
                    }]                
                });
            });
        </script>
    </head>
    <body>
    </body>
    </html>
    It produces:
    Problem.JPG
    As far as I can see there is no functionality to group those categories by uniqueness. Searching the forum I found that other people also concern on this problem or on a similar one.
    http://www.sencha.com/forum/showthread.php?254698
    http://www.sencha.com/forum/showthread.php?237288
    http://www.sencha.com/forum/showthread.php?187522

    There is no solution in those threads. So, I am trying to come with a solution.

    Solution

    As far as I can see the changes are required in both: CategoryAxis and a Series being used (unless I don't see a more clear solution). So, I started with a ScatterSeries. Here I am posting an example with some overrides. I made the actual overrides and other key points being red to allow you to review it easily.

    It is not fully tested and will probably require some (maybe, a lot) tweaking, but I hope you will find it helpful. Any feedback would be appreciated.

    To test it with your page please take the overrides and set up group: true for a CategoryAxis and groupXValues: true or groupYValues: true for a ScatterSeries.

    Example of Solution
    Code:
    <!DOCTYPE html>
    <html>
    <head>
        <title>Chart CategoryAxis grouping - solution</title>
    
        <link rel="stylesheet" href="../resources/css/ext-all.css" />
    
        <script src="../ext-all-dev.js"></script>
    
        <%-- Overrides --%>
        <script>
            Ext.chart.series.Scatter.override({
                groupXValues: false,
                groupYValues: false,
    
                getPaths: function () {
                    var me = this,
                        chart = me.chart,
                        enableShadows = chart.shadow,
                        store = chart.getChartStore(),
                        data = store.data.items,
                        i, ln, record,
                        group = me.group,
                        bounds = me.bounds = me.getBounds(),
                        bbox = me.bbox,
                        xScale = bounds.xScale,
                        yScale = bounds.yScale,
                        minX = bounds.minX,
                        minY = bounds.minY,
                        boxX = bbox.x,
                        boxY = bbox.y,
                        boxHeight = bbox.height,
                        items = me.items = [],
                        attrs = [],
                        x, y, xValue, yValue, sprite,
                        cacheXValues = [],
                        cacheYValues = [],
                        indexInCache;
    
                    for (i = 0, ln = data.length; i < ln; i++) {
                        record = data[i];
                        xValue = record.get(me.xField);
                        yValue = record.get(me.yField);
                        //skip undefined or null values
                        if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)
                            || xValue == null || yValue == null) {
                            if (Ext.isDefined(Ext.global.console)) {
                                Ext.global.console.warn("[Ext.chart.series.Scatter]  Skipping a store element with a value which is either undefined or null  at ", record, xValue, yValue);
                            }
                            continue;
                        }
    
                        // Ensure a value
                        if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)) {
                            if (me.groupXValues) {
                                indexInCache = cacheXValues.indexOf(xValue);
    
                                if (indexInCache > -1) {
                                    xValue = indexInCache;
                                } else {
                                    cacheXValues.push(xValue);
                                    xValue = i;
                                }
                            } else {
                                xValue = i;
                            }
                        }
    
                        if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {
                            if (me.groupYValues) {
                                indexInCache = cacheYValues.indexOf(yValue);
    
                                if (indexInCache > -1) {
                                    yValue = indexInCache;
                                } else {
                                    cacheYValues.push(yValue);
                                    yValue = i;
                                }
                            } else {
                                yValue = i;
                            }
                        }
    
                        x = boxX + (xValue - minX) * xScale;
                        y = boxY + boxHeight - (yValue - minY) * yScale;
                        attrs.push({
                            x: x,
                            y: y
                        });
    
                        me.items.push({
                            series: me,
                            value: [xValue, yValue],
                            point: [x, y],
                            storeItem: record
                        });
    
                        // When resizing, reset before animating
                        if (chart.animate && chart.resizing) {
                            sprite = group.getAt(i);
                            if (sprite) {
                                me.resetPoint(sprite);
                                if (enableShadows) {
                                    me.resetShadow(sprite);
                                }
                            }
                        }
                    }
                    return attrs;
                }
            });
    
            Ext.chart.axis.Category.override({
                group: false,
                
                // @private creates an array of labels to be used when rendering.
                setLabels: function () {
                    var store = this.chart.getChartStore(),
                        data = store.data.items,
                        d, dLen, record,
                        fields = this.fields,
                        ln = fields.length,
                        labels,
                        name,
                        i;
    
                    labels = this.labels = [];
                    for (d = 0, dLen = data.length; d < dLen; d++) {
                        record = data[d];
                        for (i = 0; i < ln; i++) {
                            name = record.get(fields[i]);
                            
                            if (Ext.Array.indexOf(labels, name) > -1) {
                                if (!this.group) { // add a label only if it is unique
                                    //<debug>
                                    Ext.log.warn('Duplicate category in axis, ' + name);
                                    //</debug>
    
                                    labels.push(name);
                                }
                            } else {
                                labels.push(name);
                            }
                        }
                    }
                },
    
                // @private calculates labels positions and marker positions for rendering.
                applyData: function() {
                    this.callParent();
                    this.setLabels();
                    var count = this.group ? this.labels.length : this.chart.getChartStore().getCount();
    
                    return {
                        from: 0,
                        to: count - 1,
                        power: 1,
                        step: 1,
                        steps: count - 1
                    };
                }
            });
        </script>
    
        <%-- End of Overrides --%>
    
        <script>
            Ext.onReady(function () {
                Ext.create("Ext.chart.Chart", {
                    renderTo: Ext.getBody(),
                    height: 400,
                    width: 400,
                    store: {
                        fields: ["category", "value"],
                        data: [{
                            category: "category 1",
                            value: 20
                        }, {
                            category: "category 2",
                            value: 50
                        }, {
                            category: "category 1",
                            value: 5
                        }, {
                            category: "category 2",
                            value: 70
                        }]
                    },
                    axes: [{
                        type: "category",
                        position: "bottom",
                        title: "Category",
                        fields: ["category"],
                        group: true
                    }, {
                        type: "numeric",
                        position: "left",
                        title: "Value",
                        fields: ["value"]
                    }],
                    series: [{
                        type: "scatter",
                        xField: "category",
                        yField: "value",
                        groupXValues: true
                    }]
                });
            });
        </script>
    </head>
    <body>
    </body>
    </html>
    It produces:
    Solution.JPG
    Ext.NET - ASP.NET for Ext JS
    MVC and WebForms
    Examples | Twitter

  2. #2
    Sencha - Support Team
    Join Date
    Feb 2013
    Location
    California
    Posts
    3,854
    Vote Rating
    66
    Gary Schlosberg has a spectacular aura about Gary Schlosberg has a spectacular aura about Gary Schlosberg has a spectacular aura about

      0  

    Default


    Thanks for sharing with the community!

  3. #3
    Sencha User
    Join Date
    Apr 2010
    Location
    Houston, TX, USA
    Posts
    25
    Vote Rating
    2
    keithhackworth is on a distinguished road

      0  

    Default


    This is exactly what I was looking for. Thank you!!! It's pretty bad the chart can't do this by default.

  4. #4
    Sencha User Daniil's Avatar
    Join Date
    Jun 2010
    Location
    Saint-Petersburg, Russia
    Posts
    975
    Vote Rating
    112
    Daniil is a name known to all Daniil is a name known to all Daniil is a name known to all Daniil is a name known to all Daniil is a name known to all Daniil is a name known to all

      1  

    Default


    Thank you for the feedback. I am so glad it helped one person, at least
    Ext.NET - ASP.NET for Ext JS
    MVC and WebForms
    Examples | Twitter

  5. #5
    Sencha User
    Join Date
    Apr 2010
    Location
    Houston, TX, USA
    Posts
    25
    Vote Rating
    2
    keithhackworth is on a distinguished road

      2  

    Default


    I made one minor tweak to your code:
    Code:
                        if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {
                            if (me.groupYValues) {
                                indexInCache = cacheYValues.indexOf(yValue);
    
                                if (indexInCache > -1) {
                                    yValue = indexInCache;
                                } else {
                                    cacheYValues.push(yValue);
                                    yValue = cacheYValues.indexOf(yValue);
                                }
                            } else {
                                yValue = i;
                            }
                        }
    This needs to be done to the x axis as well.

    One thing to note: This works really well as long as the grouping category you're using doesn't have a null value. If it is null, then it skips those, and the indexing is all off and data gets plotted where it shouldn't be.

    Thank you!
    Keith

  6. #6
    Sencha User Daniil's Avatar
    Join Date
    Jun 2010
    Location
    Saint-Petersburg, Russia
    Posts
    975
    Vote Rating
    112
    Daniil is a name known to all Daniil is a name known to all Daniil is a name known to all Daniil is a name known to all Daniil is a name known to all Daniil is a name known to all

      0  

    Default


    Yes, I didn't claim my solution is the final one It was rather to demonstrate a possibility and to start a discussion if someone is really interested on implement it.

    So, your input is really appreciated. Thank you.
    Ext.NET - ASP.NET for Ext JS
    MVC and WebForms
    Examples | Twitter