Node-red-contrib-ui-svg: scale without moving

Hi,
although I did already read and try for quite some time yet, I still did not figure out how to do the following (simple) thing:
In an svg graphic I want to scale one object (path) in the same place. Actually it is a arrow symbol which should just change its height but not its length.

When I scale it, it is also moving. From svg articles I understand why it does move. And I found article how to translate it first to the origin, then scale and then translate back. But all those solutions used the position of the symbol to translate it. But how do I know the position values, when I want to initate the scaling from a Nodered flow?

Hi @PastCoder,

Perhaps I did not understood your requirement correctly, but I assume you want to scale relative to the center of the shape? If so you can specify which origin should be used for your transformation. For example use the center of the shape while scaling:

"style": {
   "transform-origin": "center",
   "transform": "scale(3,1)"
}

This example flow:

[{"id": "3b053fec.6e949","type": "ui_svg_graphics","z": "8b52e098cd5f73fd","group": "87e79a83.f45268","order": 1,"width": "10","height": "10","svgString": "<svg width=\"400\" height=\"400\">\n<circle id=\"my_circle\" cx=\"200\" cy=\"200\" r=\"40\" stroke=\"black\" stroke-width=\"3\" fill=\"red\" />\n<line x1=\"0\" y1=\"200\" x2=\"400\" y2=\"200\" style=\"stroke:rgb(0,0,255);stroke-width:2\" />\n<line x1=\"200\" y1=\"0\" x2=\"200\" y2=\"400\" style=\"stroke:rgb(0,0,255);stroke-width:2\" />\n</svg>","clickableShapes": [{"targetId": "path[id^=\"ball_\"]","action": "click","payload": "circle","payloadType": "str","topic": "circle"}],"javascriptHandlers": [],"smilAnimations": [],"bindings": [],"showCoordinates": false,"autoFormatAfterEdit": false,"showBrowserErrors": false,"showBrowserEvents": false,"enableJsDebugging": false,"sendMsgWhenLoaded": false,"noClickWhenDblClick": false,"outputField": "payload","editorUrl": "https://drawsvg.org/drawsvg.html","directory": "","panning": "disabled","zooming": "disabled","panOnlyWhenZoomed": false,"doubleClickZoomEnabled": false,"mouseWheelZoomEnabled": false,"dblClickZoomPercentage": "150","cssString": "div.ui-svg svg{\ncolor: var(--nr-dashboard-widgetColor);\nfill: currentColor !important;\n}\ndiv.ui-svg path {\nfill: inherit;\n}","name": "","x": 680,"y": 2420,"wires": [[]]},{"id": "e4241244.0efc3","type": "inject","z": "8b52e098cd5f73fd","name": "Scale x * 3","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle\",\"style\":{\"transform-origin\":\"center\",\"transform\":\"scale(3,1)\"}}]","payloadType": "json","x": 420,"y": 2420,"wires": [["3b053fec.6e949"]]},{"id": "2a9bf0221c1c145b","type": "inject","z": "8b52e098cd5f73fd","name": "Scale y * 3","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle\",\"style\":{\"transform-origin\":\"center\",\"transform\":\"scale(1,3)\"}}]","payloadType": "json","x": 420,"y": 2460,"wires": [["3b053fec.6e949"]]},{"id": "bf0514d09b29ece4","type": "inject","z": "8b52e098cd5f73fd","name": "Scale original","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle\",\"style\":{\"transform-origin\":\"center\",\"transform\":\"scale(1,1)\"}}]","payloadType": "json","x": 430,"y": 2540,"wires": [["3b053fec.6e949"]]},{"id": "6f9bf1136adde642","type": "inject","z": "8b52e098cd5f73fd","name": "Scale x and y * 3","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle\",\"style\":{\"transform-origin\":\"center\",\"transform\":\"scale(3,3)\"}}]","payloadType": "json","x": 440,"y": 2500,"wires": [["3b053fec.6e949"]]},{"id": "87e79a83.f45268","type": "ui_group","name": "Zoom center demo","tab": "935e3c31.38046","order": 1,"disp": true,"width": "14","collapse": false,"className": ""},{"id": "935e3c31.38046","type": "ui_tab","name": "SVG demo","icon": "dashboard","disabled": false,"hidden": false}]

Shows how to scale a circle relative to the circle's center:
demo_scaling

Please let me know if this is what you mean?
Bart

Hi @BartButenaers

thanks for your reply. What you did in the demo is excatly what I am looking for.
But I am still struggling. If I apply your transformations to a circle in an own draing (created with the editor), it moves again.

