1. #1
    Sencha Premium Member
    Join Date
    Nov 2012
    Posts
    18
    Vote Rating
    1
    mpickell is on a distinguished road

      0  

    Default Answered: Charting issue with axis

    Answered: Charting issue with axis


    I am trying to create a two-line sparkline composite widget by adding two line series and a hidden numeric axis (Position.LEFT) to a chart.

    My understanding is that the NumericAxis is used to scale the min and max of the chart? I can't seem to get it to work.

    Here is how i'm building it:
    1. When the sparkline widget is created, I have an empty ListStore. Both ValueProviders from the model are added to the axis, and each LineSeries gets a valueprovider for its data.
    2. I trigger async calls via RPC (one per series) to get that line's data. The model is updated as these calls return. So, if LineSeries 1's async call returns, then LineSeries 1 is drawn and LinesSeries 2 has nulls in the model until its data returns and updates the model. The X axis is based on Dates, but it appears i do not need to add an axis for that in the chart.
    3. As the data is returning and being updated in the ListStore, I run a calculation across the data and find the max and min values, which are then set into the NumericAxis.
    It is this last part that doesn't seem to do anything. I'm expecting the lines on the chart to use the full height, but it seems that the NumericAxis has a range that i'm not setting. The values set are generally some low value (say, zero) to some high value like 250. The NumericAxis appears to be putting zero at (chart.getHeight/2) no matter what i do, and its scale is larger than my max value so the lines are using maybe 1/3 of the height.

    I've attached a screen shot. i'm assuming that i am using the chart incorrectly, so any guidance would be helpful. I have un-hidden the axis for these screenshots.

    badSparklines-GXT.jpg

    The code i'm using to create the axis is simple:

    Code:
            axis.setPosition(Position.LEFT);
            axis.addField(dataAccess.sparkline1Value());
            axis.addField(dataAccess.sparkline2Value());
            axis.setHidden(false);
    Then i set the min and max when the data in the ListStore is replaced. I then tell the chart to redraw.

  2. Yes, I can confirm that the snapEnds code is a little too aggressive about picking 'pretty' numbers. I think we've got another bug filed somewhere that is also tracking this - I condense the two to keep everyone better updated.

    I'll add this to the bug thread when I better verify it, but I believe it may be possible to workaround this by subclassing NumericAxis and overriding calcEnds(). All the fields involved appear to be protected, so that should make it easier to kick them into place.

  3. #2
    Sencha Premium Member
    Join Date
    Nov 2012
    Posts
    18
    Vote Rating
    1
    mpickell is on a distinguished road

      0  

    Default any help?

    any help?


    Anyone have any ideas? links to better documentation the chart and axis interactions?

  4. #3
    Sencha Premium Member
    Join Date
    Nov 2012
    Posts
    18
    Vote Rating
    1
    mpickell is on a distinguished road

      0  

    Default related issue

    related issue


    This appears to be related to a bug... so i'll track that issue instead of here.

    http://www.sencha.com/forum/showthread.php?247814-In-GXT-3.0.2b-NumericAxis.setMinimum()-doesn-t-work-minimum-is-always-0.



  5. #4
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Answers
    109
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    Yes, I can confirm that the snapEnds code is a little too aggressive about picking 'pretty' numbers. I think we've got another bug filed somewhere that is also tracking this - I condense the two to keep everyone better updated.

    I'll add this to the bug thread when I better verify it, but I believe it may be possible to workaround this by subclassing NumericAxis and overriding calcEnds(). All the fields involved appear to be protected, so that should make it easier to kick them into place.

  6. #5
    Sencha Premium Member
    Join Date
    Nov 2012
    Posts
    18
    Vote Rating
    1
    mpickell is on a distinguished road

      0  

    Default work around

    work around


    Colin,

    That is what i ended up doing. For my case, i'm specifically setting the max and min so i just subclassed NumericAxis and replaced the snapEnds method with my own that sets FROM and TO directly to what I want them to be.

    That seems to be working.

    Thanks,

    -Matt

  7. #6
    Sencha - GXT Dev Team
    Join Date
    Feb 2009
    Location
    Minnesota
    Posts
    2,734
    Answers
    109
    Vote Rating
    90
    Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light Colin Alworth is a glorious beacon of light

      0  

    Default


    Thanks for the confirmation that this works - would it be reasonable to ask you to share this workaround in the bug thread for others to see?

  8. #7
    Sencha Premium Member
    Join Date
    Nov 2012
    Posts
    18
    Vote Rating
    1
    mpickell is on a distinguished road

      0  

    Default work around

    work around


    no problem.. but it is a total hack without regard for what snapEnds was meant to do. I am calculating the exact range i want for the charts, so i set it directly.

    the snapEnds method is private so i just replace it completely here. the calcEnds method is identical to the superclass implementation other than calling the new "mySnapEnds" method.

    Code:
        public static class MyAxis<M> extends NumericAxis<M> {
            /**
             * Calculate the start, end and step points.
             */
            public void calcEnds() {
                double min = Double.isNaN(minimum) ? Double.POSITIVE_INFINITY : minimum;
                double max = Double.isNaN(maximum) ? Double.NEGATIVE_INFINITY : maximum;
                ListStore<M> store = chart.getCurrentStore();
                List<Series<M>> series = chart.getSeries();
                Set<Integer> excluded = null;
    
    
                if (getFields().size() == 0) {
                    this.from = 0;
                    this.to = 0;
                    this.power = 0;
                    this.step = 0;
                    this.steps = 1;
                    return;
                }
                for (int i = 0; i < series.size(); i++) {
                    if (series.get(i) instanceof BarSeries && ((BarSeries<?>) series.get(i)).isStacked()
                            && ((BarSeries<?>) series.get(i)).getYAxisPosition() == this.position) {
                        excluded = ((BarSeries<?>) series.get(i)).getExcluded();
                        break;
                    } else if (series.get(i) instanceof AreaSeries
                            && ((AreaSeries<?>) series.get(i)).getYAxisPosition() == this.position) {
                        excluded = ((AreaSeries<?>) series.get(i)).getExcluded();
                        break;
                    }
                }
    
    
                if (excluded != null && excluded.size() != getFields().size()) {
                    for (int i = 0; i < store.size(); i++) {
                        double value = 0;
                        if (Double.isInfinite(min)) {
                            min = 0;
                        }
                        for (int j = 0; j < getFields().size(); j++) {
                            if (excluded.contains(j)) {
                                continue;
                            }
                            Number fieldValue = getFields().get(j).getValue(store.get(i));
                            if (fieldValue != null && !Double.isNaN(fieldValue.doubleValue())) {
                                value += fieldValue.doubleValue();
                            }
                        }
                        max = Math.max(max, value);
                        min = Math.min(min, value);
                    }
                } else {
                    for (int i = 0; i < store.size(); i++) {
                        for (ValueProvider<? super M, ? extends Number> field : getFields()) {
                            Number object = field.getValue(store.get(i));
                            double value = Double.NaN;
                            if (object != null) {
                                value = object.doubleValue();
                            }
                            if (!Double.isNaN(value)) {
                                max = Math.max(max, value);
                                min = Math.min(min, value);
                            }
                        }
                    }
                }
    
    
                if (!Double.isNaN(maximum)) {
                    max = Math.min(max, maximum);
                }
                if (!Double.isNaN(minimum)) {
                    min = Math.max(min, minimum);
                }
                if (min == 0 && max == 0) {
                    this.from = 0;
                    this.to = 0;
                    this.power = 0;
                    this.step = 0;
                    this.steps = 1;
                } else {
    // CALL NEW SnapEnds method... 
                    if (stepsMax >= 0) {
                        mySnapEnds(min, max, stepsMax);
                    } else if (interval >= 0) {
                        mySnapEnds(min, max, (max - min) / interval + 1);
                    } else {
                        mySnapEnds(min, max, 10);
                    }
                    if (this.adjustMaximumByMajorUnit) {
                        this.to = Math.ceil(this.to / this.step) * this.step;
                        this.steps = (int) ((this.to - this.from) / this.step);
                    }
                    if (this.adjustMinimumByMajorUnit) {
                        this.from = Math.floor(this.from / this.step) * this.step;
                        this.steps = (int) ((this.to - this.from) / this.step);
                    }
                }
            }
    
    
            /**
             * TODO: remove this class when EXTGWT-2560 is fixed see
             * http://www.sencha.com/forum/showthread.php?247814-In-GXT
             * -3.0.2b-NumericAxis.setMinimum()-doesn-t-work-minimum-is-always-0.
             * 
             * @param from
             * @param to
             * @param stepsMax
             */
            private void mySnapEnds(double from, double to, double stepsMax) {
                this.from = from;
                this.to = to;
                this.power = 0;
                this.step = 0;
                this.steps = 1;
            }
        }

Thread Participants: 1