artus9033 / chartjs-plugin-dragdata

Draggable data points plugin for Chart.js
MIT License
261 stars 55 forks source link

Dragging does not work with panning plugin #43

Closed Zuckerbrot closed 4 years ago

Zuckerbrot commented 4 years ago

Hi, thanks for this great plugin. Is it possible to make it compatible with the zoom/panning plugin? https://github.com/chartjs/chartjs-plugin-zoom

I can only get either panning or data-dragging to work. Example (works in Safari): https://jsfiddle.net/zpq8an40/1/

Would be nice to have both, like eg here http://bl.ocks.org/stepheneb/1182434

Is there any workaround available?

Thank you!

chrispahm commented 4 years ago

Hi! Thanks for the inquiry!

I remember discussing a similar matter here: https://github.com/chrispahm/chartjs-plugin-dragdata/issues/21#issuecomment-489426959

It basically boils down to the question of which implementation would be best from a UI perspective. Currently, both the zoom plugin and this plugin use the same gestures, so there is no way to detect whether the user wants to drag a point or zoom the chart. In your example, zooming is triggered by dbl clicking the chart. As far as I know, the zoom plugin does not support this method. So you could either open an issue there or use the workaround I proposed in the other issue.

Merry Christmas and good luck, Christoph

Zuckerbrot commented 4 years ago

Thanks for the quick reply. I opened an issue at the zoom plugin project (https://github.com/chartjs/chartjs-plugin-zoom/issues/308)

However, I wonder whether it is possible to detect if the dragging gesture is made on a data point or somewhere else in the plotting area. In the first case dragging the data point would be natural, in the latter, one would expect scrolling/panning the plotting area.

Thank you and happy holidays!

chrispahm commented 4 years ago

Alright, so I had another look at the source code of the zoom plugin: Event listeners for mousedown, mousemove, and mouseup events are attached in the beforeInit function. In order to detect if the dragging gesture is made on a data point, you would need to adjust the code to something like the following:

chartInstance.$zoom._mouseDownHandler = function(event) {
  // Disable zoom when click happened on a chart element
  if (chartInstance.getElementAtEvent(event).length > 0) return
  node.addEventListener('mousemove', chartInstance.$zoom._mouseMoveHandler);
  chartInstance.$zoom._dragZoomStart = event;
};

But unfortunately, that's not all there is to it. The d3-drag event listeners used in this plugin do not seem to play nice with the event listeners attached by the zoom plugin. From the d3-drag documentation:

If an event listener was already registered for the same type and name, the existing listener is removed before the new listener is added.

There may be ways to circumvent this, but as I a) currently have no use-case for using the two plugins simultaneously b) am quite satisfied with the d3-drag implementation in this plugin for all use-cases c) can't spent much more time on this issue, as this an enhancement and not really a bug, I won't be digging any deeper 😢

If you are eager to get these two plugins to work fine together, and are willing to go the extra mile, I would propose you to 1) Fork both plugins 2) Add the above element click detection to the zoom plugin 3) Tinker with the event detection in this plugin until they work well together 4) ??? 5) Nobel prize in open source development

I wish you all the best luck with this, please keep me posted when you come up with something! Best Christoph

chrispahm commented 4 years ago

PS: An excellent example of how the drag plugin can be used in combination with the zoom plugin was done by @imartinezl https://imartinezl.github.io/formula-one-viewer/

I think this demo shows how an innovative UI can make data approachable and easy to understand, without overwhelming the user.

chrispahm commented 4 years ago

Hi @Zuckerbrot,

in an issue similar to this one, @yoelz55 mentioned that the mode property required by the chartjs-plugin-zoom can be defined with a function.

plugins: {
  zoom: {
    pan: {
      enabled: true,
      mode: function({chart }) {
        // check if gesture was made over a data point
        return 'xy' // or ''
      }
    },

with this in mind, you could add a mousemove event to the ChartJS container, and observe whether a gesture was made on a datapoint or not. Then, depending on the value of the global variable, either return mode 'xy' or ''.

var eventOutsideDataPoint = true
chartCanvas.onmousemove = function(evt) {
  var activePoints = yourChart.getElementsAtEvent(evt);
  eventOutsideDataPoint = activePoints && activePoints.length ? false : true
};

//
var chartConfig: {
  ...
  plugins: {
  zoom: {
    pan: {
      enabled: true,
      mode: function({chart }) {
        if (eventOutsideDataPoint) {
          return 'xy';
        }
        return ''
      }
    },
   ....
}

Here is a fiddle showcasing this workaround.

Since the mousemove events executes hundreds of times per second, you need to be careful with this approach from a performance perspective. Also, you would need to think about the necessary adaptations (e.g. the correct listeners) to make this work correctly on touch screens. However, from a technical point of view I think these are managable obstacles from now on.

From a UI perspective, I'd still argue that this is rather cumbersome though...

chrispahm commented 4 years ago

I will close this as answered (see above comment for a working example). Please re-open if necessary.