[
    {
        "id": "14dfb853caf0f9e6",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "3b053fec.6e949",
        "type": "ui_svg_graphics",
        "z": "14dfb853caf0f9e6",
        "group": "ee9f3da5a95984a5",
        "order": 1,
        "width": "6",
        "height": "10",
        "svgString": "<svg width=\"400\" height=\"400\">\n<circle id=\"my_circle\" cx=\"200\" cy=\"200\" r=\"40\" stroke=\"black\" stroke-width=\"3\" fill=\"red\" />\n<line x1=\"0\" y1=\"200\" x2=\"400\" y2=\"200\" style=\"stroke:rgb(0,0,255);stroke-width:2\" />\n<line x1=\"200\" y1=\"0\" x2=\"200\" y2=\"400\" style=\"stroke:rgb(0,0,255);stroke-width:2\" />\n</svg>",
        "clickableShapes": [
            {
                "targetId": "path[id^=\"ball_\"]",
                "action": "click",
                "payload": "circle",
                "payloadType": "str",
                "topic": "circle"
            }
        ],
        "javascriptHandlers": [],
        "smilAnimations": [],
        "bindings": [],
        "showCoordinates": false,
        "autoFormatAfterEdit": false,
        "showBrowserErrors": false,
        "showBrowserEvents": false,
        "enableJsDebugging": false,
        "sendMsgWhenLoaded": false,
        "noClickWhenDblClick": false,
        "outputField": "payload",
        "editorUrl": "https://drawsvg.org/drawsvg.html",
        "directory": "",
        "panning": "disabled",
        "zooming": "disabled",
        "panOnlyWhenZoomed": false,
        "doubleClickZoomEnabled": false,
        "mouseWheelZoomEnabled": false,
        "dblClickZoomPercentage": "150",
        "cssString": "div.ui-svg svg{\ncolor: var(--nr-dashboard-widgetColor);\nfill: currentColor !important;\n}\ndiv.ui-svg path {\nfill: inherit;\n}",
        "name": "",
        "x": 980,
        "y": 420,
        "wires": [
            []
        ]
    },
    {
        "id": "e4241244.0efc3",
        "type": "inject",
        "z": "14dfb853caf0f9e6",
        "name": "Scale x * 3",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle\",\"style\":{\"transform-origin\":\"center\",\"transform\":\"scale(3,1)\"}}]",
        "payloadType": "json",
        "x": 700,
        "y": 380,
        "wires": [
            [
                "3b053fec.6e949"
            ]
        ]
    },
    {
        "id": "2a9bf0221c1c145b",
        "type": "inject",
        "z": "14dfb853caf0f9e6",
        "name": "Scale y * 3",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle\",\"style\":{\"transform-origin\":\"center\",\"transform\":\"scale(1,3)\"}}]",
        "payloadType": "json",
        "x": 700,
        "y": 420,
        "wires": [
            [
                "3b053fec.6e949"
            ]
        ]
    },
    {
        "id": "bf0514d09b29ece4",
        "type": "inject",
        "z": "14dfb853caf0f9e6",
        "name": "Scale original",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle\",\"style\":{\"transform-origin\":\"center\",\"transform\":\"scale(1,1)\"}}]",
        "payloadType": "json",
        "x": 710,
        "y": 500,
        "wires": [
            [
                "3b053fec.6e949"
            ]
        ]
    },
    {
        "id": "6f9bf1136adde642",
        "type": "inject",
        "z": "14dfb853caf0f9e6",
        "name": "Scale x and y * 3",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle\",\"style\":{\"transform-origin\":\"center\",\"transform\":\"scale(3,3)\"}}]",
        "payloadType": "json",
        "x": 720,
        "y": 460,
        "wires": [
            [
                "3b053fec.6e949"
            ]
        ]
    },
    {
        "id": "6160073de7ed2c27",
        "type": "ui_svg_graphics",
        "z": "14dfb853caf0f9e6",
        "group": "ee9f3da5a95984a5",
        "order": 1,
        "width": "6",
        "height": "10",
        "svgString": "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"150px\" height=\"150px\" viewBox=\"0 0 150 150\" preserveAspectRatio=\"xMidYMid meet\"><rect id=\"svgEditorBackground\" x=\"0\" y=\"0\" width=\"150\" height=\"150\" style=\"fill: none; stroke: none;\"/><circle id=\"my_circle1\" cx=\"29.52\" cy=\"47.53\" style=\"fill:rosybrown;stroke:black;stroke-width:1px\" r=\"10.00\"/></svg>",
        "clickableShapes": [
            {
                "targetId": "path[id^=\"ball_\"]",
                "action": "click",
                "payload": "circle",
                "payloadType": "str",
                "topic": "circle"
            }
        ],
        "javascriptHandlers": [],
        "smilAnimations": [],
        "bindings": [],
        "showCoordinates": false,
        "autoFormatAfterEdit": false,
        "showBrowserErrors": false,
        "showBrowserEvents": false,
        "enableJsDebugging": false,
        "sendMsgWhenLoaded": false,
        "noClickWhenDblClick": false,
        "outputField": "payload",
        "editorUrl": "https://drawsvg.org/drawsvg.html",
        "directory": "",
        "panning": "disabled",
        "zooming": "disabled",
        "panOnlyWhenZoomed": false,
        "doubleClickZoomEnabled": false,
        "mouseWheelZoomEnabled": false,
        "dblClickZoomPercentage": "150",
        "cssString": "div.ui-svg svg{\ncolor: var(--nr-dashboard-widgetColor);\nfill: currentColor !important;\n}\ndiv.ui-svg path {\nfill: inherit;\n}",
        "name": "",
        "x": 1080,
        "y": 720,
        "wires": [
            []
        ]
    },
    {
        "id": "4191acf0fec8bc0b",
        "type": "inject",
        "z": "14dfb853caf0f9e6",
        "name": "Scale x * 3",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle1\",\"style\":{\"transform-origin\":\"center\",\"transform\":\"scale(3,1)\"}}]",
        "payloadType": "json",
        "x": 700,
        "y": 660,
        "wires": [
            [
                "6160073de7ed2c27"
            ]
        ]
    },
    {
        "id": "1459a499de511cf6",
        "type": "inject",
        "z": "14dfb853caf0f9e6",
        "name": "Scale y * 3",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle1\",\"style\":{\"transform-origin\":\"center\",\"transform\":\"scale(1,3)\"}}]",
        "payloadType": "json",
        "x": 700,
        "y": 700,
        "wires": [
            [
                "6160073de7ed2c27"
            ]
        ]
    },
    {
        "id": "81fdb0b6b5336b80",
        "type": "inject",
        "z": "14dfb853caf0f9e6",
        "name": "Scale original",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle1\",\"style\":{\"transform-origin\":\"center\",\"transform\":\"scale(1,1)\"}}]",
        "payloadType": "json",
        "x": 710,
        "y": 780,
        "wires": [
            [
                "6160073de7ed2c27"
            ]
        ]
    },
    {
        "id": "780aa4a9a9bdce3b",
        "type": "inject",
        "z": "14dfb853caf0f9e6",
        "name": "Scale x and y * 3",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle1\",\"style\":{\"transform-origin\":\"center\",\"transform\":\"scale(3,3)\"}}]",
        "payloadType": "json",
        "x": 720,
        "y": 740,
        "wires": [
            [
                "6160073de7ed2c27"
            ]
        ]
    },
    {
        "id": "ee9f3da5a95984a5",
        "type": "ui_group",
        "name": "TEST",
        "tab": "90ecd555.7a4478",
        "order": 8,
        "disp": true,
        "width": "6",
        "collapse": true,
        "className": ""
    },
    {
        "id": "90ecd555.7a4478",
        "type": "ui_tab",
        "name": "Charts",
        "icon": "fa-bar-chart",
        "order": 9,
        "disabled": false,
        "hidden": false
    }
]

