Drag and Drop offset with react

Hi,
I am trying to implement a simple drag and drop, but I cannot make it work. Dragged elements are always dropped at the same location, which seems to be the center of diagram. Here is some excerpts of my code:

const diagram = $(go.Diagram, {
            "undoManager.isEnabled": true,
            allowDragOut: true,
            allowDrop: true
        }); 

const handleDropEvent = (event: any) => {
            event.preventDefault();
            const data: { [key: string]: any } = JSON.parse(event.dataTransfer.getData("text"));
            const diagram = diagRef.current?.getDiagram();
            const model = diagram?.model as go.GraphLinksModel;
            if (model == undefined) return;

            // Get the mouse event's clientX and clientY properties
            const clientX = event.clientX;
            const clientY = event.clientX;
        
            // Convert the client coordinates to diagram coordinates
            const docPoint = diagram!!.transformViewToDoc(diagram!!.transformDocToView(new go.Point(clientX, clientY)));
        
            diagram?.startTransaction('new node');
            model.addNodeData({
                key: data['id'],
                type: data['type'],
                name: data['name'],
                category: data['type'],
                location: `${docPoint?.x} ${docPoint?.y}`
            });
            diagram?.commitTransaction('new node');
        };

I use gojs with ReactJS, any help is welcome ! Thx.

Hi. I’m missing some context here. What is the user trying to drag-and-drop? What are they dragging from, and where do they start from?

Is there a Diagram.layout in the target Diagram? If so, might it be automatically locating the newly dropped node at or near the center?

Objects are dragged from another div, transfered data is represented by a JSON object.

const handleDragStart = (event: React.DragEvent<HTMLDivElement>) => {
        const data = JSON.stringify(props.semanticElement);
        event.dataTransfer.setData("text", data);
    };

And here is the typical structure that is transfered:

{
        "id": "http://xowl.org/requirements/resources#req003",
        "http://xowl.org/schema#name": "REQ003",
        "http://xowl.org/requirements/ontology#refines": "http://xowl.org/requirements/resources#req002",
        "type": "http://xowl.org/requirements/ontology#Requirement"
 }

I have not used any particular layout.
Thank you for your help.

Ah, OK, you are not dragging from another Diagram such as a Palette. All such drag-and-drops are built-in and should just work without any additional non-app-specific code.

Have you seen this sample? HTML Drag and Drop, and external Clipboard pasting

That shows you how to convert coordinate systems and how to deal with drag-overs and drops.

Yes I have seen the mentioned example, but I did not manage to apply it correctly in my component.
Here is the full content of my component as an example:

import { Box, Button } from '@mui/material';
import React, { useEffect, useState, useRef } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as go from 'gojs';
import key from '../common/gojs';
import { ReactDiagram } from 'gojs-react';
import { setSelectedElement } from '../../redux/EditionSlice';
import type { RootState } from '../../redux/store';
import CollaborationsController from '../../api/collaborations';
import { ArtifactElement } from '../../data/artifacts';
import GenericNode from './GenericNode';

