Dominick Navarro
Dominick Navarro

Reputation: 752

Highcharts manually added svg elements not following stock graph on pan

I have a click event in my highstock / highchart graph, I have successfully added custom drawing tools such as adding lines and text. Here is the code for that

$('#stockchart-canvas-container').on('click','svg',function(e){
        var svg = $('#stockchart-canvas-container svg')[0];
        var point= svg.createSVGPoint(), svgP
        point.x = e.clientX
        point.y = e.clientY
        svgP = point.matrixTransform(svg.getScreenCTM().inverse());

        if(user.selected_tool=='line'){
            if(user.previous_x == undefined && user.previous_y == undefined) {
                user.current_x = svgP.x
                user.current_y = svgP.y
                user.previous_x = 0
                user.previous_y = 0
                $('#stockchart-canvas-container').on('mousemove','svg',function(ev){
                    var svg2 = $('#stockchart-canvas-container svg')[0];
                    var point2= svg.createSVGPoint(), svgP2
                    point2.x = ev.clientX
                    point2.y = ev.clientY
                    svgP2 = point2.matrixTransform(svg2.getScreenCTM().inverse());
                    $('#temp-line').remove()
                    stockchart.renderer.path(['M',
                                        user.current_x,
                                        user.current_y,
                                        'L',
                                        svgP2.x,
                                        svgP2.y,
                                        'Z',
                                        ]).attr({'stroke-width':2,stroke:'#ccc',id:'temp-line'}).add(stockchart.seriesGroup)
                })
            } else {
                $('#stockchart-canvas-container').off('mousemove')
                stockchart.renderer.path(['M',
                                        user.current_x,
                                        user.current_y,
                                        'L',
                                        svgP.x,
                                        svgP.y,
                                        'Z'
                                        ]).attr({'stroke-width':2,stroke:'#ccc'}).add(stockchart.seriesGroup)
                user.current_x=0
                user.current_y=0
                user.previous_x=undefined
                user.previous_y=undefined
            }
        } else if (user.selected_tool=='text') {
            $('#insert-text-modal').modal('show')
            $('#accept-insert-text').on('click',function(){
               if($('#text-input').val()){
                stockchart.renderer.text($('#text-input').val(),svgP.x,svgP.y).add(stockchart.seriesGroup)    
               }
               $(this).off('click')
               $('#insert-text-modal').modal('hide')
            })
        }
    })

My problem is that I want the line and the text to follow the stock graph as I pan or zoom the graph. Any ideas how I can do this?

Upvotes: 1

Views: 182

Answers (1)

morganfree
morganfree

Reputation: 12472

You have to preserve coordinate values at the moment the text/line is drawn - the coordinates in terms of axes. On each chart redraw, you need to reposition the line/text - so you have to calculate new pixel position (which can be calculated via axis.toPixels) and set the new values to the line/text. For a text you need to calculate one point, for a path element you need to recalculate each segment.

See the code below:

Function for calculating pixels from values and values from pixels - it includes some basic logic for hiding a text if it overflows a chart's plot area - but it should be adjusted depending on your needs.

function translate (x, y, chart, toPixels) {
  const xAxis = chart.xAxis[0]
  const yAxis = chart.yAxis[0]
  let tx, ty, hide

  if (toPixels) {
    tx = xAxis.toPixels(x)
    ty = yAxis.toPixels(y)

    if (tx < xAxis.left || tx > xAxis.left + xAxis.width) {
       hide = true
     } else if (!hide && (ty < yAxis.top || ty > yAxis.top + yAxis.height)) {
       hide = true
     }

     if (hide) {
       tx = -9e7
       ty = -9e7
     }
  } else {
    tx = xAxis.toValue(x)
    ty = yAxis.toValue(y)
  }    

  return { x: tx, y: ty }
}

On chart click - it adds the text and keep in the array, on chart redraw r - it repositions items.

 chart: {
   events: {
     load: function () {
       this.drawnItems = []
     },
     click: function (e) {
       const { x, y } = e
       const text = this.renderer.text('custom text', x, y).add()
       text.point = translate(x, y, this)
       this.drawnItems.push(text)
     },
     redraw: function () {
       this.drawnItems.forEach(item => {
         const { x, y } = item.point
         item.attr(translate(x, y, this, true))
       })
     }
   }
 },

Live example: http://jsfiddle.net/nsf67ro6/

Upvotes: 2

Related Questions