Google Line Chart: drag to adjust value
Asked Answered
K

3

9

I've recently dropped use of Graphael and and extensions package Ico. I think it's still a great library, it just needs better documentation and a proper API before it can be widely adopted.

I've converted the areas with charting to use Google Charts and can't seem to find a way to do one particular feature I used graphael for: drag support. One of my line charts needs to be editable, meaning that individual dots on the line can be dragged up and down to adjust value.

I've been trying to find an event or a way to attach an event in the API without much success. Has anyone managed to do something like that?

It's supposedly possible to do custom charts - would it be possible to do it this way?

EDIT: Seems like it's not really possible or is incredibly hard to hook into Google API or outputted SVG. It being in an iframe and no source code available just makes it less and less attractive option.

I've since started experimenting with Highcharts. I was able to do everything that Google engine does and can fiddle with chart after it's been rendered. Since source code is provided it makes it easier to figure certain things out. It also seems to work a bit faster than Google solution since it uses path when doing a Line Chart instead of individual SVG circle elements.

The issue I'm facing now has to do with the SVG Path element - there is no single point to set up drag on. So in order to use jQuery.draggable one has to inject a SVG Circle or another element at the same position as the SVG Path fragment tying to drag. So the following has to be resolved: - how to place the created element to correct position? - how to move element when chart is scaled (min or max value change on either axis)? - how to convert new position back to a value, scaled for series data supplied?

Has anyone got enough in depth experience with Highcharts to fiddle with these? I'll try to provide JSfiddle example when possible.

Kilroy answered 13/1, 2012 at 8:35 Comment(3)
I've tried using jQuery and listen for mousedown / mouseup on the container (div) holding the chart but contained Google Chart suppresses event bubbling ... Perhaps there is a way to allow event bubbling on the chart?Kilroy
This might work if only one was able to tell Google Charts to render <object> instead of <iframe> chartKilroy
Can you provide a link to the API or demos for the draggable charts behaviour in Raphael? Also do you happen to know any library that enables draggable/resizable pie charts? Relates to https://mcmap.net/q/1172227/-js-input-control-for-proportion-a-widget-to-allocate-proportion-to-more-then-3-elements/339872Montenegro
P
16

Draggable points is not built-in into Highcharts but using the events and methods in the API it is possible to implement an editable line/column/scatter/area-chart (will only handle moving the point in y-direction so far). The following example uses mousemove, mousedown and mouseup events to handle interaction.

  • At mousedown the start y value is recorded if a point is focused. The y-axis.translate method is the key here, it will translate mouse position to the data domain.
  • At mousemove the focused point and tooltip is updated with the new value.
  • At mouseup the point is updated and a new event drop is fired that updates the text in a status message.

Full example on jsfiddle. See also the feature request draggable points

Palais answered 22/1, 2012 at 10:39 Comment(2)
Great! Just what I was looking for. Voted for feature request too.Kilroy
Can we add drag-gable points in a pie chart, i have a requirement where pie slice should get resized by draggingFino
W
3

This is now an available plugin on Highcharts: http://www.highcharts.com/plugin-registry/single/3/Draggable%20Points

Waterspout answered 9/2, 2016 at 0:55 Comment(1)
Thanks! Will look into updating the implementation using the plugin if it's viable.Kilroy
S
0

Realize this is a rather old question, yet its highly referenced in relation to Google Charts, so perhaps this will help someone.

Spent a while working on this exact same issue, and with the help of several answers from @WhiteHat finally managed to get this working.

Basically, you need to addEventListener to the container for the chart for "mousemove", "mouseup", and "mousedown" and then track whether you're currently selected on a point or dragging a point.

The rest mostly ends up being a lot of edge and distance and scaling calculations to try and figure out where the point actually is at in the chart.

The main parts that end up being really relevant are:

// Get layout object for the chart (mostly for bounding boxes)
var chartLayout     = myLineChart.getChart().getChartLayoutInterface();
// Get the actual chart container (has all the chart properties as sub-variables)
var chartContainer  = document.getElementById( myLineChart.getContainerId() );
// Find the outer (beyond title and axes labels) limits of chart
// Varies with screen scrolling
var chartBounds     = chartContainer.getBoundingClientRect();
// Find the actual drawn region where your lines are (offset relative to chart)
var chartAreaBounds = chartLayout.getChartAreaBoundingBox();

Once you've actually figured out where the point is at with your mouse movement, then the location can be adjusted with:

var dataTable = myLineChart.getDataTable();
var spX = ( x - chartBounds.left - chartAreaBounds.left ) / chartAreaBounds.width  * ( chartMaxX - chartMinX );
var spY = ( chartAreaBounds.height - ( y - chartBounds.top  - chartAreaBounds.top ) ) / chartAreaBounds.height * ( chartMaxY - chartMinY );
dataTable.setValue( selectedPoint.row, 0, spX );
dataTable.setValue( selectedPoint.row, selectedPoint.column, spY );

