zsviczian / obsidian-excalidraw-plugin

A plugin to edit and view Excalidraw drawings in Obsidian
4.13k stars 228 forks source link

FR: How long will users enjoy better shape boundingbox #1539

Open WesternFastShooters opened 9 months ago

WesternFastShooters commented 9 months ago
  1. Better boundingbox like this , these points attach that create arrows around this circle boundingbox image image

  2. Arrow need to be optimized like this, users can choose styles of arrow directly image

  3. a new tool -- no-shape lasso image

  4. handwriting strokes recognization Shapes can be transfer into regular shapes in free draw mode reference: https://www.myscript.com/

  5. Support for mindmap Though construct a mindmap with existed shapes such as rectangles and arrows,but a better mind map can support automate layout algorithm of mindmap and shortcut key that creates next node of mindmap. like this image

zsviczian commented 9 months ago

This is something I wanted for a long time... it will happen eventually

WesternFastShooters commented 9 months ago

This is something I wanted for a long time... it will happen eventually

Some time before,I wanted to code for these features in some excalidraw`s fork,but the complexity blocks me.

Loners-xq commented 9 months ago

@zsviczian Dear Developer, I saw you reply in a number of topics that you will consider adding foldable mind mapping at a later stage, and I am very excited because it is a great and useful feature. I saw someone implement it through a script, here is the URL, you can have a look. Look forward to seeing him soon. Best wishes for you.

https://www.bilibili.com/video/BV1Je411e7Qx/?spm_id_from=333.999.0.0 https://www.bilibili.com/read/cv25597391/?from=readlist

zsviczian commented 9 months ago

@Loners-xq - Wow! this is great. Can you help translate, maybe convince the developer to publish this as a script in the script library?

Bowen-0x00 commented 9 months ago

@Loners-xq I am the author of this video. In order to implement the mind map functionality, I made some modifications to the obsidian-excalidraw-plugin, and I will consider adding it to the script library after refining the code.

The modifications include exposing hooks for onPointerDown and onKeyDown. Then, in the mouse events, I maintain the tree structure and show/hide corresponding elements (using element.opacity=0 and lock=true for hiding, and restoring the backed-up opacity and lock when showing). Then, I call the slightly modified Mindmap format script (fixing some coordinate calculation issues). In the keyboard events, I add new elements and also call the Mindmap format script.

On a side note, I have implemented the fourth point mentioned above, which allows converting hand-drawn quadrilaterals, triangles, circles, and straight lines into regular shapes. For more details, you can visit https://www.bilibili.com/read/cv26287346/ Here is the code provided for reference:

let editorElement = document.getElementById('iink-editor');
let state = api.getAppState()
function register() {
    if (!editorElement) {
        editorElement = document.createElement('div');
        editorElement.id = "iink-editor";
        editorElement.style.display = 'block'; // Hide the element
        editorElement.style.width = `${state.width}px`;
        editorElement.style.height = `${state.height}px`;
        editorElement.style.position = "absolute";
        document.body.appendChild(editorElement);
    }
    iink.register(editorElement, {
        recognitionParams: {
            type: 'DIAGRAM',
            protocol: 'WEBSOCKET',
            server: {
            applicationKey: 'a10a3491-9281-4c18-a922-212daea0d5d8',
            hmacKey: 'afd10a3d-8e68-4ab1-8a09-ad24fb9d70cc'
            }
        }
    });
}

if (!editorElement) {
    const script = document.createElement("script");
    script.type = "text/javascript";
    script.src = "http://localhost:55555/myscript/node_modules/iink-js/dist/iink.min.js";
    script.onload = () => {
        console.log("iink.min.js loaded")
        register()

    }

    document.body.appendChild(script);
}

if (editorElement?.editor) {
    console.log("editorElement.editor")

    let selectedEl = ea.getViewSelectedElement();
    if (!selectedEl) {
        let elements = await api.getSceneElements()
         selectedEl =elements.filter(el=>el.type=="freedraw").sort((a,b) => {
            b.updated - a.updated
        })[0]
    }
    let xs = []
    let ys = []

    scrollXFixed = state.scrollX - (1-state.zoom.value) * state.width
    scrollYFixed = state.scrollY - (1-state.zoom.value) * state.height
    if(selectedEl.type == "freedraw") {
        selectedEl.points.forEach(p => {
            xs.push(p[0])
            ys.push(p[1])
        })
    }
    xMin = Math.min(...xs)
    yMin = Math.min(...ys)
    xMax = Math.max(...xs)
    yMax = Math.max(...ys)

    width = xMax - xMin
    height = yMax - yMin
    centerX = (xMin + xMax) / 2
    centerY = (yMin + yMax) / 2
    offsetX = state.width / 2 - centerX//centerX = state.width / 2
    offsetY = state.height / 2 - centerY
    xs = xs.map(p => p+=offsetX)
    ys = ys.map(p => p+=offsetY)

    const strokes = { "events": [{
        "pointerType": "PEN",
        "pointerId": 1,
        "x": xs,
        "y": ys
        }
    ]}

    const processGestures = false;
    try{
        await editorElement.editor.clear()
    }
    catch(e){
        console.log(`error: ${e}`)
    }
    await editorElement.editor.pointerEvents(strokes)
    await editorElement.editor.convert()
    let parser = new DOMParser();
    let svgDoc = parser.parseFromString(editorElement.outerHTML, "image/svg+xml");
    let svgModel = svgDoc.querySelectorAll('svg[data-layer="MODEL"]')[0];
    let diagramArea = svgModel.querySelectorAll('[id$="DiagramArea"]')[0];
    if (diagramArea) {
        let gModels = diagramArea.querySelectorAll('g[id^="MODEL"]')

        let viewTransform = svgDoc.querySelector('#MODEL-viewTransform')
        let transform = viewTransform.getAttribute('transform')
        transform = transform.substring(7, transform.length-1).split(',').map(el => Number(el))

        let points = []
        function addLine() {
            points.map(p => {p[0] *= transform[0], p[1] *= transform[3]})
            points.map(p => {p[0] += (selectedEl.x - offsetX), p[1] +=  (selectedEl.y - offsetY)})
            ea.addLine(points)
            points = []
        }
        ea.style.backgroundColor = selectedEl.backgroundColor;
        ea.style.strokeColor = selectedEl.strokeColor
        ea.style.strokeWidth = selectedEl.strokeWidth + 1
        ea.style.roughness = 0
        ea.style.fillStyle = "solid";
        gModels.forEach(model => {
            let child = model.firstElementChild
            if (child.tagName == "line") {
                let x1 = Number(child.getAttribute('x1'))
                let x2 = Number(child.getAttribute('x2'))
                let y1 = Number(child.getAttribute('y1'))
                let y2 = Number(child.getAttribute('y2'))
                points.push([x1, y1])
                points.push([x2, y2])
            }
            else {
                if (points.length > 0) {
                    addLine()
                }
                if (child.tagName == "path") {
                    let path = child.getAttribute('d')
                    let cmds = path.split(/(?=[AaCcHhLlMmQqSsTtVvZz])/);
                    let currentX = 0, currentY = 0
                    ciecleList = []
                    cmds.forEach(cmd => {
                        cmd = cmd.replace(/,/g, ' ')
                        cmdChar = cmd.split(' ')
                        if (cmdChar[0].toLowerCase() == 'm') {
                            currentX = Number(cmdChar[1])
                            currentY = Number(cmdChar[2])
                        }
                        else if (cmdChar[0].toLowerCase() == 'a') {
                            ciecleList.push(cmdChar)
                        }
                        if (ciecleList.length == 2 && ciecleList[0][1] == ciecleList[1][1] &&
                            ciecleList[0][2] == ciecleList[1][2] && 
                            ciecleList[0][3] == ciecleList[1][3] &&
                            ciecleList[0][4] == ciecleList[1][4] &&
                            ciecleList[0][5] == ciecleList[1][5] &&
                            ciecleList[0][7] == ciecleList[1][7]) {
                            currentXFixed = currentX * transform[0]
                            currentYFixed = currentY * transform[3]
                            currentXFixed += (selectedEl.x - offsetX)
                            currentYFixed += (selectedEl.y - offsetY)
                            let rx = ciecleList[0][1] * transform[0]
                            let ry = ciecleList[0][2] * transform[3]
                            ea.addEllipse(currentXFixed - 2*rx, currentYFixed-ry, rx * 2, ry * 2)
                            ciecleList = []
                        }

                    })
                }
            }
        })
        if (gModels.length == 4 && points.length == 8) {// for rect
            //set(x).length = set(y).length == 2
        }
        // ea.addLine([...new Set(points.map(JSON.stringify))].map(JSON.parse))
        if (points.length > 0) {
            addLine()
        }

        ea.addElementsToView(false,false);
        ea.deleteViewElements([selectedEl]);
    }
    else {
        new Notice("can't convert");
        console.log(`xs: ${xs}`)
    }
}