text: /**
   * The Crosshair interaction allows the user to get precise values for a specific point on the chart.
   * The values are obtained by single-touch dragging on the chart.
   *
   *     @example
   *     Ext.create('Ext.Container', {
   *         renderTo: Ext.getBody(),
   *         width: 600,
   *         height: 400,
   *         layout: 'fit',
   *         items: {
   *             xtype: 'cartesian',
   *             innerPadding: 20,
   *             interactions: {
   *                 type: 'crosshair',
   *                 axes: {
   *                     left: {
   *                         label: {
   *                             fillStyle: 'white'
   *                         },
   *                         rect: {
   *                             fillStyle: 'brown',
   *                             radius: 6
   *                         }
   *                     },
   *                     bottom: {
   *                         label: {
   *                             fontSize: '14px',
   *                             fontWeight: 'bold'
   *                         }
   *                     }
   *                 },
   *                 lines: {
   *                     horizontal: {
   *                         strokeStyle: 'brown',
   *                         lineWidth: 2,
   *                         lineDash: [20, 2, 2, 2, 2, 2, 2, 2]
   *                     }
   *                 }
   *             },
   *             store: {
   *                 fields: ['name', 'data'],
   *                 data: [
   *                     {name: 'apple', data: 300},
   *                     {name: 'orange', data: 900},
   *                     {name: 'banana', data: 800},
   *                     {name: 'pear', data: 400},
   *                     {name: 'grape', data: 500}
   *                 ]
   *             },
   *             axes: [{
   *                 type: 'numeric',
   *                 position: 'left',
   *                 fields: ['data'],
   *                 title: {
   *                     text: 'Value',
   *                     fontSize: 15
   *                 },
   *                 grid: true,
   *                 label: {
   *                     rotationRads: -Math.PI / 4
   *                 }
   *             }, {
   *                 type: 'category',
   *                 position: 'bottom',
   *                 fields: ['name'],
   *                 title: {
   *                     text: 'Category',
   *                     fontSize: 15
   *                 }
   *             }],
   *             series: {
   *                 type: 'line',
   *                 style: {
   *                     strokeStyle: 'black'
   *                 },
   *                 xField: 'name',
   *                 yField: 'data',
   *                 marker: {
   *                     type: 'circle',
   *                     radius: 5,
   *                     fillStyle: 'lightblue'
   *                 }
   *             }
   *         }
   *     });
   */
  Ext.define('Ext.chart.interactions.Crosshair', {
  
      extend: 'Ext.chart.interactions.Abstract',
      requires: [
          'Ext.chart.grid.HorizontalGrid',
          'Ext.chart.grid.VerticalGrid',
          'Ext.chart.CartesianChart',
          'Ext.chart.axis.layout.Discrete'
      ],
  
      type: 'crosshair',
      alias: 'interaction.crosshair',
  
      config: {
          /**
           * @cfg {Object} axes
           * Specifies label text and label rect configs on per axis basis or as a single config for all axes.
           *
           *     {
           *         type: 'crosshair',
           *         axes: {
           *             label: { fillStyle: 'white' },
           *             rect: { fillStyle: 'maroon'}
           *         }
           *     }
           *
           * In case per axis configuration is used, an object with keys corresponding
           * to the {@link Ext.chart.axis.Axis#position position} must be provided.
           *
           *     {
           *         type: 'crosshair',
           *         axes: {
           *             left: {
           *                 label: { fillStyle: 'white' },
           *                 rect: {
           *                     fillStyle: 'maroon',
           *                     radius: 4
           *                 }
           *             },
           *             bottom: {
           *                 label: {
           *                     fontSize: '14px',
           *                     fontWeight: 'bold'
           *                 },
           *                 rect: { fillStyle: 'white' }
           *             }
           *         }
           *
           * If the `axes` config is not specified, the following defaults will be used:
           * - `label` will use values from the {@link Ext.chart.axis.Axis#label label} config.
           * - `rect` will use the 'white' fillStyle.
           */
          axes: {
              top: {label: {}, rect: {}},
              right: {label: {}, rect: {}},
              bottom: {label: {}, rect: {}},
              left: {label: {}, rect: {}}
          },
  
          /**
           * @cfg {Object} lines
           * Specifies attributes of horizontal and vertical lines that make up the crosshair.
           * If this config is missing, black dashed lines will be used.
           *
           *     {
           *         horizontal: {
           *             strokeStyle: 'red',
           *             lineDash: [] // solid line
           *         },
           *         vertical: {
           *             lineWidth: 2,
           *             lineDash: [15, 5, 5, 5]
           *         }
           *     }
           */
          lines: {
              horizontal: {
                  strokeStyle: 'black',
                  lineDash: [5, 5]
              },
              vertical: {
                  strokeStyle: 'black',
                  lineDash: [5, 5]
              }
          },
  
          /**
           * @cfg {String} gesture
           * Specifies which gesture should be used for starting/maintaining/ending the interaction.
           */
          gesture: 'drag'
      },
  
      applyAxes: function (axesConfig, oldAxesConfig) {
          return Ext.merge(oldAxesConfig || {}, axesConfig);
      },
  
      applyLines: function (linesConfig, oldLinesConfig) {
          return Ext.merge(oldLinesConfig || {}, linesConfig);
      },
  
      updateChart: function (chart) {
          if (chart && !chart.isCartesian) {
              Ext.raise("Crosshair interaction can only be used on cartesian charts.");
          }
          this.callParent(arguments);
      },
  
      getGestures: function () {
          var me = this,
              gestures = {},
              gesture = me.getGesture();
  
          gestures[gesture] = 'onGesture';
          gestures[gesture + 'start'] = 'onGestureStart';
          gestures[gesture + 'end'] = 'onGestureEnd';
          gestures[gesture + 'cancel'] = 'onGestureCancel';
          return gestures;
      },
  
      onGestureStart: function (e) {
          var me = this,
              chart = me.getChart(),
              axesTheme = chart.getTheme().getAxis(), axisTheme,
              surface = chart.getSurface('overlay'),
              rect = chart.getInnerRect(),
              chartWidth = rect[2],
              chartHeight = rect[3],
              xy = chart.getEventXY(e),
              x = xy[0],
              y = xy[1],
              axes = chart.getAxes(),
              axesConfig = me.getAxes(),
              linesConfig = me.getLines(),
              axis, axisSurface, axisRect, axisWidth, axisHeight, axisPosition, axisAlignment,
              axisLabel, axisLabelConfig, crosshairLabelConfig, tickPadding,
              axisSprite, attr, axisThickness, lineWidth, halfLineWidth,
              title, titleBBox,
              horizontalLineCfg, verticalLineCfg,
              i;
  
          e.claimGesture();
  
          if (x > 0 && x < chartWidth && y > 0 && y < chartHeight) {
              me.lockEvents(me.getGesture());
              horizontalLineCfg = Ext.apply({
                  xclass: 'Ext.chart.grid.HorizontalGrid',
                  x: 0,
                  y: y,
                  width: chartWidth
              }, linesConfig.horizontal);
              verticalLineCfg = Ext.apply({
                  xclass: 'Ext.chart.grid.VerticalGrid',
                  x: x,
                  y: 0,
                  height: chartHeight
              }, linesConfig.vertical);
              me.axesLabels = me.axesLabels || {};
              for (i = 0; i < axes.length; i++) {
                  axis = axes[i];
                  axisSurface = axis.getSurface();
                  axisRect = axisSurface.getRect();
                  axisSprite = axis.getSprites()[0];
                  axisWidth = axisRect[2];
                  axisHeight = axisRect[3];
                  axisPosition = axis.getPosition();
                  axisAlignment = axis.getAlignment();
                  title = axis.getTitle();
                  titleBBox = title && title.attr.text !== '' && title.getBBox();
                  attr = axisSprite.attr;
                  axisThickness = axisSprite.thickness;
                  lineWidth = attr.axisLine ? attr.lineWidth : 0;
                  halfLineWidth = lineWidth / 2;
                  tickPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + lineWidth;
  
                  axisLabel = me.axesLabels[axisPosition] = axisSurface.add({type: 'composite'});
  
                  axisLabel.labelRect = axisLabel.addSprite(Ext.apply({
                      type: 'rect',
                      fillStyle: 'white',
                      x: axisPosition === 'right' ? lineWidth : 0,
                      y: axisPosition === 'bottom' ? lineWidth : 0,
                      width: axisWidth - lineWidth - (axisAlignment === 'vertical' && titleBBox ? titleBBox.width : 0),
                      height: axisHeight - lineWidth - (axisAlignment === 'horizontal' && titleBBox ? titleBBox.height : 0),
                      translationX: axisPosition === 'left' && titleBBox ? titleBBox.width : 0,
                      translationY: axisPosition === 'top' && titleBBox ? titleBBox.height : 0
                  }, axesConfig.rect || axesConfig[axisPosition].rect));
  
                  if (axisAlignment === 'vertical' && !verticalLineCfg.strokeStyle) {
                      verticalLineCfg.strokeStyle = attr.strokeStyle;
                  }
                  if (axisAlignment === 'horizontal' && !horizontalLineCfg.strokeStyle) {
                      horizontalLineCfg.strokeStyle = attr.strokeStyle;
                  }
  
                  axisTheme = Ext.merge({}, axesTheme.defaults, axesTheme[axisPosition]);
                  axisLabelConfig = Ext.apply({}, axis.config.label, axisTheme.label);
                  crosshairLabelConfig = axesConfig.label || axesConfig[axisPosition].label;
                  axisLabel.labelText = axisLabel.addSprite(Ext.apply(axisLabelConfig, crosshairLabelConfig, {
                      type: 'text',
                      x: me.calculateLabelTextPoint(false, axisPosition, tickPadding, titleBBox, axisWidth, halfLineWidth),
                      y: me.calculateLabelTextPoint(true, axisPosition, tickPadding, titleBBox, axisHeight, halfLineWidth)
                  }));
              }
              me.horizontalLine = surface.add(horizontalLineCfg);
              me.verticalLine = surface.add(verticalLineCfg);
              return false;
          }
  
      },
  
      onGesture: function (e) {
          var me = this;
          if (me.getLocks()[me.getGesture()] !== me) {
              return;
          }
          var chart = me.getChart(),
              surface = chart.getSurface('overlay'),
              rect = Ext.Array.slice(chart.getInnerRect()),
              padding = chart.getInnerPadding(),
              px = padding.left,
              py = padding.top,
              chartWidth = rect[2],
              chartHeight = rect[3],
              xy = chart.getEventXY(e),
              x = xy[0],
              y = xy[1],
              axes = chart.getAxes(),
              axis, axisPosition, axisAlignment, axisSurface, axisSprite, axisMatrix,
              axisLayoutContext, axisSegmenter,
              axisLabel, labelBBox, textPadding,
              xx, yy, dx, dy,
              xValue, yValue,
              text,
              i;
  
          if (x < 0) {
              x = 0;
          } else if (x > chartWidth) {
              x = chartWidth;
          }
          if (y < 0) {
              y = 0;
          } else if (y > chartHeight) {
              y = chartHeight;
          }
          x += px;
          y += py;
  
          for (i = 0; i < axes.length; i++) {
              axis = axes[i];
              axisPosition = axis.getPosition();
              axisAlignment = axis.getAlignment();
              axisSurface = axis.getSurface();
              axisSprite = axis.getSprites()[0];
              axisMatrix = axisSprite.attr.matrix;
              textPadding = axisSprite.attr.textPadding * 2;
              axisLabel = me.axesLabels[axisPosition];
              axisLayoutContext = axisSprite.getLayoutContext();
              axisSegmenter = axis.getSegmenter();
  
              if (axisLabel) {
                  if (axisAlignment === 'vertical') {
                      yy = axisMatrix.getYY();
                      dy = axisMatrix.getDY();
                      yValue = (y - dy - py) / yy;
                      if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
                          y = Math.round(yValue) * yy + dy + py;
                          yValue = axisSegmenter.from(Math.round(yValue));
                          yValue = axisSprite.attr.data[yValue];
                      } else {
                          yValue = axisSegmenter.from(yValue);
                      }
                      text = axisSegmenter.renderer(yValue, axisLayoutContext);
  
                      axisLabel.setAttributes({translationY: y - py});
                      axisLabel.labelText.setAttributes({text: text});
                      labelBBox = axisLabel.labelText.getBBox();
                      axisLabel.labelRect.setAttributes({
                          height: labelBBox.height + textPadding,
                          y: -(labelBBox.height + textPadding) / 2
                      });
                      axisSurface.renderFrame();
                  } else {
                      xx = axisMatrix.getXX();
                      dx = axisMatrix.getDX();
                      xValue = (x - dx - px) / xx;
                      if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
                          x = Math.round(xValue) * xx + dx + px;
                          xValue = axisSegmenter.from(Math.round(xValue));
                          xValue = axisSprite.attr.data[xValue];
                      } else {
                          xValue = axisSegmenter.from(xValue);
                      }
                      text = axisSegmenter.renderer(xValue, axisLayoutContext);
  
                      axisLabel.setAttributes({translationX: x - px});
                      axisLabel.labelText.setAttributes({text: text});
                      labelBBox = axisLabel.labelText.getBBox();
                      axisLabel.labelRect.setAttributes({
                          width: labelBBox.width + textPadding,
                          x: -(labelBBox.width + textPadding) / 2
                      });
                      axisSurface.renderFrame();
                  }
              }
          }
          me.horizontalLine.setAttributes({y: y, strokeStyle: axisSprite.attr.strokeStyle});
          me.verticalLine.setAttributes({x: x, strokeStyle: axisSprite.attr.strokeStyle});
          surface.renderFrame();
          return false;
      },
  
      onGestureEnd: function (e) {
          var me = this,
              chart = me.getChart(),
              surface =  chart.getSurface('overlay'),
              axes = chart.getAxes(),
              axis, axisPosition, axisSurface, axisLabel,
              i;
  
          surface.remove(me.verticalLine);
          surface.remove(me.horizontalLine);
  
          for (i = 0; i < axes.length; i++) {
              axis = axes[i];
              axisPosition = axis.getPosition();
              axisSurface = axis.getSurface();
              axisLabel = me.axesLabels[axisPosition];
              if (axisLabel) {
                  delete me.axesLabels[axisPosition];
                  axisSurface.remove(axisLabel);
              }
              axisSurface.renderFrame();
          }
  
          surface.renderFrame();
          me.unlockEvents(me.getGesture());
      },
  
      onGestureCancel: function(e) {
          this.onGestureEnd(e);
      },
  
      privates: {
          vertMap: {
              top: 'start',
              bottom: 'end'
          },
  
          horzMap: {
              left: 'start',
              right: 'end'
          },
  
          calculateLabelTextPoint: function(vertical, position, tickPadding, titleBBox, axisSize, halfLineWidth) {
              var titlePadding, sizeProp, pointProp;
  
              if (vertical) {
                  pointProp = 'y';
                  sizeProp = 'height';
                  position = this.vertMap[position];
              } else {
                  pointProp = 'x';
                  sizeProp = 'width';
                  position = this.horzMap[position];
              }
  
              switch (position) {
                  case 'start':
                      titlePadding = titleBBox ? titleBBox[pointProp] + titleBBox[sizeProp] : 0;
                      return titlePadding + (axisSize - titlePadding - tickPadding) / 2 - halfLineWidth;
                  case 'end':
                      titlePadding = titleBBox ? axisSize - titleBBox[pointProp] : 0;
                      return tickPadding + (axisSize - tickPadding - titlePadding) / 2 + halfLineWidth;
                  default:
                      return 0;
              }
          }
      }
  });
  