Google折线图:拖动调整值

发布于 2024-12-26 15:23:16 字数 1040 浏览 2 评论 0原文

我最近放弃使用 Graphael 和扩展包 Ico。我认为它仍然是一个很棒的库,只是需要更好的文档和适当的 API 才能被广泛采用。

我已经将图表区域转换为使用 Google Charts,但似乎找不到一种方法来实现我使用 graphael 实现的一项特定功能:拖动支持。我的一张折线图需要可编辑,这意味着可以上下拖动线上的各个点来调整值。

我一直在尝试寻找一个事件或一种在 API 中附加事件的方法,但没有取得太大成功。有人成功做到过类似的事情吗?

据说可以制作自定义图表 - 可以这样做吗?

编辑:似乎不太可能,或者很难连接到 Google API 或输出的 SVG。它位于 iframe 中且没有可用的源代码,这使得它的吸引力越来越小。

从那时起,我开始尝试 Highcharts。我能够做谷歌引擎所做的一切,并且可以在渲染图表后摆弄图表。由于提供了源代码,因此可以更轻松地解决某些问题。它的工作速度似乎也比 Google 解决方案快一点,因为它在制作折线图时使用路径而不是单个 SVG 圆形元素。

我现在面临的问题与 SVG 路径元素有关 - 没有单个点可以设置拖动。因此,为了使用 jQuery.draggable,必须在与拖动的 SVG 路径片段相同的位置注入一个 SVG Circle 或另一个元素。因此必须解决以下问题: - 如何将创建的元素放置到正确的位置? - 当图表缩放时如何移动元素(任一轴上的最小或最大值变化)? - 如何将新位置转换回针对提供的系列数据进行缩放的值?

有没有人对 Highcharts 有足够的深入经验来摆弄这些?我会尽可能提供 JSfiddle 示例。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

初与友歌 2025-01-02 15:23:16

Highcharts 中并未内置可拖动点,但使用 API 中的事件和方法可以实现可编辑的线图/柱图/散点图/面积图(目前仅处理 y 方向上的点移动)。以下示例使用 mousemovemousedownmouseup 事件来处理交互。

  • mousedown 处,如果某个点获得焦点,则会记录起始 y 值。 y-axis.translate 方法是这里的关键,它将鼠标位置转换到数据域。
  • mousemove 处,焦点和工具提示将更新为新值。
  • mouseup 处,该点被更新,并触发一个新的事件 drop 来更新状态消息中的文本。

jsfiddle 上的完整示例。
另请参阅功能请求可拖动点

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

宫墨修音 2025-01-02 15:23:16

意识到这是一个相当老的问题,但它与谷歌图表相关的引用率很高,所以也许这会对某人有所帮助。

花了一段时间研究这个完全相同的问题,并在 @WhiteHat 的几个答案的帮助下终于成功解决了这个问题。

基本上,您需要将EventListener 添加到“mousemove”、“mouseup”和“mousedown”图表的容器,然后跟踪您当前是否选择了某个点或拖动了一个点。

剩下的大部分最终都是大量的边缘、距离和缩放计算,以尝试找出该点在图表中的实际位置。

最终真正相关的主要部分是:

// 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();

一旦您实际确定了鼠标移动的点所在的位置,则可以通过以下方式调整位置:

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 );

下面包含一个工作示例,其中包含两个不同的线数据集,其中两个数据集具有不同的线数据集。 X 值。

        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>

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>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文