You found a bug! We've classified it as EXTJSIV-6493 . We encourage you to continue the discussion and to find an acceptable workaround while we work on a permanent fix.
  1. #1
    Sencha User Daniil's Avatar
    Join Date
    Jun 2010
    Location
    Saint-Petersburg, Russia
    Posts
    678
    Vote Rating
    62
    Daniil is a jewel in the rough Daniil is a jewel in the rough Daniil is a jewel in the rough Daniil is a jewel in the rough

      1  

    Default [4.1.0] Chart Axis majorTickSteps appears to be not consistent

    [4.1.0] Chart Axis majorTickSteps appears to be not consistent


    REQUIRED INFORMATION

    Ext version tested:
    • Ext 4.1.0
    Browser versions tested against:
    • Any
    DOCTYPE tested against:
    • Any
    Description:
    • The API docs article tells the following about the majorTickSteps config option
    If minimum and maximum are specified it forces the number of major ticks to the specified value. If a number of major ticks is forced, it wont search for pretty numbers at the ticks.
    Here is the link to that article.
    http://docs.sencha.com/ext-js/4-1/#!/api/Ext.chart.axis.Axis-cfg-majorTickSteps
    • The problem is the fact that I can't get it work as expected, please refer the example to reproduce the problem.
    Steps to reproduce the problem:
    • Just run the page
    The result that was expected:
    • 15 ticks between the minimum and maximum of the axis.
    • The 60 step between these ticks: (1480 - 440) / (15 + 1) = 60
    The result that occurs instead:
    • There are 15 ticks as expected.
    • But the step is about 55.38.
    Test Case:

    Code:
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>majorTickSteps Bug</title>
    
        <link type="text/css" rel="stylesheet" href="../resources/css/ext-all.css" />
        
        <script type="text/javascript" src="../ext-all.js"></script>
    
        <script type="text/javascript">
            Ext.onReady(function () {
                Ext.create("Ext.chart.Chart", {
                    renderTo : Ext.getBody(),
                    height   : 400,
                    width    : 800,
                    axes : [{
                        type           : "Numeric",
                        position       : "bottom",
                        title          : "Time",
                        fields         : ["time"],
                        maximum        : 1440,
                        minimum        : 480,
                        majorTickSteps : 15
                    }],
                    store : {
                        model : Ext.define(Ext.id(), {
                            extend : "Ext.data.Model",
                            fields : [{
                                name : "time"
                            }]
                        }),
                        autoLoad : true,
                        proxy    : {
                            data : [],
                            type : "memory"
                        }
                    }
                });
            });
        </script>
    </head>
    <body>
    
    </body>
    </html>
    HELPFUL INFORMATION

    Debugging already done:
    • The problem appears to be in the calcEnds of the Ext.chart.axis.Axis class or/and the snapEnds function of the Ext.draw.Draw class. Here is the code of the calcEnds function. I have highlighted the related code.
    Code:
    calcEnds: function () {
        var me = this,
            fields = me.fields,
            range = me.getRange(),
            min = range.min,
            max = range.max,
            outfrom, outto, out;
    
        out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ? (me.majorTickSteps + 1) : me.steps, (me.majorTickSteps === false));
        outfrom = out.from;
        outto = out.to;
    
        if (me.forceMinMax) {
            if (!isNaN(max)) {
                out.to = max;
            }
            if (!isNaN(min)) {
                out.from = min;
            }
        }
        if (!isNaN(me.maximum)) {
            //TODO(nico) users are responsible for their own minimum/maximum values set.
            //Clipping should be added to remove lines in the chart which are below the axis.
            out.to = me.maximum;
        }
        if (!isNaN(me.minimum)) {
            //TODO(nico) users are responsible for their own minimum/maximum values set.
            //Clipping should be added to remove lines in the chart which are below the axis.
            out.from = me.minimum;
        }
    
        //Adjust after adjusting minimum and maximum
        out.step = (out.to - out.from) / (outto - outfrom) * out.step;
    
        if (me.adjustMaximumByMajorUnit) {
            out.to = Math.ceil(out.to / out.step) * out.step;
            out.steps = (out.to - out.from) / out.step;
        }
    
        if (me.adjustMinimumByMajorUnit) {
            out.from = Math.floor(out.from / out.step) * out.step;
            out.steps = (out.to - out.from) / out.step;
        }
    
        me.prevMin = min == max ? 0 : min;
        me.prevMax = max;
        return out;
    }
    • The Ext.draw.Draw.snapEnds returns the object with "from : 400". I can't understand why not 480, there is too hard logic to understand quicly. And it also returns the expected "step : 60".
    • Then the step is adjusted here:
    Code:
    //Adjust after adjusting minimum and maximum
    out.step = (out.to - out.from) / (outto - outfrom) * out.step;
    and it becomes ~ 55.38.

    Possible fix:
    • not provided

  2. #2
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    St. Louis, MO
    Posts
    33,599
    Vote Rating
    434
    mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of

      0  

    Default


    Thanks for the report.

    May I suggest one change in your code. Instead of defining the model like you are, just use the fields config in your store. This will create an implicit model for you making your code cleaner letting the framework handle it.
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Forum Manager
    ________________
    http://www.JSONPLint.com - Source to lint your JSONP!

    Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
    https://github.com/mitchellsimoens

    Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/

    Need more help with your app? Hire Sencha Services services@sencha.com

    Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is almost in print!

    When posting code, please use BBCode's CODE tags.

  3. #3
    Sencha User Daniil's Avatar
    Join Date
    Jun 2010
    Location
    Saint-Petersburg, Russia
    Posts
    678
    Vote Rating
    62
    Daniil is a jewel in the rough Daniil is a jewel in the rough Daniil is a jewel in the rough Daniil is a jewel in the rough

      0  

    Default


    Quote Originally Posted by mitchellsimoens View Post
    Thanks for the report.
    Thanks for the update, Mitchell. And, certainly, for opening a bug ticket.

    By the way, the following thread appears to be related.
    http://www.sencha.com/forum/showthread.php?136795

    Quote Originally Posted by mitchellsimoens View Post
    May I suggest one change in your code. Instead of defining the model like you are, just use the fields config in your store. This will create an implicit model for you making your code cleaner letting the framework handle it.
    Thanks for the advice, much appreciate.

    Sure, I could use it for such simple examples as my one and I will in the future.

  4. #4
    Sencha User
    Join Date
    Apr 2010
    Posts
    48
    Vote Rating
    0
    whippersnapper is on a distinguished road

      0  

    Default


    @mitchellsimoens

    Has this been resolved?

  5. #5
    Sencha User
    Join Date
    Mar 2013
    Posts
    1
    Vote Rating
    0
    j_torrei is on a distinguished road

      0  

    Default


    Has this been resolved?

  6. #6
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    St. Louis, MO
    Posts
    33,599
    Vote Rating
    434
    mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of mitchellsimoens has much to be proud of

      0  

    Default


    The status above updates every night. Once the bug ticket has been marked as closed it will reflect it here that night.
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Forum Manager
    ________________
    http://www.JSONPLint.com - Source to lint your JSONP!

    Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
    https://github.com/mitchellsimoens

    Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/

    Need more help with your app? Hire Sencha Services services@sencha.com

    Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is almost in print!

    When posting code, please use BBCode's CODE tags.

  7. #7
    Sencha User
    Join Date
    Jan 2012
    Posts
    1
    Vote Rating
    0
    jode is on a distinguished road

      0  

    Default A particular SOLUTION

    A particular SOLUTION


    Hi everybody,

    "SOLVED". My solution to set a specific number of ticks was:

    1. Add method "drawAxis" in my axes and then add bold lines.

    Code:
      axes: [
                       {
                           title: ...,
                           drawAxis: function(init) {
                                var me = this,
                                i, j,
                                x = me.x,
                                y = me.y,
                                gutterX = me.chart.maxGutter[0],
                                gutterY = me.chart.maxGutter[1],
                                dashSize = me.dashSize,
                                subDashesX = me.minorTickSteps || 0,
                                subDashesY = me.minorTickSteps || 0,
                                length = me.length,
                                position = me.position,
                                inflections = [],
                                calcLabels = false,
                                stepCalcs = me.applyData(),
                                step = stepCalcs.step,
                                steps = stepCalcs.steps,
                                from = stepCalcs.from,
                                to = stepCalcs.to,
                                trueLength,
                                currentX,
                                currentY,
                                path,
                                prev,
                                dashesX,
                                dashesY,
                                delta;
    
    
                            //If no steps are specified
                            //then don't draw the axis. This generally happens
                            //when an empty store.
                            if (me.hidden || isNaN(step) || (from == to)) {
                                return;
                            }
    
                           step = 128;  // In my case, I want ticks every 128
                           steps = me.prevMax/step; // My data is a multiple of 128
    
                           me.from = stepCalcs.from; // minimum                        me.to = stepCalcs.to; // maximum
                            if (position == 'left' || position == 'right') {
                                currentX = Math.floor(x) + 0.5;
                                path = ["M", currentX, y, "l", 0, -length];
                                trueLength = length - (gutterY * 2);
                            }
                            else {
                                currentY = Math.floor(y) + 0.5;
                                path = ["M", x, currentY, "l", length, 0];
                                trueLength = length - (gutterX * 2);
                            }
                            
                            delta = trueLength / (steps || 1);
                            dashesX = Math.max(subDashesX +1, 0);
                            dashesY = Math.max(subDashesY +1, 0);
                            
                            if (me.type == 'Numeric' || me.type == 'Time') {
                                calcLabels = true;
                                me.labels = [stepCalcs.from];
                            }
                            if (position == 'right' || position == 'left') {
                                currentY = y - gutterY;
                                currentX = x - ((position == 'left') * dashSize * 2);
                                while (currentY >= y - gutterY - trueLength) {
                                    path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
                                    if (currentY != y - gutterY) {
                                        for (i = 1; i < dashesY; i++) {
                                            path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
                                        }
                                    }
                                    inflections.push([ Math.floor(x), Math.floor(currentY) ]);
                                    currentY -= delta;
                                    if (calcLabels) {
                                        me.labels.push(me.labels[me.labels.length -1] + step);
                                    }
                                    if (delta === 0) {
                                        break;
                                    }
                                }
                                if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
                                    path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
                                    for (i = 1; i < dashesY; i++) {
                                        path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
                                    }
                                    inflections.push([ Math.floor(x), Math.floor(currentY) ]);
                                    if (calcLabels) {
                                        me.labels.push(me.labels[me.labels.length -1] + step);
                                    }
                                }
                            } else {                    
                                currentX = x + gutterX;
                                currentY = y - ((position == 'top') * dashSize * 2);
                                while (currentX <= x + gutterX + trueLength) {
                                    path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
                                    if (currentX != x + gutterX) {
                                        for (i = 1; i < dashesX; i++) {
                                            path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
                                        }
                                    }
                                    inflections.push([ Math.floor(currentX), Math.floor(y) ]);
                                    currentX += delta;
                                    if (calcLabels) {
                                        me.labels.push(me.labels[me.labels.length -1] + step);
                                    }
                                    if (delta === 0) {
                                        break;
                                    }
                                }
                                if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
                                    path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
                                    for (i = 1; i < dashesX; i++) {
                                        path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
                                    }
                                    inflections.push([ Math.floor(currentX), Math.floor(y) ]);
                                    if (calcLabels) {
                                        me.labels.push(me.labels[me.labels.length -1] + step);
                                    }
                                }
                            }
                            if (!me.axis) {
                                me.axis = me.chart.surface.add(Ext.apply({
                                    type: 'path',
                                    path: path
                                }, me.axisStyle));
                            }
                            me.axis.setAttributes({
                                path: path
                            }, true);
                            me.inflections = inflections;
                            if (!init && me.grid) {
                                me.drawGrid();
                            }
                            me.axisBBox = me.axis.getBBox();
                            me.drawLabel();
                           }
    
                   ]