text: /**
   * @class Ext.chart.axis.layout.Discrete
   * @extends Ext.chart.axis.layout.Layout
   *
   * Simple processor for data that cannot be interpolated.
   */
  Ext.define('Ext.chart.axis.layout.Discrete', {
      extend: 'Ext.chart.axis.layout.Layout',
      alias: 'axisLayout.discrete',
      isDiscrete: true,
  
      processData: function () {
          var me = this,
              axis = me.getAxis(),
              seriesList = axis.boundSeries,
              direction = axis.getDirection(),
              i, ln, series;
          this.labels = [];
          this.labelMap = {};
          for (i = 0, ln = seriesList.length; i < ln; i++) {
              series = seriesList[i];
              if (series['get' + direction + 'Axis']() === axis) {
                  series['coordinate' + direction]();
              }
          }
          // About the labels on Category axes (aka. axes with a Discrete layout)...
          //
          // When the data set from the store changes, series.processData() is called, which does its thing
          // at the series level and then calls series.updateLabelData() to update the labels in the sprites
          // that belong to the series. At the same time, series.processData() calls axis.processData(), which
          // also does its thing but at the axis level, and also needs to update the labels for the sprite(s)
          // that belong to the axis. This is not that simple, however. So how are the axis labels rendered?
          // First, axis.sprite.Axis.render() calls renderLabels() which obtains the majorTicks from the 
          // axis.layout and iterate() through them. The majorTicks are an object returned by snapEnds() below
          // which provides a getLabel() function that returns the label from the axis.layoutContext.data array.
          // So now the question is: how are the labels transferred from the axis.layout to the axis.layoutContext?
          // The easy response is: it's in calculateLayout() below. The issue is to call calculateLayout() because
          // it takes in an axis.layoutContext that can only be created in axis.sprite.Axis.doLayout(), which is 
          // a private "updater" function that is called by all the sprite's "triggers". Of course, we don't
          // want to call doLayout() directly from here, so instead we update the sprite's data attribute, which 
          // sets the trigger which calls doLayout() which calls calculateLayout() etc...
          // Note that the sprite's data attribute could be set to any value and it would still result in the  
          // trigger we need. For consistency, however, it is set to the labels.
          axis.getSprites()[0].setAttributes({data: this.labels});
          this.fireEvent('datachange', this.labels);
      },
  
      /**
       * @inheritdoc
       */
      calculateLayout: function (context) {
          context.data = this.labels;
          this.callParent([context]);
      },
  
      /**
       * @inheritdoc
       */
      calculateMajorTicks: function (context) {
          var me = this,
              attr = context.attr,
              data = context.data,
              range = attr.max - attr.min,
              zoom = range / Math.max(1, attr.length) * (attr.visibleMax - attr.visibleMin),
              viewMin = attr.min + range * attr.visibleMin,
              viewMax = attr.min + range * attr.visibleMax,
              estStepSize = attr.estStepSize * zoom;
  
          var out = me.snapEnds(context, Math.max(0, attr.min), Math.min(attr.max, data.length - 1), estStepSize);
          if (out) {
              me.trimByRange(context, out, viewMin, viewMax);
              context.majorTicks = out;
          }
      },
  
      /**
       * @inheritdoc
       */
      snapEnds: function (context, min, max, estStepSize) {
          estStepSize = Math.ceil(estStepSize);
          var steps = Math.floor((max - min) / estStepSize),
              data = context.data;
          return {
              min: min,
              max: max,
              from: min,
              to: steps * estStepSize + min,
              step: estStepSize,
              steps: steps,
              unit: 1,
              getLabel: function (current) {
                  return data[this.from + this.step * current];
              },
              get: function (current) {
                  return this.from + this.step * current;
              }
          };
      },
  
      /**
       * @inheritdoc
       */
      trimByRange: function (context, out, trimMin, trimMax) {
          var unit = out.unit,
              beginIdx = Math.ceil((trimMin - out.from) / unit) * unit,
              endIdx = Math.floor((trimMax - out.from) / unit) * unit,
              begin = Math.max(0, Math.ceil(beginIdx / out.step)),
              end = Math.min(out.steps, Math.floor(endIdx / out.step));
  
          if (end < out.steps) {
              out.to = end;
          }
  
          if (out.max > trimMax) {
              out.max = out.to;
          }
  
          if (out.from < trimMin && out.step > 0) {
              out.from = out.from + begin * out.step * unit;
              while (out.from < trimMin) {
                  begin++;
                  out.from += out.step * unit;
              }
          }
  
          if (out.min < trimMin) {
              out.min = out.from;
          }
  
          out.steps = end - begin;
      },
  
      getCoordFor: function (value, field, idx, items) {
          this.labels.push(value);
          return this.labels.length - 1;
      }
  });