const LinksPanel = (props: any) => {
    const collaborations = CollaborationsController.getInstance();

    const dispatch = useDispatch();
    const diagRef = useRef<ReactDiagram>(null);
    const activeCollabHeader = useSelector((state: RootState) => state.collab.activeCollaboration);

    const [nodes, setNodes] = useState<go.ObjectData[]>([]);
    const [links, setLinks] = useState<go.ObjectData[]>([]);
    const [isNew, setNew] = useState(true);

    const nodeClicked = (e: any, obj: any) => {
        dispatch(setSelectedElement(obj.data.element as ArtifactElement));
    };

    const initDiagram = () => {
        const $ = go.GraphObject.make;
        go.Diagram.licenseKey = key;
        const diagram = $(go.Diagram, {
            "undoManager.isEnabled": true,
            allowDragOut: true,
            allowDrop: true,
            allowTextEdit: true,
        });

        // Define node templates
        diagram.nodeTemplate = GenericNode(nodeClicked);

        // Define link template
        diagram.linkTemplate = $(go.Link, {
            layerName: "Foreground",
            routing: go.Link.AvoidsNodes,
            corner: 10,
            toShortLength: 4,
            relinkableFrom: true,
            relinkableTo: true,
            reshapable: true,
            resegmentable: true
        }, $(go.Shape, { strokeWidth: 1.5 }), $(go.Shape, { toArrow: "standard", stroke: null }), $(go.Panel, "Auto", new go.Binding("location", "location", go.Point.parse), $(go.Shape, {
            fill: $(go.Brush, "Radial", { 0: "rgb(245, 245, 245)", 0.7: "rgb(245, 245, 245)", 1: "rgba(245, 245, 245, 0)" }),
            stroke: null
        }), $(go.TextBlock, {
            textAlign: "center",
            font: "9pt helvetica, arial, sans-serif",
            margin: 4,
            editable: true,
            segmentOffset: new go.Point(0, -10)
        }, new go.Binding("text", "label").makeTwoWay())));

        // Set model
        diagram.model = new go.GraphLinksModel({
            linkKeyProperty: 'id'
        });

        diagram.addDiagramListener("PartCreated", function(e) {
            const part = e.subject;
            if (part instanceof go.Link) {
                part.data.label = "links";
            }
        }); //FIXME: does not work

        return diagram;
    };

    const handleSave = () => {
        if (activeCollabHeader == null) return;
        const diagram = diagRef.current?.getDiagram();
        const model = diagram?.model as go.GraphLinksModel;
        if (model == undefined) return;
        const data = {
            nodes: model.nodeDataArray,
            links: model.linkDataArray
        };
        // TODO: send data to server
    };

    useEffect(() => {

        const handleDndEvent = (event: any) => {
            event.preventDefault();
        };

        const handleDropEvent = (event: any) => {
            event.preventDefault();
            const data: { [key: string]: any } = JSON.parse(event.dataTransfer.getData("text"));
            const diagram = diagRef.current?.getDiagram();
            const model = diagram?.model as go.GraphLinksModel;
            if (model == undefined) return;

            const dragged = event.target;
            const pixelratio = diagram?.computePixelRatio();

            const bbox = dragged.getBoundingClientRect();
            let bbw = bbox.width;
            if (bbw === 0) bbw = 0.001;
            let bbh = bbox.height;
            if (bbh === 0) bbh = 0.001;
            const mx = event.clientX - bbox.left * ((dragged.width / pixelratio!!) / bbw);
            const my = event.clientY - bbox.top * ((dragged.height / pixelratio!!) / bbh);
            const point = diagram?.transformViewToDoc(new go.Point(mx, my));

            // Convert the client coordinates to diagram coordinates
            const docPoint = diagram!!.transformViewToDoc(new go.Point(mx - dragged.offsetX, my - dragged.offsetY));

            diagram?.startTransaction('new node');
            model.addNodeData({
                key: data['id'],
                type: data['type'].split('#')[1],
                name: data['http://xowl.org/schema#name'],
                category: data['type'].split('#')[1],
                location: `${docPoint?.x} ${docPoint?.y}`,
                element: data
            });
            diagram?.commitTransaction('new node');
        };

        const diagClassName = diagRef.current?.props.divClassName;
        if (diagClassName != null) {
            const diagElems = document.getElementsByClassName(diagClassName);
            if (diagElems.length > 0) {
                const diagElem = diagElems[0];
                diagElem.addEventListener("drag", handleDndEvent);
                diagElem.addEventListener("dragenter", handleDndEvent);
                diagElem.addEventListener("dragleave", handleDndEvent);
                diagElem.addEventListener("dragend", handleDndEvent);
                diagElem.addEventListener("dragover", handleDndEvent);
                diagElem.addEventListener("drop", handleDropEvent);
            }
        };
        return () => {
            const diagClassName = diagRef.current?.props.divClassName;
            if (diagClassName != null) {
                const diagElems = document.getElementsByClassName(diagClassName);
                if (diagElems.length > 0) {
                    const diagElem = diagElems[0];
                    diagElem.removeEventListener("drag", handleDndEvent);
                    diagElem.removeEventListener("dragenter", handleDndEvent);
                    diagElem.removeEventListener("dragleave", handleDndEvent);
                    diagElem.removeEventListener("dragend", handleDndEvent);
                    diagElem.removeEventListener("dragover", handleDndEvent);
                    diagElem.removeEventListener("drop", handleDropEvent);
                }
            };
        }
    }, [diagRef]);

    return (
        <Box>
            <ReactDiagram
                ref={diagRef}
                initDiagram={initDiagram}
                divClassName='diagram-editor'
                nodeDataArray={nodes}
                linkDataArray={links}
            />
            <Box sx={{ textAlign: 'center' }}>
                <Button variant="contained" sx={{ borderRadius: 5 }} onClick={handleSave}>
                    Save
                </Button>
            </Box>
        </Box>
    );
};

export default LinksPanel;

For specific reasons, I could not use the palette provided by gojs.
Thank you for your help.

In your handleDropEvent function, when you step through the code, what are the values of point and docPoint?

If afterwards you select the dropped node and look at its Part.location, does it match the value of docPoint?