@PastCoder,
If this would be a subscription for the "how to get Bart getting nuts" contest, you would have most probably ended up in the top 3 :wink: At first, I had no clue what was wrong in your example...

I have slightly adapted your flow:

[{"id": "a5250a605ceb8f5e","type": "ui_svg_graphics","z": "5d812160e1626c40","group": "f598287ef6b5b381","order": 1,"width": "6","height": "10","svgString": "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"150px\" height=\"150px\" viewBox=\"0 0 150 150\" >\n  <rect id=\"svgEditorBackground\" x=\"0\" y=\"0\" width=\"150\" height=\"150\" style=\"fill: none; stroke: none;\" />\n  <circle id=\"my_circle1\" cx=\"29.52\" cy=\"47.53\" style=\"fill:rosybrown;stroke:black;stroke-width:1px\" r=\"10.00\" />\n</svg>","clickableShapes": [{"targetId": "path[id^=\"ball_\"]","action": "click","payload": "circle","payloadType": "str","topic": "circle"}],"javascriptHandlers": [],"smilAnimations": [],"bindings": [],"showCoordinates": false,"autoFormatAfterEdit": false,"showBrowserErrors": false,"showBrowserEvents": false,"enableJsDebugging": false,"sendMsgWhenLoaded": false,"noClickWhenDblClick": false,"outputField": "payload","editorUrl": "https://drawsvg.org/drawsvg.html","directory": "","panning": "disabled","zooming": "disabled","panOnlyWhenZoomed": false,"doubleClickZoomEnabled": false,"mouseWheelZoomEnabled": false,"dblClickZoomPercentage": "150","cssString": "div.ui-svg svg{\ncolor: var(--nr-dashboard-widgetColor);\nfill: currentColor !important;\n}\ndiv.ui-svg path {\nfill: inherit;\n}","name": "","x": 1080,"y": 720,"wires": [[]]},{"id": "f1417601d9718932","type": "inject","z": "5d812160e1626c40","name": "Scale x * 3","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle1\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(3,1)\"}}]","payloadType": "json","x": 700,"y": 660,"wires": [["a5250a605ceb8f5e"]]},{"id": "cec15f8380430ed4","type": "inject","z": "5d812160e1626c40","name": "Scale y * 3","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle1\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(1,3)\"}}]","payloadType": "json","x": 700,"y": 700,"wires": [["a5250a605ceb8f5e"]]},{"id": "73ac37395be8673a","type": "inject","z": "5d812160e1626c40","name": "Scale original","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle1\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(1,1)\"}}]","payloadType": "json","x": 710,"y": 780,"wires": [["a5250a605ceb8f5e"]]},{"id": "6e4ca4ddacd6441a","type": "inject","z": "5d812160e1626c40","name": "Scale x and y * 3","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_style\",\"selector\":\"#my_circle1\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(3,3)\"}}]","payloadType": "json","x": 720,"y": 740,"wires": [["a5250a605ceb8f5e"]]},{"id": "f598287ef6b5b381","type": "ui_group","name": "Non-working zoom demo","tab": "935e3c31.38046","order": 8,"disp": true,"width": "10","collapse": true,"className": ""},{"id": "935e3c31.38046","type": "ui_tab","name": "SVG demo","icon": "dashboard","disabled": false,"hidden": false}]