A working example is included below that has two different line datasets with separate X values.

        google.charts.load('current', {'packages':['corechart']});
        google.charts.setOnLoadCallback( initChart );
        
        var myLineChart;
        var selectedPoint = null;
        
        var arrData = [
            ['Age', 'Weight', 'Weight2'],
            [ 3,      3.5,      null   ],
            [ 4,      5,        null   ],
            [ 4,      5.5,      null   ],
            [ 6.5,    7,        null   ],
            [ 8,      12,       null   ],
            [ 11,     14,       null   ],
            [ 1,      null,      3.551 ],
            [ 2,      null,     12.753 ],
            [ 3,      null,      5.058 ],
            [ 4,      null,      6.620 ],
            [ 5,      null,     12.371 ],
            [ 6,      null,      1.342 ],
            [ 7,      null,      5.202 ],
            [ 8,      null,      7.008 ]
        ];
        
        var data;
        
        var options = {
            title: 'Age vs. Weight comparison',
            hAxis: {title: 'Age', minValue: 0, maxValue: 15},
            vAxis: {title: 'Weight', minValue: 0, maxValue: 15},
            legend: 'none'
        };
        
        function initChart(){
            data = google.visualization.arrayToDataTable( arrData );
            
            myLineChart = new google.visualization.ChartWrapper({
                chartType:   'LineChart',
                containerId: 'exampleChart',
                dataTable:   data,
                options:     options
            });
            
            document.getElementById("exampleChart").addEventListener( "mousemove", mouseMoveScript );
            document.getElementById("exampleChart").addEventListener( "mousedown", mouseDownScript );
            document.getElementById("exampleChart").addEventListener( "mouseup", mouseUpScript );
            
            drawChart();
        }

        function drawChart() {
            myLineChart.draw();
        }
        
        function selectPoints( mx, my ) {
            var chartLayout    = myLineChart.getChart().getChartLayoutInterface();
            var chartContainer = document.getElementById( myLineChart.getContainerId() );
            var chartBounds    = chartContainer.getBoundingClientRect();
            if ( ( ( (chartBounds.left + window.pageXOffset) <= mx ) && ( (chartBounds.left + chartBounds.width  + window.pageXOffset) >= mx ) ) &&
                 ( ( (chartBounds.top  + window.pageYOffset) <= my ) && ( (chartBounds.top  + chartBounds.height + window.pageYOffset) >= my ) ) ){
                var selection = [];
                var dataTable = myLineChart.getDataTable();
                for (var row = 0; row < dataTable.getNumberOfRows(); row++) {
                    for (var col = 1; col < dataTable.getNumberOfColumns(); col++) {
                        var point = chartLayout.getBoundingBox('point#' + (col - 1) + '#' + row);
                        if( point != null ){
                            if ((((chartBounds.left + point.left) >= (mx - point.width)) &&
                                 ((chartBounds.left + point.left + point.width) <= (mx + point.width))) &&
                                (((chartBounds.top + point.top) >= (my - point.height)) &&
                                 ((chartBounds.top + point.top + point.height) <= (my + point.height)))) {
                              selection.push({row: row, column: col});
                            }
                        }
                    }
                }
            
                if( selection.length > 0 ){
                    var item = selection[0];
                    selectedPoint = selection[0];
                } else {
                    selectedPoint = null;
                }
                
                myLineChart.getChart().setSelection( selection );
            }
        }
        
        function mouseMoveScript( e ){
            var x = e.clientX;
            var y = e.clientY;
            var coor = "Coordinates: (" + x + "," + y + ")";
            document.getElementById("output").innerHTML = coor;
            if( selectedPoint != null ){
                var chartContainer  = document.getElementById( myLineChart.getContainerId() );
                var chartBounds    = chartContainer.getBoundingClientRect();
                var chartLayout     = myLineChart.getChart().getChartLayoutInterface()
                var chartAreaBounds = chartLayout.getChartAreaBoundingBox();
                var chartMinX       = chartLayout.getHAxisValue( chartAreaBounds.left );
                var chartMaxX       = chartLayout.getHAxisValue( chartAreaBounds.left + chartAreaBounds.width );
                var chartMinY       = chartLayout.getVAxisValue( chartAreaBounds.top + chartAreaBounds.height );
                var chartMaxY       = chartLayout.getVAxisValue( chartAreaBounds.top );
                
                var dataTable = myLineChart.getDataTable();
                var spX = ( x - chartBounds.left - chartAreaBounds.left ) / chartAreaBounds.width  * ( chartMaxX - chartMinX );
                var spY = ( chartAreaBounds.height - ( y - chartBounds.top  - chartAreaBounds.top ) ) / chartAreaBounds.height * ( chartMaxY - chartMinY );
                dataTable.setValue( selectedPoint.row, 0, spX );
                dataTable.setValue( selectedPoint.row, selectedPoint.column, spY );
                drawChart();
            }
        }
        
        function mouseDownScript( e ){
            var mx = e.clientX;
            var my = e.clientY;
            
            if( e.target ){
                targ = e.target;
                selectPoints( mx, my );
            } else if (e.srcElement) {
                targ = e.srcElement;
            }
            var tname;
            tname = targ.tagName;
        }
        
        function mouseUpScript( e ){
            if( selectedPoint != null ){
                selectedPoint = null;
            }
        }
html, body {
  height: 100%;
  margin: 0px 0px 0px 0px;
  padding: 0px 0px 0px 0px;
}

#select_div {
  border: 1px dashed #3366cc;
  position: absolute;
  z-index: 1000;
}

.exampleChart{
  height: 100%;
}

.hidden {
  display: none;
  visibility: hidden;
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="output"></div>
<div>
    <div id="exampleChart"  style="width: 900px; height: 500px;"></div>
</div>
Survive answered 20/12, 2022 at 20:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.