title: Highcharts custom renderer chart and tooltip
body: <p>Our idea was to draw a waterfall chart with unregular widths. We achieved this chart style by rendering rectangulars to the corresponding data points (visible in fiddle for demonstration purposes).<br>
  Moreover, we want to add a tooltip and make this tooltip following the mouse.</p>
  
  <p>We are facing three problems:</p>
  
  <ol>
  <li><p>When you zoom very close to the chart, you will see that namely rect2 and rect3 as well as rect3 and rect4 show small gaps between the rectangulars edges. This seems to be strange because all rectangulars have been created by the same for-loop procedure (lines 68-84 in fiddle). Any ideas? (If you change the chart width, the gaps can vanish or occur between other rectangulars...)</p></li>
  <li><p>For the first and last rectangular we wanted to create individual borders. Therefore, we set up white borders to the first and last rectangulars (lines 97,155 in fiddle) and added our renderer paths (dotted, solid) lines afterwards (lines 221-298). As you can see in case of rect0 the vertical line does not cover the white border exactly, even though we used the same plot coordinates as the rectangular has. 
  (If you change the chart width, the magnitude of the problem gets better or even worse)</p></li>
  <li><p>We rendered custom tooltips to the renderer group (rectangulars, dataLabels) and display these by mouseover and mouseout events. A first problem was that the tooltip disappeared when hovering the dataLabel. We made a workaround (lines 190-195) but we are wondering if there is a more elegant way to display the tooltip on both rects and labels. Further, we want to make the tooltip follow the mouse movements (event mousemove) but we can't get this event to work on our example.</p></li>
  </ol>
  
  <p><a href="http://jsfiddle.net/8zDdA/1/" rel="nofollow">Here is our fiddle example</a></p>
  
  <pre><code>$(function () {
  
  
  var chart = new Highcharts.Chart({
      chart: {
          renderTo: 'container',
          type: 'scatter'
      },
      title: {
          text: 'Custom waterfall with unequal width'
      },
      xAxis: {
          min: 0,
          max: 50,
          reversed: true
      },
      yAxis: {
          title: {
              text: 'Share'
          },
          min: 0,
          max: 100
      },
      tooltip: {
          enabled: false
      },
      legend: {
          enabled: false
      },
      credits: {
          enabled: false
      },
      series: [{
          name: 'basicData',
          visible: true, //for demonstration purpose
          data: [
              [50, 40],
              [45, 48],
              [39, 52],
              [33, 68],
              [22, 75],
              [15, 89],
              [5, 100]
          ]
      }]
  },
  //add function for custom renderer
  function (chart) {
  
      var points = this.series[0].data,
          addMarginX = this.plotLeft,
          addMarginY = this.plotTop,
          xZero = this.series[0].points[0].plotX,
          yZero = this.chartHeight - addMarginY - this.yAxis[0].bottom,
          xAll = [],
          yAll = [],
          widthAll = [],
          heightAll = [];
  
      //renderer group for all rectangulars
      rectGroup = chart.renderer.g()
          .attr({
          zIndex: 5
      })
          .add();
  
      //draw for each point a rectangular
      for (var i = 0; i &lt; points.length; i++) {
  
          var x = points[i].plotX + addMarginX,
              y = points[i].plotY + addMarginY,
              width,
              height;
  
          if (i === 0) { //for the first rect height is defined by pixel difference of yAxis and yValue
              height = yZero - points[i].plotY
          } else { // else height is pixel difference of yValue and preceeding yValue
              height = points[i - 1].plotY - points[i].plotY
          };
          if (i === points.length - 1) { // for the last rectangular pixel difference of xValue and xAxis at point=0
              width = this.xAxis[0].translate(0) - points[i].plotX
          } else { // else pixel difference of xValue and subsequent xValue
              width = points[i + 1].plotX - points[i].plotX
          };
  
          xAll.push(x);
          yAll.push(y);
          widthAll.push(width);
          heightAll.push(height);
  
          //general styling of rects, exception for first and last rect
          var attrOptions;
          if (i === 0) {
              attrOptions = {
                  id: i,
                      'stroke-width': 0.75,
                  stroke: 'rgb(255, 255, 255)', //white border which is later covered by dotted lines
                  fill: {
                      linearGradient: {
                          x1: 1,
                          y1: 0,
                          x2: 0,
                          y2: 0
                      },
                      stops: [
                          [0, Highcharts.getOptions().colors[0]],
                          [1, 'rgba(255,255,255,0.5)']
                      ]
                  }
              };
          } else if (i === points.length - 1) {
              attrOptions = {
                  id: i,
                      'stroke-width': 0.75,
                  stroke: 'rgb(255, 255, 255)', //white border which is later covered by dotted lines
                  fill: {
                      linearGradient: {
                          x1: 0,
                          y1: 0,
                          x2: 1,
                          y2: 0
                      },
                      stops: [
                          [0, Highcharts.getOptions().colors[0]],
                          [1, 'rgba(255,255,255,0.5)']
                      ]
                  }
              };
          } else {
              attrOptions = {
                  id: i,
                      'stroke-width': 0.75,
                  stroke: 'black',
                  fill: Highcharts.getOptions().colors[0]
              };
          }
  
          // draw rect, y-position is set to yAxis for animation
          var tempRect = chart.renderer.rect(x, this.chartHeight - this.yAxis[0].bottom, width, 0, 0)
              .attr(attrOptions)
              .add(rectGroup);
  
          //animate rect
          tempRect.animate({
              y: y,
              height: height
  
          }, {
              duration: 1000
          });
      }; // for loop ends over all rect
  
  
      //renderer centered dataLabels to rectangulars
      for (var i = 0; i &lt; points.length; i++) {
  
          var labelColor = 'rgb(255,255,255)';
          if (i === 0 || i === points.length - 1) {
              labelColor = '#666666'
          }
          var label = chart.renderer.label('rect' + i)
              .attr({
              align: 'center',
              zIndex: 5,
              padding: 0
          })
              .css({
              fontSize: '11px',
              color: labelColor
          })
              .add(rectGroup);
  
          var labelBBox = label.getBBox();
  
          label.attr({
              x: xAll[i] + widthAll[i] * 0.5,
              y: yAll[i] + heightAll[i] * 0.5 - labelBBox.height * 0.5
          });
      }; // loop for dataLabels ends
  
  
      // add tooltip to rectangulars AND labels (rectGroup)
      var tooltipIndex;
  
      rectGroup.on('mouseover', function (e) {
  
          //get the active element (or is there a simpler way?)
          var el = (e.target.correspondingUseElement) ? e.target.correspondingUseElement : e.target;
  
          //determine with the 'id' to which dataPoint this element belongs
          //problem: if label is hovered, use tootltipIndex of rect
          var i = parseFloat(el.getAttribute('id'));
          if (!isNaN(i)) {
              tooltipIndex = i;
          }
          // render text for tooltip based on coordinates of rect
          text = chart.renderer.text('This could be &lt;br&gt;an informative text', xAll[tooltipIndex], yAll[tooltipIndex] - 30)
              .attr({
              zIndex: 101
          })
              .add();
  
          var box = text.getBBox();
          //box surrounding the tool tip text                     
          border = chart.renderer.rect(box.x - 5, box.y - 5, box.width + 10, box.height + 10, 5)
              .attr({
              fill: 'rgba(255, 255, 255, 0.95)',
              stroke: 'blue',
                  'stroke-width': 1,
              zIndex: 100
          })
              .add();
      })
          .on('mouseout', function () {
  
          text.destroy();
          border.destroy();
      })
  
  
      //render first and last rect as open and partly dotted rect
      var M = 'M',
          L = 'L',
          pathStartSol = [],
          pathEndSol = [],
          pathStartDot = [],
          pathEndDot = [],
          y0 = this.chartHeight - this.yAxis[0].bottom,
          last = xAll.length - 1;
  
      pathStartDot = [
      M, xAll[0], y0,
      L, xAll[0] + widthAll[0] * 0.6, y0,
      M, xAll[0], y0,
      L, xAll[0] + widthAll[0] * 0.6, y0,
      M, xAll[last] + widthAll[last] * 0.4, y0,
      L, xAll[last] + widthAll[last], y0,
      M, xAll[last] + widthAll[last] * 0.4, y0,
      L, xAll[last] + widthAll[last], y0];
  
      pathStartSol = [
      M, xAll[0] + widthAll[0] * 0.6, y0,
      L, xAll[1], y0,
      L, xAll[1], y0,
      L, xAll[0] + widthAll[0] * 0.6, y0,
      M, xAll[last] + widthAll[last] * 0.4, y0,
      L, xAll[last], y0,
      L, xAll[last], y0,
      L, xAll[last] + widthAll[last] * 0.4, y0];
  
      pathEndDot = [
      M, xAll[0], yAll[0],
      L, xAll[0] + widthAll[0] * 0.6, yAll[0],
      M, xAll[0], y0,
      L, xAll[0] + widthAll[0] * 0.6, y0,
      M, xAll[last] + widthAll[last] * 0.4, yAll[last],
      L, xAll[last] + widthAll[last], yAll[last],
      M, xAll[last] + widthAll[last] * 0.4, yAll[last - 1],
      L, xAll[last] + widthAll[last], yAll[last - 1]];
  
      pathEndSol = [
      M, xAll[0] + widthAll[0] * 0.6, yAll[0],
      L, xAll[1], yAll[0], // does not match exactly the underlying white border of rect
      L, xAll[1], y0, // does not match exactly the underlying white border of rect
      L, xAll[0] + widthAll[0] * 0.6, y0,
      M, xAll[last] + widthAll[last] * 0.4, yAll[last],
      L, xAll[last], yAll[last],
      L, xAll[last], yAll[last - 1],
      L, xAll[last] + widthAll[last] * 0.4, yAll[last - 1]];
  
      var pathSol = chart.renderer.path(pathStartSol)
          .attr({
          'stroke-width': 1,
          stroke: 'black',
          zIndex: 100
      }).add();
  
      var pathDot = chart.renderer.path(pathStartDot)
          .attr({
          'stroke-width': 1,
          stroke: 'black',
          zIndex: 100,
          dashstyle: 'Dot'
      }).add();
  
      pathSol.animate({
          d: pathEndSol
      }, {
          duration: 1000
      });
  
      pathDot.animate({
          d: pathEndDot
      }, {
          duration: 1000
      });
  
  });
  
  });
  </code></pre>
  
  <p>We know it's a rather complex example but would appreciate all ideas that come up to you guys. Thanks!</p>