The only thing that has changed is, that I now set two transform properties:

{
   "command": "update_style",
   "selector": "#my_circle1",
   "style": {
      "transform-box": "fill-box",
      "transform-origin": "center",
      "transform": "scale(3,1)"
   }
}

In my first attempt above, I had set the transform-origin to "center". So we say that the (scale) transform needs to be applied around the center.

But the center of what?

With the transform-box property you can specify to which box the transform related properties are related. The default/initial value is "view-box", which explains why my flow worked and yours not:

  • In my flow the circle was positioned in the center of my viewbox, so the center of my circle was exactly in the center of the viewbox. The scaling was around the center of my viewbox, but it looked like it was around the center of my circle:

    image

  • In your flow the circle was not positioned in the center of the viewbox, so your circle starts moving when you started scaling around the center of your viewbox:

    image

By specifying that the "transform-box" needs to be "fill-box", in fact we say that the shape its bounding box needs to be used as the reference box.

So we scale now around the center of the bounding box of the circle.

Hopefully that is a bit clear?

1 Like

@PastCoder,
Was my approach ok to solve your problem?
If yes, I can use this material to add a new tutorial on the wiki of the svg Github repository...

Hi,
sorry for my late reply. Last two weeks were quite busy :slight_smile:
The answer is yes and no. It works perfectly now for elements (circle, rectangle). With my "arrow symbol" (in svg terms probably a "shape") I still do not succeed. I tried to put a around, but it did not help.
Sorry for keeping you busy with my low svg knowledge.

Could you please be a bit more specific about what does not work, because this way it is hard to assist you.
I have quickly created a demo flow, that contains a path in the shape of an arrow:

[{"id": "88f6a147db8e38de","type": "ui_svg_graphics","z": "5d812160e1626c40","group": "f598287ef6b5b381","order": 1,"width": "6","height": "10","svgString": "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"150px\" height=\"150px\" viewBox=\"0 0 150 150\" preserveAspectRatio=\"xMidYMid meet\">\n  <rect id=\"svgEditorBackground\" x=\"0\" y=\"0\" width=\"150\" height=\"150\" style=\"fill: none; stroke: none;\"/>\n  <path id=\"my_arrow\" d=\"M40,54 H70 V44 L80,64 L70,84 V74 H40 Z\" style=\"fill:rosybrown;stroke:black;stroke-width:1px\"/>\n<line id=\"e1_line\" x1=\"58\" y1=\"22\" x2=\"58\" y2=\"108\" style=\"stroke:#F00000;fill:none;stroke-width:1px\"/>\n<line id=\"e2_line\" x1=\"0\" y1=\"65\" x2=\"120\" y2=\"65\" style=\"stroke:#F00000;fill:none;stroke-width:1px\"/></svg>","clickableShapes": [{"targetId": "path[id^=\"ball_\"]","action": "click","payload": "circle","payloadType": "str","topic": "circle"}],"javascriptHandlers": [],"smilAnimations": [],"bindings": [],"showCoordinates": false,"autoFormatAfterEdit": false,"showBrowserErrors": false,"showBrowserEvents": false,"enableJsDebugging": false,"sendMsgWhenLoaded": false,"noClickWhenDblClick": false,"outputField": "payload","editorUrl": "https://drawsvg.org/drawsvg.html","directory": "","panning": "disabled","zooming": "disabled","panOnlyWhenZoomed": false,"doubleClickZoomEnabled": false,"mouseWheelZoomEnabled": false,"dblClickZoomPercentage": "150","cssString": "div.ui-svg svg{\ncolor: var(--nr-dashboard-widgetColor);\nfill: currentColor !important;\n}\ndiv.ui-svg path {\nfill: inherit;\n}","name": "","x": 1360,"y": 440,"wires": [[]]},{"id": "8322ec2a9257f794","type": "inject","z": "5d812160e1626c40","name": "Scale x * 2","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_style\",\"selector\":\"#my_arrow\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(2,1)\"}}]","payloadType": "json","x": 980,"y": 380,"wires": [["88f6a147db8e38de"]]},{"id": "9fe59577b852c1e1","type": "inject","z": "5d812160e1626c40","name": "Scale y * 3","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_style\",\"selector\":\"#my_arrow\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(1,2)\"}}]","payloadType": "json","x": 980,"y": 420,"wires": [["88f6a147db8e38de"]]},{"id": "892fcddfb4eb75a2","type": "inject","z": "5d812160e1626c40","name": "Scale original","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_style\",\"selector\":\"#my_arrow\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(1,1)\"}}]","payloadType": "json","x": 990,"y": 500,"wires": [["88f6a147db8e38de"]]},{"id": "9350c6e7d8496f63","type": "inject","z": "5d812160e1626c40","name": "Scale x and y * 2","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_style\",\"selector\":\"#my_arrow\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(2,2)\"}}]","payloadType": "json","x": 1000,"y": 460,"wires": [["88f6a147db8e38de"]]},{"id": "f598287ef6b5b381","type": "ui_group","name": "Non-working zoom demo","tab": "935e3c31.38046","order": 8,"disp": true,"width": "10","collapse": true,"className": ""},{"id": "935e3c31.38046","type": "ui_tab","name": "SVG demo","icon": "dashboard","disabled": false,"hidden": false}]

