chartjs / chartjs-plugin-annotation

Annotation plugin for Chart.js
MIT License
603 stars 325 forks source link

Draggable Annotations don't work in React #910

Open PaulSender opened 11 months ago

PaulSender commented 11 months ago

I've set up my chart to mimic the configuration on this page.

element never gets updated even if I put it in a state variable

Here's the code for reference.

const dragger = {
    id: 'dragger',
    beforeEvent(chart, args, options) {
      console.log(element, _ele); // logs undefined always
      if (handleDrag(args.event)) {
        args.changed = true;
        return;
      }
    },
  };

  const [element, setElement] = useState<any>();
  const [lastEvent, setLastEvent] = useState<any>();
  let _ele;

  const drag = function (moveX, moveY) {
    element.x += moveX;
    element.y += moveY;
    element.x2 += moveX;
    element.y2 += moveY;
    element.centerX += moveX;
    element.centerY += moveY;
    if (element.elements && element.elements.length) {
      for (const subEl of element.elements) {
        subEl.x += moveX;
        subEl.y += moveY;
        subEl.x2 += moveX;
        subEl.y2 += moveY;
        subEl.centerX += moveX;
        subEl.centerY += moveY;
        subEl.bX += moveX;
        subEl.bY += moveY;
      }
    }
  };

  const handleElementDragging = function (event) {
    console.log('dragging'); // never logs
    if (!lastEvent || !element) {
      return;
    }
    const moveX = event.x - lastEvent.x;
    const moveY = event.y - lastEvent.y;
    drag(moveX, moveY);
    setLastEvent(event);
    return true;
  };

  const handleDrag = function (event) {
    console.log(element, _ele); // logs undefined always
    if (element) {
      switch (event.type) {
        case 'mousemove':
          return handleElementDragging(event);
        case 'mouseout':
        case 'mouseup':
          setLastEvent(undefined);
          break;
        case 'mousedown':
          setLastEvent(event);
          break;
        default:
      }
    }
  };

     <_Chart
            type="bar"
            data={chartData}
            plugins={[dragger]}
            options={{
              events: ['mousedown', 'mouseup', 'mousemove', 'mouseout'],
              scales: {
                y: {
                  beginAtZero: true,
                  min: 0,
                  max: 100,
                },
              },
              plugins: {
                annotation: {
                  enter(ctx) {
                    setElement(ctx.element);
                    _ele = ctx.element;
                    console.log(_ele); // logs correctly here
                  },
                  leave() {
                    setElement(undefined);
                    setLastEvent(undefined);
                  },
                  annotations: {
                    annotation1: {
                      type: 'label',
                      backgroundColor: 'rgba(255, 99, 132, 0.25)',
                      borderWidth: 3,
                      borderColor: 'black',
                      content: ['Label annotation', 'to drag'],
                      callout: {
                        display: true,
                        borderColor: 'black',
                      },
                      xValue: 1,
                      yValue: 40,
                    },
                  },
                },
              },
            }}
          />
stockiNail commented 11 months ago

@PaulSender thank you for the issue. I'm not so expert on React. Can you prepare a codesandbox with the sample in order to reproduce the issue?

TristanDuck commented 7 months ago

Did you ever fix this?

Gregorha commented 5 months ago

You can use Ref to fix this problems with external mutations To setup the variable you can do with use ref const elementRef = useRef(); Updates to values can be done with elementRef.current =. The entire code will look like this:

const elementRef = useRef<AnnotationElement | undefined>();
  const lastEventRef = useRef<AnnotationEvents | undefined>();

  const drag = function (moveX: number, moveY: number) {
    elementRef.current.x += moveX;
    elementRef.current.y += moveY;
    elementRef.current.x2 += moveX;
    elementRef.current.y2 += moveY;
    elementRef.current.centerX += moveX;
    elementRef.current.centerY += moveY;
    if (elementRef.current.elements && elementRef.current.elements.length) {
      for (const subEl of elementRef.current.elements) {
        subEl.x += moveX;
        subEl.y += moveY;
        subEl.x2 += moveX;
        subEl.y2 += moveY;
        subEl.centerX += moveX;
        subEl.centerY += moveY;
        subEl.bX += moveX;
        subEl.bY += moveY;
      }
    }
  };

  const handleElementDragging = function (event) {

    if (!lastEventRef.current || !elementRef.current) {
      return;
    }
    const moveX = event.x - lastEventRef.current.x;
    const moveY = event.y - lastEventRef.current.y;
    drag(moveX, moveY);
    lastEventRef.current = event;
    return true;
  };

  const handleDrag = function (event) {
    if (elementRef.current && elementRef.current.options.id === "draggablebox") {
      switch (event.type) {
        case "mousemove":
          return handleElementDragging(event);
        case "mouseout":
        case "mouseup":
          lastEventRef.current = undefined;
          break;
        case "mousedown":
          lastEventRef.current = event;
          break;
        default:
      }
    }
  };

  const handleEnter = (ctx) => {
    elementRef.current = ctx.element;
    console.log(elementRef.current)
  };

  const handleLeave = () => {
    elementRef.current = undefined;
    lastEventRef.current = undefined;
  };

  const dragger = {
    id: "dragger",
    beforeEvent(chart, args, options) {
      if (handleDrag(args.event)) {
        args.changed = true;
        return;
      }
    },
  };
  const options: ChartOptions<"line"> = {
    responsive: true,
    interaction: {
      mode: "index" as const,
      intersect: false,
    },
    plugins: {
      annotation: {
        enter(ctx) {
          handleEnter(ctx)
        },
        leave() {
          handleLeave()
        },
        annotations: {
          draggablebox: {
            type: "box",
            backgroundColor: "rgba(165, 214, 167, 0.2)",
            borderColor: "rgb(165, 214, 167)",
            borderWidth: 2,
            label: {
              display: true,
              content: ["Box annotation", "to drag"],
              textAlign: "center",
            },
            xMax: labels[2],
            xMin: labels[7],
            xScaleID: "x",
            yMax: 5,
            yMin: 0,
            yScaleID: "y",
          },
        },
      },
    },
    events: ["mousedown", "mouseup", "mousemove", "mouseout"],
  };
  // const labels =  dates && getUniqueDays(dates)
  const datasets = dataGraph.registros && [
    {
      label: "data",
      data: dataGraph.registros.map((registro) => registro.aceleracaoXEmMs2),
      borderColor: "#ffc210",
      backgroundColor: colorLib("#ffc210").alpha(0.5).rgbString(),
    },
  ];

  const data: ChartData<"line"> = {
    datasets,
    labels,
  };

  return (
    <>
      <div id="chart-area">
        {datasets && labels && <Line options={options} data={data} plugins={[dragger]} />}
      </div>
    </>
  );