text: /**
   * @class Ext.chart.interactions.CrossZoom
   * @extends Ext.chart.interactions.Abstract
   *
   * The CrossZoom interaction allows the user to zoom in on a selected area of the chart.
   *
   *     @example
   *     Ext.create({
   *         xtype: 'cartesian',
   *         renderTo: Ext.getBody(),
   *         width: 600,
   *         height: 400,
   *         insetPadding: 40,
   *         interactions: 'crosszoom',
   *         store: {
   *             fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
   *             data: [{
   *                 'name': 'metric one',
   *                 'data1': 10,
   *                 'data2': 12,
   *                 'data3': 14,
   *                 'data4': 8,
   *                 'data5': 13
   *             }, {
   *                 'name': 'metric two',
   *                 'data1': 7,
   *                 'data2': 8,
   *                 'data3': 16,
   *                 'data4': 10,
   *                 'data5': 3
   *             }, {
   *                 'name': 'metric three',
   *                 'data1': 5,
   *                 'data2': 2,
   *                 'data3': 14,
   *                 'data4': 12,
   *                 'data5': 7
   *             }, {
   *                 'name': 'metric four',
   *                 'data1': 2,
   *                 'data2': 14,
   *                 'data3': 6,
   *                 'data4': 1,
   *                 'data5': 23
   *             }, {
   *                 'name': 'metric five',
   *                 'data1': 27,
   *                 'data2': 38,
   *                 'data3': 36,
   *                 'data4': 13,
   *                 'data5': 33
   *             }]
   *         },
   *         axes: [{
   *             type: 'numeric',
   *             position: 'left',
   *             fields: ['data1'],
   *             title: {
   *                 text: 'Sample Values',
   *                 fontSize: 15
   *             },
   *             grid: true,
   *             minimum: 0
   *         }, {
   *             type: 'category',
   *             position: 'bottom',
   *             fields: ['name'],
   *             title: {
   *                 text: 'Sample Values',
   *                 fontSize: 15
   *             }
   *         }],
   *         series: [{
   *             type: 'line',
   *             highlight: {
   *                 size: 7,
   *                 radius: 7
   *             },
   *             style: {
   *                 stroke: 'rgb(143,203,203)'
   *             },
   *             xField: 'name',
   *             yField: 'data1',
   *             marker: {
   *                 type: 'path',
   *                 path: ['M', - 2, 0, 0, 2, 2, 0, 0, - 2, 'Z'],
   *                 stroke: 'blue',
   *                 lineWidth: 0
   *             }
   *         }, {
   *             type: 'line',
   *             highlight: {
   *                 size: 7,
   *                 radius: 7
   *             },
   *             fill: true,
   *             xField: 'name',
   *             yField: 'data3',
   *             marker: {
   *                 type: 'circle',
   *                 radius: 4,
   *                 lineWidth: 0
   *             }
   *         }]
   *     });
   */
  Ext.define('Ext.chart.interactions.CrossZoom', {
  
      extend: 'Ext.chart.interactions.Abstract',
  
      type: 'crosszoom',
      alias: 'interaction.crosszoom',
  
      isCrossZoom: true,
  
      config: {
          /**
           * @cfg {Object/Array} axes
           * Specifies which axes should be made navigable. The config value can take the following formats:
           *
           * - An Object whose keys correspond to the {@link Ext.chart.axis.Axis#position position} of each
           *   axis that should be made navigable. Each key's value can either be an Object with further
           *   configuration options for each axis or simply `true` for a default set of options.
           *       {
           *           type: 'crosszoom',
           *           axes: {
           *               left: {
           *                   maxZoom: 5,
           *                   allowPan: false
           *               },
           *               bottom: true
           *           }
           *       }
           *
           *   If using the full Object form, the following options can be specified for each axis:
           *
           *   - minZoom (Number) A minimum zoom level for the axis. Defaults to `1` which is its natural size.
           *   - maxZoom (Number) A maximum zoom level for the axis. Defaults to `10`.
           *   - startZoom (Number) A starting zoom level for the axis. Defaults to `1`.
           *   - allowZoom (Boolean) Whether zooming is allowed for the axis. Defaults to `true`.
           *   - allowPan (Boolean) Whether panning is allowed for the axis. Defaults to `true`.
           *   - startPan (Boolean) A starting panning offset for the axis. Defaults to `0`.
           *
           * - An Array of strings, each one corresponding to the {@link Ext.chart.axis.Axis#position position}
           *   of an axis that should be made navigable. The default options will be used for each named axis.
           *
           *       {
           *           type: 'crosszoom',
           *           axes: ['left', 'bottom']
           *       }
           *
           * If the `axes` config is not specified, it will default to making all axes navigable with the
           * default axis options.
           */
          axes: true,
          
          gestures: {
              dragstart: 'onGestureStart',
              drag: 'onGesture',
              dragend: 'onGestureEnd',
              dblclick: 'onDoubleTap'
          },
  
          undoButton: {}
      },
  
      stopAnimationBeforeSync: false,
  
      zoomAnimationInProgress: false,
  
      constructor: function () {
          this.callParent(arguments);
          this.zoomHistory = [];
      },
  
      applyAxes: function (axesConfig) {
          var result = {};
  
          if (axesConfig === true) {
              return {
                  top: {},
                  right: {},
                  bottom: {},
                  left: {}
              };
          } else if (Ext.isArray(axesConfig)) {
              // array of axis names - translate to full object form
              result = {};
              Ext.each(axesConfig, function (axis) {
                  result[axis] = {};
              });
          } else if (Ext.isObject(axesConfig)) {
              Ext.iterate(axesConfig, function (key, val) {
                  // axis name with `true` value -> translate to object
                  if (val === true) {
                      result[key] = {};
                  } else if (val !== false) {
                      result[key] = val;
                  }
              });
          }
          return result;
      },
  
      applyUndoButton: function (button, oldButton) {
          var me = this;
  
          if (oldButton) {
              oldButton.destroy();
          }
  
          if (button) {
              return Ext.create('Ext.Button', Ext.apply({
                  cls: [],
                  text: 'Undo Zoom',
                  disabled: true,
                  handler: function () {
                      me.undoZoom();
                  }
              }, button));
          }
      },
  
      getSurface: function () {
          return this.getChart() && this.getChart().getSurface('main');
      },
      
      setSeriesOpacity: function (opacity) {
          var surface = this.getChart() && this.getChart().getSurface('series');
          if (surface) {
              surface.element.setStyle('opacity', opacity);
          }
      },
  
      onGestureStart: function (e) {
          var me = this,
              chart = me.getChart(),
              surface = me.getSurface(),
              rect = chart.getInnerRect(),
              innerPadding = chart.getInnerPadding(),
              minX = innerPadding.left,
              maxX = minX + rect[2],
              minY = innerPadding.top,
              maxY = minY + rect[3],
              xy = chart.getEventXY(e),
              x = xy[0],
              y = xy[1];
  
          if (me.zoomAnimationInProgress) {
              return;
          }
          if (x > minX && x < maxX && y > minY && y < maxY) {
              me.gestureEvent = 'drag';
              me.lockEvents(me.gestureEvent);
              me.startX = x;
              me.startY = y;
              me.selectionRect = surface.add({
                  type: 'rect',
                  globalAlpha: 0.5,
                  fillStyle: 'rgba(80,80,140,0.5)',
                  strokeStyle: 'rgba(80,80,140,1)',
                  lineWidth: 2,
                  x: x,
                  y: y,
                  width: 0,
                  height: 0,
                  zIndex: 10000
              });
              me.setSeriesOpacity(0.8);
              return false;
          }
      },
  
      onGesture: function (e) {
          var me = this;
          if (me.zoomAnimationInProgress) {
              return;
          }
          if (me.getLocks()[me.gestureEvent] === me) {
              var chart = me.getChart(),
                  surface = me.getSurface(),
                  rect = chart.getInnerRect(),
                  innerPadding = chart.getInnerPadding(),
                  minX = innerPadding.left,
                  maxX = minX + rect[2],
                  minY = innerPadding.top,
                  maxY = minY + rect[3],
                  xy = chart.getEventXY(e),
                  x = xy[0],
                  y = xy[1];
  
              if (x < minX) {
                  x = minX;
              } else if (x > maxX) {
                  x = maxX;
              }
              if (y < minY) {
                  y = minY;
              } else if (y > maxY) {
                  y = maxY;
              }
              me.selectionRect.setAttributes({
                  width: x - me.startX,
                  height: y - me.startY
              });
              if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
                  me.selectionRect.setAttributes({globalAlpha: 0.5});
              } else {
                  me.selectionRect.setAttributes({globalAlpha: 1});
              }
              surface.renderFrame();
              return false;
          }
      },
  
      onGestureEnd: function (e) {
          var me = this;
          if (me.zoomAnimationInProgress) {
              return;
          }
          if (me.getLocks()[me.gestureEvent] === me) {
              var chart = me.getChart(),
                  surface = me.getSurface(),
                  rect = chart.getInnerRect(),
                  innerPadding = chart.getInnerPadding(),
                  minX = innerPadding.left,
                  maxX = minX + rect[2],
                  minY = innerPadding.top,
                  maxY = minY + rect[3],
                  rectWidth = rect[2],
                  rectHeight = rect[3],
                  xy = chart.getEventXY(e),
                  x = xy[0],
                  y = xy[1];
  
              if (x < minX) {
                  x = minX;
              } else if (x > maxX) {
                  x = maxX;
              }
              if (y < minY) {
                  y = minY;
              } else if (y > maxY) {
                  y = maxY;
              }
              if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
                  surface.remove(me.selectionRect);
              } else {
                  me.zoomBy([
                      Math.min(me.startX, x) / rectWidth,
                      1 - Math.max(me.startY, y) / rectHeight,
                      Math.max(me.startX, x) / rectWidth,
                      1 - Math.min(me.startY, y) / rectHeight
                  ]);
  
                  me.selectionRect.setAttributes({
                      x: Math.min(me.startX, x),
                      y: Math.min(me.startY, y),
                      width: Math.abs(me.startX - x),
                      height: Math.abs(me.startY - y)
                  });
  
                  me.selectionRect.setAnimation(chart.getAnimation() || {duration: 0});
                  me.selectionRect.setAttributes({
                      globalAlpha: 0,
                      x: 0,
                      y: 0,
                      width: rectWidth,
                      height: rectHeight
                  });
  
                  me.zoomAnimationInProgress = true;
  
                  chart.suspendThicknessChanged();
                  me.selectionRect.fx.on('animationend', function () {
                      chart.resumeThicknessChanged();
  
                      surface.remove(me.selectionRect);
                      me.selectionRect = null;
  
                      me.zoomAnimationInProgress = false;
                  });
              }
  
              surface.renderFrame();
              me.sync();
              me.unlockEvents(me.gestureEvent);
              me.setSeriesOpacity(1.0);
  
              if (!me.zoomAnimationInProgress) {
                  surface.remove(me.selectionRect);
                  me.selectionRect = null;
              }
          }
      },
  
      zoomBy: function (rect) {
          var me = this,
              axisConfigs = me.getAxes(),
              chart = me.getChart(),
              axes = chart.getAxes(),
              isRtl = chart.getInherited().rtl,
              config,
              zoomMap = {},
              x1, x2;
  
          if (isRtl) {
              rect = rect.slice();
              x1 =  1 - rect[0];
              x2 =  1 - rect[2];
              rect[0] = Math.min(x1, x2);
              rect[2] = Math.max(x1, x2);
          }
          for (var i = 0; i < axes.length; i++) {
              var axis = axes[i];
              config = axisConfigs[axis.getPosition()];
              if (config && config.allowZoom !== false) {
                  var isSide = axis.isSide(),
                      oldRange = axis.getVisibleRange();
                  zoomMap[axis.getId()] = oldRange.slice(0);
                  if (!isSide) {
                      axis.setVisibleRange([
                          (oldRange[1] - oldRange[0]) * rect[0] + oldRange[0],
                          (oldRange[1] - oldRange[0]) * rect[2] + oldRange[0]
                      ]);
                  } else {
                      axis.setVisibleRange([
                          (oldRange[1] - oldRange[0]) * rect[1] + oldRange[0],
                          (oldRange[1] - oldRange[0]) * rect[3] + oldRange[0]
                      ]);
                  }
              }
          }
  
          me.zoomHistory.push(zoomMap);
          me.getUndoButton().setDisabled(false);
      },
  
      undoZoom: function () {
          var zoomMap = this.zoomHistory.pop(),
              axes = this.getChart().getAxes();
          if (zoomMap) {
              for (var i = 0; i < axes.length; i++) {
                  var axis = axes[i];
                  if (zoomMap[axis.getId()]) {
                      axis.setVisibleRange(zoomMap[axis.getId()]);
                  }
              }
          }
          this.getUndoButton().setDisabled(this.zoomHistory.length === 0);
          this.sync();
      },
  
      onDoubleTap: function (e) {
          this.undoZoom();
      },
  
      destroy: function () {
          this.setUndoButton(null);
          this.callParent(arguments);
      }
  
  });
  