And to me that seems to be working fine:

arrow_transformation_svg

Hi,
as I am not familiar with svg I am using the editor. There I modified your example and added a second arrow.
Somehow I made somewthing different because the new arrow is not just scaling but moving.
Sorry for being this unprecise. As I am struggling with this topic, I don't know which information is needed (I am just a little lost in the svg space).
Here is my "drawing".

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="150px" height="150px" viewBox="0 0 150 150" preserveAspectRatio="xMinYMin meet">
  <rect id="svgEditorBackground" x="0" y="0" width="150" height="150" style="fill: none; stroke: none;" />
  <path id="my_arrow1" d="M40,54H70V44L80,64L70,84V74H40Z" style="fill:rosybrown;stroke:black;stroke-width:1px" transform="matrix(0.141272 0 0 0.141272 -0.354231 0.105042)" />
  <path d="M0,-2v-2l4,4l-4,4v-2h-4v-4Z" style="fill:rosybrown;stroke:black;vector-effect:non-scaling-stroke;stroke-width:1px" id="my_arrow" transform="matrix(0.587297 0 0 0.587297 9.83505 16.4128)" />
</svg>

The problem seems to be caused because your arrow path already contains a transformation of its own:

Don't know how that transformation arrived there? I assume it is being added by your drawing tool, for example because you have moved your arrow. Could you explain step by step how you have drawn that arrow (which tool, which steps, and so on...).

When you manually remove that transform (i.e. the part in the red border of my screenshot above), you will see that your arrow will move to the origin of your drawing in the dashboard (on the top left side).

The same happens in Node-RED:

  1. Your arrow has a transform that moves it to some location in the drawing. It is a matrix transformation which could be anything (translation, rotation, scaling).
  2. You click on the button of an Inject nodes to inject a message containing a scale transformation.
  3. The svg node will replace the original transformation by the new injected scale transformation.
  4. Because the original transformation is gone, your arrow will go back to the origin of your drawing (in the top left corner).
  5. The new injected scale transform will scale your arrow.

So as a result your arrow will scale correctly, but your arrow is located at the wrong place in your drawing.

I'm afraid there is only one solution possible: Use another SVG drawing software. For example when I move my arrow path in DrawSvg to another location, then DrawSvg changes the coordinates inside the "d" attribute of the path element. Which means that e.g. DrawSvg changes the coordinates of the path. Your drawing tool has taken another road, by keeping the original path coordinates and simply applying a transform to accomplish the same effect.

I first thought we might solve it another way. Because the transform can be a list of multiple transforms that are applied to the shape. For example you can apply multiple transformations to an svg shape like this:

transform="translate(20 20) rotate(90) scale(2)"

These transformations are then applied from the right to the left, i.e. in this example the shape is first scaled, then rotated and at last translated (i.e. moved).

However I don't think (because I quickly tested it...) you can combine a matrix transformation (like yours) this way with another transformation. So you cannot do something like this:

"transform": "matrix(0.587297 0 0 0.587297 9.83505 16.4128) scale(2)"

You can add multiple transformation OR you can use a matrix transformation (which can contain scale/translate/rotate). But you cannot use both by combining a matrix with another transformation.

So I don't see any way how the svg node could be extended to solve this problem...

1 Like

So far I used the editor embedded in the node:

  1. 2022-12-08_17h22_49

  2. 2022-12-08_17h22_55

  3. 2022-12-08_17h27_59

  4. 2022-12-08_17h23_20

But if using a different editor solves the problem, that's fine for me.
Thanks for investigating and explaining!

Thanks for sharing the steps. That really helps to understand your issue.
I am not sure anymore if another editor will help you, because I proposed above to use DrawSvg but you are using that already...

Seems that DrawSvg does not handle all moves in the same way:

  1. I had created (in my example flow above) an arrow path manually from scratch. When I move that around in DrawSvg, it seems to me that DrawSvg changes the coordinates inside the path "d" attribute (instead of applying a transform).
  2. If you create an arrow via the DrawSvg and move that around, then DrawSvg will do this via a transform attribute.

You can see the difference in the following demo:

two_arrows_in_drawsvg

That puts your issue somehow in a different perspective for me.
It would be nice if the svg node could offer you an option to manipulate transformations easier.
Will need to think about that...

I have created a new "custom-transforms" branch on the Github repository of the SVG node.
Would be nice if you could test it, to make sure it is enough to solve your issue.
It is not available on NPM yet, but you can install it directly from my Github repository using following command (from within your .node-red folder):

npm install bartbutenaers/node-red-contrib-ui-svg#custom-transforms

P.S. Note that you need to install Git to make this possible...

Here is my example flow for this new version:

[{"id": "88f6a147db8e38de","type": "ui_svg_graphics","z": "5d812160e1626c40","group": "f598287ef6b5b381","order": 1,"width": "6","height": "10","svgString": "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"150px\" height=\"150px\" viewBox=\"0 0 150 150\" preserveAspectRatio=\"xMinYMin meet\">\n  <rect id=\"svgEditorBackground\" x=\"0\" y=\"0\" width=\"150\" height=\"150\" style=\"fill: none; stroke: none;\" />\n  <path d=\"M0,-2v-2l4,4l-4,4v-2h-4v-4Z\" style=\"fill:rosybrown;stroke:black;vector-effect:non-scaling-stroke;stroke-width:1px\" id=\"my_arrow\" transform=\"matrix(0.587297 0 0 0.587297 9.83505 16.4128)\" />\n</svg>","clickableShapes": [{"targetId": "path[id^=\"ball_\"]","action": "click","payload": "circle","payloadType": "str","topic": "circle"}],"javascriptHandlers": [],"smilAnimations": [],"bindings": [],"showCoordinates": false,"autoFormatAfterEdit": false,"showBrowserErrors": false,"showBrowserEvents": false,"enableJsDebugging": false,"sendMsgWhenLoaded": false,"noClickWhenDblClick": false,"outputField": "payload","editorUrl": "https://drawsvg.org/drawsvg.html","directory": "","panning": "disabled","zooming": "disabled","panOnlyWhenZoomed": false,"doubleClickZoomEnabled": false,"mouseWheelZoomEnabled": false,"dblClickZoomPercentage": "150","cssString": "div.ui-svg svg{\ncolor: var(--nr-dashboard-widgetColor);\nfill: currentColor !important;\n}\ndiv.ui-svg path {\nfill: inherit;\n}","name": "","x": 1360,"y": 440,"wires": [["6b024b0617b3ad9b"]]},{"id": "5715092cf878b0db","type": "inject","z": "5d812160e1626c40","name": "Analyze transform","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"analyze_transform\",\"selector\":\"#my_arrow\"}]","payloadType": "json","x": 1110,"y": 440,"wires": [["88f6a147db8e38de"]]},{"id": "6b024b0617b3ad9b","type": "debug","z": "5d812160e1626c40","name": "debug 22","active": true,"tosidebar": true,"console": false,"tostatus": false,"complete": "true","targetType": "full","statusVal": "","statusType": "auto","x": 1560,"y": 440,"wires": []},{"id": "bfa70ccd7e5b1b0d","type": "inject","z": "5d812160e1626c40","name": "Scale x * 2","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_transform\",\"selector\":\"#my_arrow\",\"scaleX\":2}]","payloadType": "json","x": 1080,"y": 480,"wires": [["88f6a147db8e38de"]]},{"id": "c809c7ad4a83325a","type": "inject","z": "5d812160e1626c40","name": "Scale y * 2","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_transform\",\"selector\":\"#my_arrow\",\"scaleY\":2}]","payloadType": "json","x": 1080,"y": 520,"wires": [["88f6a147db8e38de"]]},{"id": "2991095404cf8e12","type": "inject","z": "5d812160e1626c40","name": "Translate y + 40","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_transform\",\"selector\":\"#my_arrow\",\"translateY\":40}]","payloadType": "json","x": 1100,"y": 600,"wires": [["88f6a147db8e38de"]]},{"id": "45854b3d46bc3aaf","type": "inject","z": "5d812160e1626c40","name": "Translate x + 40","props": [{"p": "payload"},{"p": "topic","vt": "str"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "[{\"command\":\"update_transform\",\"selector\":\"#my_arrow\",\"translateX\":40}]","payloadType": "json","x": 1100,"y": 560,"wires": [["88f6a147db8e38de"]]},{"id": "f598287ef6b5b381","type": "ui_group","name": "Arrow transformation demo","tab": "935e3c31.38046","order": 8,"disp": true,"width": "10","collapse": true,"className": ""},{"id": "935e3c31.38046","type": "ui_tab","name": "SVG demo","icon": "dashboard","disabled": false,"hidden": false}]

This branch offers two new commands:

"analyse-transform" command

I have added this command because it seems to be very confusing what the current transform of a shape is, and to analyze the transform.

When you inject the following message:

{
   "command": "analyze_transform",
   "selector": "#my_arrow"
}

You will get an output message like this:

{
   "topic":"analyze_transform",
   "payload": [{
      "elementId": "my_arrow",
      "transform": "matrix(1.17459 0 0 1.17459 174.278 86.8884)",
      "matrix": {
         "a": 1.1745939254760742,
         "b": 0,
         "c": 0,
         "d": 1.1745939254760742,
         "e": 174.27821723630467,
         "f": 86.88844886748956},
      "decomposedMatrix": {
        "translateX": 174.27821723630467,
         "translateY": 86.88844886748956,
         "scaleX": 1.1745939254760742,
         "scaleY": 1.1745939254760742,
         "skewX": 0,
         "skewY": 0,
         "rotation": 0 }
      }
   ]}

Some explanation of this output message:

  • "transform": is the transformation of the shape, which can be a chain of multiple transformations.

  • "matrix": this is the internal matrix representation of the "transform" (see previous property), which your browser is using to transform your shape.

    P.S. In this case it is equal to the transform because the transform string also contains a matrix. However the transform string might also contain expressions like "translate(40, 50) scale(3)".

  • "decomposedMatrix": this is a human readable analysis of the matrix. In case you are not familiar with matrices, you can find easily which transformations your matrix includes. This way you can analyze the matrix that DrawSvg has been added, and determine why it is has been added, and what it will be doing...

"update_transform" command

In my previous example flows, I had send input messages to use a complete new "transform" value. So the original transform value was removed, and overwritten by this new value.

But like in your case, you want to keep the original transform and only manipulate it.
Using this command you can inject a message that contains on ore more properties to change the current transformation only partially:

{
   "command": "update_transform",
   "selector": "#my_arrow",
   "scaleX": 2,
   "scaleY": 3,
   "translateX": 40,
   "translateY": 50,
   "skewX": 45,
   "skewY": 45,
   "rotation": 90,
}

All those transformation properties are optional. Only the one that you have specified in your input message will be used.

Below you can see a demo of the above demo flow, based on your arrow that already has an existing matrix transformation (which we are going to manipulate by injecting input messages):

arrow_transforms

Hopefully this fits your needs...

Hi @BartButenaers
I just tried to do so. Because I am using NodeRed in iobroker, I assumed that the correct folder is:
/opt/iobroker/node_modules/node-red
It completed without issues:

After this my dashboard is still working, but when I try to open the NodeRed editor, it stop at node 36 or 93 (just hangs).

I 'think' that might indeed be the correct folder.

Not sure what you mean with that. Does iobroker gives number id's to all nodes? And do you mean your Node-RED doesn't work anymore due to my simple install command?

NodeRed itself seems to work as my dashboard is still operating as it was before.
But when I start the editor, it starts to load the nodes but stops somewhere inbetween. When loading the nodes, the progress is shown. There is moves up to node 36 of 93 nodes. It is not an ID for the node - just the count when loading.

Cannot explain that, because my changes were only on the backend side not on the fronted side...
Do you see any errors in your browser console log?

Yes, there are errors:

Not sure to what those errors mean, but they are not related to the svg node...
If anybody else has an idea what could cause the flow editor to stop, please share your thoughts here!

Are there sooner extra browser plugins enabled ?

1 Like

Seems that I executed the install in the wrong folder.
As it was broken anyhow, I tried again.
This time in /opt/iobroker/node_modules/iobroker.node-red

Now the edit works again. Hopefully I did not break anything with the wrong folder....

Now both arrows (yours and mine) seem to move instead of pure rescaling.
How could I verify that your updated code is used?

Meanwhile I used an alternative graphical representation while I don't manage to scale.
So there is no need to invest lots of your time - except to provide this for the future (and maybe I adjust then again to use it :slight_smile: )

current flow:

[{"id":"54b1ccabb7f5ce84","type":"tab","label":"Flow 1","disabled":false,"info":"","env":[]},{"id":"88f6a147db8e38de","type":"ui_svg_graphics","z":"54b1ccabb7f5ce84","group":"ee9f3da5a95984a5","order":1,"width":"6","height":"10","svgString":"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"150px\" height=\"150px\" viewBox=\"0 0 150 150\" preserveAspectRatio=\"xMinYMin meet\">\n  <rect id=\"svgEditorBackground\" x=\"0\" y=\"0\" width=\"150\" height=\"150\" style=\"fill:none;stroke:none\" />\n  <path id=\"my_arrow1\" d=\"M40,54H70V44L80,64L70,84V74H40Z\" style=\"fill:rosybrown;stroke:black;stroke-width:1px\" transform=\"matrix(0.418365 0 0 0.418365 -3.82437 -6.19621)\" />\n  <path d=\"M0,-2v-2l4,4l-4,4v-2h-4v-4Z\" style=\"fill:rosybrown;stroke:black;vector-effect:non-scaling-stroke;stroke-width:1px\" id=\"my_arrow\" transform=\"matrix(2.46665 0 0 2.46665 31.7608 46.639)\" />\n</svg>","clickableShapes":[{"targetId":"path[id^=\"ball_\"]","action":"click","payload":"circle","payloadType":"str","topic":"circle"}],"javascriptHandlers":[],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":true,"showBrowserErrors":false,"showBrowserEvents":false,"enableJsDebugging":false,"sendMsgWhenLoaded":false,"noClickWhenDblClick":false,"outputField":"payload","editorUrl":"https://drawsvg.org/drawsvg.html","directory":"","panning":"disabled","zooming":"disabled","panOnlyWhenZoomed":false,"doubleClickZoomEnabled":false,"mouseWheelZoomEnabled":false,"dblClickZoomPercentage":"150","cssString":"div.ui-svgsvg{\ncolor:var(--nr-dashboard-widgetColor);\nfill:currentColor!important;\n}\ndiv.ui-svgpath{\nfill:inherit;\n}","name":"","x":800,"y":300,"wires":[[]]},{"id":"8322ec2a9257f794","type":"inject","z":"54b1ccabb7f5ce84","name":"Scalex*2","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"command\":\"update_style\",\"selector\":\"#my_arrow\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(2,1)\"}}]","payloadType":"json","x":360,"y":240,"wires":[["88f6a147db8e38de"]]},{"id":"9fe59577b852c1e1","type":"inject","z":"54b1ccabb7f5ce84","name":"Scaley*3","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"command\":\"update_style\",\"selector\":\"#my_arrow\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(1,2)\"}}]","payloadType":"json","x":360,"y":280,"wires":[["88f6a147db8e38de"]]},{"id":"892fcddfb4eb75a2","type":"inject","z":"54b1ccabb7f5ce84","name":"Scaleoriginal","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"command\":\"update_style\",\"selector\":\"#my_arrow\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(1,1)\"}}]","payloadType":"json","x":370,"y":360,"wires":[["88f6a147db8e38de"]]},{"id":"9350c6e7d8496f63","type":"inject","z":"54b1ccabb7f5ce84","name":"Scalexandy*2","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"command\":\"update_style\",\"selector\":\"#my_arrow\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(2,2)\"}}]","payloadType":"json","x":380,"y":320,"wires":[["88f6a147db8e38de"]]},{"id":"1f8337fc01a8acab","type":"inject","z":"54b1ccabb7f5ce84","name":"Scalex*2","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"command\":\"update_style\",\"selector\":\"#my_arrow1\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(2,1)\"}}]","payloadType":"json","x":440,"y":520,"wires":[["88f6a147db8e38de"]]},{"id":"87d9cf14b246fac1","type":"inject","z":"54b1ccabb7f5ce84","name":"Scaley*3","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"command\":\"update_style\",\"selector\":\"#my_arrow1\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(1,2)\"}}]","payloadType":"json","x":440,"y":560,"wires":[["88f6a147db8e38de"]]},{"id":"199ee3563d0e58c0","type":"inject","z":"54b1ccabb7f5ce84","name":"Scaleoriginal","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"command\":\"update_style\",\"selector\":\"#my_arrow1\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(1,1)\"}}]","payloadType":"json","x":450,"y":640,"wires":[["88f6a147db8e38de"]]},{"id":"df0935bbcfb180bc","type":"inject","z":"54b1ccabb7f5ce84","name":"Scalexandy*2","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"command\":\"update_style\",\"selector\":\"#my_arrow1\",\"style\":{\"transform-box\":\"fill-box\",\"transform-origin\":\"center\",\"transform\":\"scale(2,2)\"}}]","payloadType":"json","x":460,"y":600,"wires":[["88f6a147db8e38de"]]},{"id":"ee9f3da5a95984a5","type":"ui_group","name":"TEST","tab":"90ecd555.7a4478","order":8,"disp":true,"width":"6","collapse":true,"className":""},{"id":"90ecd555.7a4478","type":"ui_tab","name":"Charts","icon":"fa-bar-chart","order":9,"disabled":false,"hidden":false}]