diff --git a/custom_marker_functions.md b/custom_marker_functions.md
new file mode 100644
index 00000000000..8e10ead1209
--- /dev/null
+++ b/custom_marker_functions.md
@@ -0,0 +1,163 @@
+# Custom Marker Functions
+
+This document describes how to use custom SVG marker functions in plotly.js scatter plots.
+
+## Overview
+
+You can now pass a custom function directly as the `marker.symbol` value to create custom marker shapes. This provides a simple, flexible way to extend the built-in marker symbols without any registration required.
+
+## Function Signature
+
+Custom marker functions receive:
+
+```javascript
+function customMarker(r, customdata) {
+ // r: radius/size of the marker (half of marker.size)
+ // customdata: the value from trace.customdata[i] for this point (optional)
+
+ // Return an SVG path string centered at (0,0)
+ return 'M...Z';
+}
+```
+
+**Simple markers** can use just `(r)`:
+```javascript
+function diamond(r) {
+ return 'M' + r + ',0L0,' + r + 'L-' + r + ',0L0,-' + r + 'Z';
+}
+```
+
+**Data-aware markers** use `(r, customdata)`:
+```javascript
+function categoryMarker(r, customdata) {
+ if (customdata === 'high') {
+ return 'M0,-' + r + 'L' + r + ',' + r + 'L-' + r + ',' + r + 'Z'; // up triangle
+ }
+ return 'M0,' + r + 'L' + r + ',-' + r + 'L-' + r + ',-' + r + 'Z'; // down triangle
+}
+```
+
+Note: Rotation is handled automatically via `marker.angle` - your function just returns an unrotated path.
+
+## Usage Examples
+
+### Basic Example
+
+```javascript
+function heartMarker(r) {
+ var x = r * 0.6, y = r * 0.8;
+ return 'M0,' + (-y/2) +
+ 'C' + (-x) + ',' + (-y) + ' ' + (-x*2) + ',' + (-y/3) + ' ' + (-x*2) + ',0' +
+ 'C' + (-x*2) + ',' + (y/2) + ' 0,' + (y) + ' 0,' + (y*1.5) +
+ 'C0,' + (y) + ' ' + (x*2) + ',' + (y/2) + ' ' + (x*2) + ',0' +
+ 'C' + (x*2) + ',' + (-y/3) + ' ' + (x) + ',' + (-y) + ' 0,' + (-y/2) + 'Z';
+}
+
+Plotly.newPlot('myDiv', [{
+ type: 'scatter',
+ x: [1, 2, 3, 4, 5],
+ y: [2, 3, 4, 3, 2],
+ mode: 'markers',
+ marker: {
+ symbol: heartMarker,
+ size: 15,
+ color: 'red'
+ }
+}]);
+```
+
+### Multiple Custom Markers
+
+```javascript
+function star(r) {
+ var path = 'M';
+ for (var i = 0; i < 10; i++) {
+ var radius = i % 2 === 0 ? r : r * 0.4;
+ var ang = (i * Math.PI) / 5 - Math.PI / 2;
+ path += (i === 0 ? '' : 'L') + (radius * Math.cos(ang)).toFixed(2) + ',' + (radius * Math.sin(ang)).toFixed(2);
+ }
+ return path + 'Z';
+}
+
+Plotly.newPlot('myDiv', [{
+ x: [1, 2, 3, 4, 5],
+ y: [2, 3, 4, 3, 2],
+ mode: 'markers',
+ marker: {
+ symbol: [heartMarker, star, 'circle', star, heartMarker],
+ size: 18,
+ color: ['red', 'gold', 'blue', 'orange', 'crimson']
+ }
+}]);
+```
+
+### Data-Driven Markers with customdata
+
+```javascript
+function weatherMarker(r, customdata) {
+ var weather = customdata;
+
+ if (weather.type === 'sunny') {
+ // Sun: circle with rays
+ var cr = r * 0.5;
+ var path = 'M' + cr + ',0A' + cr + ',' + cr + ' 0 1,1 0,-' + cr +
+ 'A' + cr + ',' + cr + ' 0 0,1 ' + cr + ',0Z';
+ for (var i = 0; i < 8; i++) {
+ var ang = i * Math.PI / 4;
+ var x1 = (cr + 2) * Math.cos(ang), y1 = (cr + 2) * Math.sin(ang);
+ var x2 = (cr + r*0.4) * Math.cos(ang), y2 = (cr + r*0.4) * Math.sin(ang);
+ path += 'M' + x1.toFixed(2) + ',' + y1.toFixed(2) + 'L' + x2.toFixed(2) + ',' + y2.toFixed(2);
+ }
+ return path;
+ }
+
+ if (weather.type === 'cloudy') {
+ var cy = r * 0.2;
+ return 'M' + (-r*0.6) + ',' + cy +
+ 'A' + (r*0.35) + ',' + (r*0.35) + ' 0 1,1 ' + (-r*0.1) + ',' + (-cy) +
+ 'A' + (r*0.4) + ',' + (r*0.4) + ' 0 1,1 ' + (r*0.5) + ',' + (-cy*0.5) +
+ 'A' + (r*0.3) + ',' + (r*0.3) + ' 0 1,1 ' + (r*0.7) + ',' + cy +
+ 'L' + (-r*0.6) + ',' + cy + 'Z';
+ }
+
+ // Default: circle
+ return 'M' + r + ',0A' + r + ',' + r + ' 0 1,1 0,-' + r + 'A' + r + ',' + r + ' 0 0,1 ' + r + ',0Z';
+}
+
+Plotly.newPlot('myDiv', [{
+ type: 'scatter',
+ x: [-122.4, -118.2, -87.6],
+ y: [37.8, 34.1, 41.9],
+ customdata: [
+ { type: 'sunny' },
+ { type: 'cloudy' },
+ { type: 'sunny' }
+ ],
+ mode: 'markers',
+ marker: {
+ symbol: weatherMarker,
+ size: 30,
+ color: ['#FFD700', '#708090', '#FFD700']
+ }
+}]);
+```
+
+## SVG Path Commands
+
+Common SVG path commands:
+
+- `M x,y`: Move to (x, y)
+- `L x,y`: Line to (x, y)
+- `H x`: Horizontal line to x
+- `V y`: Vertical line to y
+- `C x1,y1 x2,y2 x,y`: Cubic Bézier curve
+- `Q x1,y1 x,y`: Quadratic Bézier curve
+- `A rx,ry rotation large-arc sweep x,y`: Elliptical arc
+- `Z`: Close path
+
+## Notes
+
+- Custom marker functions work with all marker styling options (color, size, line, etc.)
+- The function is called for each point that uses it
+- Rotation is handled via `marker.angle` - your function returns an unrotated path
+- For best performance, define functions once outside the plot call
diff --git a/devtools/custom_marker_demo.html b/devtools/custom_marker_demo.html
new file mode 100644
index 00000000000..0d8269701d2
--- /dev/null
+++ b/devtools/custom_marker_demo.html
@@ -0,0 +1,172 @@
+
+
+
+
+ Custom Marker Functions Demo
+
+
+
+
+
+
Custom Marker Functions Demo
+
+
+ New Feature: You can now pass custom functions directly as
+ marker.symbol values to create custom marker shapes!
+
+
+
Example: Custom Marker Functions
+
+
+
Code:
+
+
// Define custom marker functions
+function heartMarker(r) {
+ var x = r * 0.6, y = r * 0.8;
+ return 'M0,' + (-y/2) +
+ 'C' + (-x) + ',' + (-y) + ' ' + (-x*2) + ',' + (-y/3) + ' ' + (-x*2) + ',0' +
+ 'C' + (-x*2) + ',' + (y/2) + ' 0,' + (y) + ' 0,' + (y*1.5) +
+ 'C0,' + (y) + ' ' + (x*2) + ',' + (y/2) + ' ' + (x*2) + ',0' +
+ 'C' + (x*2) + ',' + (-y/3) + ' ' + (x) + ',' + (-y) + ' 0,' + (-y/2) + 'Z';
+}
+
+function star5Marker(r) {
+ var points = 5, path = 'M';
+ for (var i = 0; i < points * 2; i++) {
+ var radius = i % 2 === 0 ? r : r * 0.4;
+ var ang = (i * Math.PI) / points - Math.PI / 2;
+ path += (i === 0 ? '' : 'L') +
+ (radius * Math.cos(ang)).toFixed(2) + ',' +
+ (radius * Math.sin(ang)).toFixed(2);
+ }
+ return path + 'Z';
+}
+
+// Use them directly in a plot
+Plotly.newPlot('plot1', [{
+ x: [1, 2, 3, 4, 5],
+ y: [2, 3, 4, 3, 2],
+ mode: 'markers+lines',
+ marker: {
+ symbol: [heartMarker, star5Marker, 'circle', star5Marker, heartMarker],
+ size: 20,
+ color: ['red', 'gold', 'blue', 'orange', 'crimson']
+ }
+}]);
+
+
+
+
+
+
diff --git a/devtools/demos/all_demos.html b/devtools/demos/all_demos.html
new file mode 100644
index 00000000000..199efc872cc
--- /dev/null
+++ b/devtools/demos/all_demos.html
@@ -0,0 +1,185 @@
+
+
+
+
+ Custom Markers - SVG path strings (New API)
+
+
+
+
+ Custom Markers — SVG path strings
+
+ Pass SVG path strings as marker.symbol to create custom shapes.
+ Paths are precomputed at r=20; Plotly scales them by size/20.
+ Use an array for per-point shapes. Rotation uses marker.angle
+ (applied as transform="rotate()" via SVG <use>).
+
+
+
+ 1. Basic Custom Markers
+ Precomputed SVG paths at r=20. Mix with built-in symbol names.
+
+
+// Heart shape at r=20
+var HEART = 'M0,-8C-12,-16 -24,-5.33 -24,0C-24,8 0,16 0,24C0,16 24,8 24,0C24,-5.33 12,-16 0,-8Z';
+
+// 5-point star at r=20
+var STAR = 'M0,-20L4.70,-6.47L19.02,-6.18L7.61,2.47L11.76,16.18' +
+ 'L0,8L-11.76,16.18L-7.61,2.47L-19.02,-6.18L-4.70,-6.47Z';
+
+Plotly.newPlot('plot1', [{
+ x: [1, 2, 3, 4, 5],
+ y: [2, 3, 4, 3, 2],
+ mode: 'markers+lines',
+ marker: {
+ // mix path strings and built-in symbol names
+ symbol: [HEART, STAR, 'circle', STAR, HEART],
+ size: 25,
+ color: ['red', 'gold', 'blue', 'gold', 'red']
+ }
+}]);
+
+
+ 2. Per-Point Shapes Driven by Data
+ Build a per-point symbol array from your data instead of using a function.
+
+
+var DIAMOND = 'M20,0L0,20L-20,0L0,-20Z';
+var BIG_DIAMOND = 'M28,0L0,28L-28,0L0,-28Z'; // 1.4× size
+var STAR = '...'; // (same as Demo 1)
+
+var types = ['normal', 'big', 'star', 'normal'];
+var symbols = types.map(function(t) {
+ if (t === 'big') return BIG_DIAMOND;
+ if (t === 'star') return STAR;
+ return DIAMOND;
+});
+
+Plotly.newPlot('plot2', [{
+ x: [1, 2, 3, 4],
+ y: [1, 1, 1, 1],
+ mode: 'markers',
+ marker: { symbol: symbols, size: 25, color: '#10b981' }
+}]);
+
+
+ 3. Weather Map with Rotation
+ Per-point symbols + marker.angle for wind direction rotation via SVG.
+
+
+// Paths at r=20 for each weather type
+var SUN_PATH = 'M10,0A10,10 0 1,1 0,-10A10,10 0 0,1 10,0Z' + /* rays... */;
+var CLOUD_PATH = 'M-12,4A7,7 0 1,1 -2,-4A8,8 0 1,1 10,-2A6,6 0 1,1 14,4L-12,4Z';
+var WIND_PATHS = {
+ 1: 'M0,24L0,-24M0,-24L12,-18',
+ 2: 'M0,24L0,-24M0,-24L12,-18M0,-18L12,-12',
+ 3: 'M0,24L0,-24M0,-24L12,-18M0,-18L12,-12M0,-12L12,-6'
+};
+
+var symbols = locations.map(function(l) {
+ if (l.weather.type === 'sunny') return SUN_PATH;
+ if (l.weather.type === 'cloudy') return CLOUD_PATH;
+ return WIND_PATHS[l.weather.speed];
+});
+
+Plotly.newPlot('plot3', [{
+ mode: 'markers+text',
+ marker: {
+ symbol: symbols,
+ angle: locations.map(l => l.weather.direction || 0), // SVG rotate()
+ size: 30 // scale = 30/20 = 1.5×
+ }
+}]);
+
+
+
+
diff --git a/devtools/demos/backward_compatibility_test.html b/devtools/demos/backward_compatibility_test.html
new file mode 100644
index 00000000000..bcd2dec375e
--- /dev/null
+++ b/devtools/demos/backward_compatibility_test.html
@@ -0,0 +1,64 @@
+
+
+
+
+ Custom Markers - Static SVG Paths (New API)
+
+
+
+
+ Custom Markers - Static SVG Paths at r=20
+
+ New API: marker.symbol accepts SVG path strings precomputed
+ at r=20. Plotly scales them by size/20. Use an array for per-point shapes.
+ Rotation is applied as transform="rotate()" via marker.angle.
+
+
+
+
+
+
diff --git a/devtools/demos/weather_map_demo.html b/devtools/demos/weather_map_demo.html
new file mode 100644
index 00000000000..024b11aab03
--- /dev/null
+++ b/devtools/demos/weather_map_demo.html
@@ -0,0 +1,114 @@
+
+
+
+
+ Weather Map Demo - SVG Symbol/Use Rendering
+
+
+
+
+ Weather Map Demo
+ Custom markers using static SVG path strings at r=20, scaled by size/20.
+
+ New API: marker.symbol accepts an array of SVG path strings
+ precomputed at r=20. Rotation is applied via marker.angle using
+ transform="rotate()" on each <use> element.
+
+
+
+ Legend: ☀️ Sunny | ☁️ Cloudy | 🌬️ Wind (more barbs = stronger)
+
+
+
+
+
diff --git a/draftlogs/7653_add.md b/draftlogs/7653_add.md
new file mode 100644
index 00000000000..d5bf2455325
--- /dev/null
+++ b/draftlogs/7653_add.md
@@ -0,0 +1 @@
+- Add custom marker symbol support [#7653](https://github.com/plotly/plotly.js/pull/7653)
diff --git a/package-lock.json b/package-lock.json
index 5867cccd72f..86427b191a0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -43,7 +43,6 @@
"mouse-event-offset": "^3.0.2",
"mouse-wheel": "^1.2.0",
"native-promise-only": "^0.8.1",
- "parse-svg-path": "^0.1.2",
"point-in-polygon": "^1.1.0",
"polybooljs": "^1.2.2",
"probe-image-size": "^7.2.3",
@@ -104,6 +103,7 @@
"minify-stream": "^2.1.0",
"npm-link-check": "^5.0.1",
"open": "^8.4.2",
+ "parse-svg-path": "^0.1.2",
"pixelmatch": "^5.3.0",
"prepend-file": "^2.0.1",
"prettysize": "^2.0.0",
@@ -454,7 +454,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
},
@@ -478,7 +477,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -968,6 +966,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
+ "peer": true,
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
@@ -982,6 +981,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=6.0.0"
}
@@ -991,6 +991,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=6.0.0"
}
@@ -1000,6 +1001,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
@@ -1016,6 +1018,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
@@ -1440,7 +1443,8 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/@types/geojson": {
"version": "7946.0.14",
@@ -1670,6 +1674,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
"integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==",
"dev": true,
+ "peer": true,
"dependencies": {
"@webassemblyjs/helper-numbers": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6"
@@ -1679,25 +1684,29 @@
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz",
"integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
"dev": true,
+ "peer": true,
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
@@ -1708,13 +1717,15 @@
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz",
"integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==",
"dev": true,
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.12.1",
"@webassemblyjs/helper-buffer": "1.12.1",
@@ -1727,6 +1738,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
"dev": true,
+ "peer": true,
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
@@ -1736,6 +1748,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"@xtuc/long": "4.2.2"
}
@@ -1744,13 +1757,15 @@
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz",
"integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==",
"dev": true,
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.12.1",
"@webassemblyjs/helper-buffer": "1.12.1",
@@ -1767,6 +1782,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz",
"integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==",
"dev": true,
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.12.1",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
@@ -1780,6 +1796,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz",
"integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==",
"dev": true,
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.12.1",
"@webassemblyjs/helper-buffer": "1.12.1",
@@ -1792,6 +1809,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz",
"integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.12.1",
"@webassemblyjs/helper-api-error": "1.11.6",
@@ -1806,6 +1824,7 @@
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz",
"integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==",
"dev": true,
+ "peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.12.1",
"@xtuc/long": "4.2.2"
@@ -1815,13 +1834,15 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/abs-svg-path": {
"version": "0.1.1",
@@ -1847,7 +1868,6 @@
"integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1888,7 +1908,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -2450,7 +2469,8 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
- ]
+ ],
+ "peer": true
},
"node_modules/canvas": {
"version": "3.1.0",
@@ -2459,7 +2479,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"node-addon-api": "^7.0.0",
"prebuild-install": "^7.1.1"
@@ -2613,6 +2632,7 @@
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
"integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=6.0"
}
@@ -3848,7 +3868,8 @@
"version": "1.5.6",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz",
"integrity": "sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/element-size": {
"version": "1.1.1",
@@ -4005,7 +4026,8 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz",
"integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
@@ -4071,7 +4093,6 @@
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
"dev": true,
"hasInstallScript": true,
- "peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -4249,6 +4270,7 @@
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
+ "peer": true,
"dependencies": {
"estraverse": "^5.2.0"
},
@@ -4261,6 +4283,7 @@
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=4.0"
}
@@ -4949,7 +4972,8 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.0.2",
@@ -6224,6 +6248,7 @@
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"dev": true,
+ "peer": true,
"dependencies": {
"@types/node": "*",
"merge-stream": "^2.0.0",
@@ -6238,6 +6263,7 @@
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=8"
}
@@ -6247,6 +6273,7 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
+ "peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -6382,7 +6409,6 @@
"resolved": "https://registry.npmjs.org/karma/-/karma-6.4.2.tgz",
"integrity": "sha512-C6SU/53LB31BEgRg+omznBEMY4SjHU3ricV6zBcAe1EeILKkeScr+fZXtaI5WyDbkVowJxxAI6h73NcFPmXolQ==",
"dev": true,
- "peer": true,
"dependencies": {
"@colors/colors": "1.5.0",
"body-parser": "^1.19.0",
@@ -6480,7 +6506,6 @@
"resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-3.3.1.tgz",
"integrity": "sha512-Nxh7eX9mOQMyK0VSsMxdod+bcqrR/ikrmEiWj5M6fwuQ7oI+YEF1FckaDsWfs6TIpULm9f0fTKMjF7XcrvWyqQ==",
"dev": true,
- "peer": true,
"dependencies": {
"jasmine-core": "^3.5.0"
},
@@ -6610,6 +6635,7 @@
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=6.11.5"
}
@@ -7052,7 +7078,8 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/merge2": {
"version": "1.4.1",
@@ -7414,7 +7441,8 @@
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/next-tick": {
"version": "1.1.0",
@@ -7458,7 +7486,8 @@
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/node-source-walk": {
"version": "7.0.0",
@@ -8117,7 +8146,6 @@
"url": "https://github.com/sponsors/ai"
}
],
- "peer": true,
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.1",
@@ -8431,6 +8459,7 @@
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
@@ -9055,6 +9084,7 @@
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true,
+ "peer": true,
"dependencies": {
"randombytes": "^2.1.0"
}
@@ -9943,6 +9973,7 @@
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz",
"integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==",
"dev": true,
+ "peer": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.20",
"jest-worker": "^27.4.5",
@@ -9978,6 +10009,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -9990,6 +10022,7 @@
"resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz",
"integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==",
"dev": true,
+ "peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@@ -10331,7 +10364,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"dev": true,
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -10468,6 +10500,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "peer": true,
"dependencies": {
"escalade": "^3.1.2",
"picocolors": "^1.0.1"
@@ -10606,6 +10639,7 @@
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
"dev": true,
+ "peer": true,
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
@@ -10697,6 +10731,7 @@
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=10.13.0"
}
@@ -10720,6 +10755,7 @@
"resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
"integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
"dev": true,
+ "peer": true,
"peerDependencies": {
"acorn": "^8"
}
@@ -10729,6 +10765,7 @@
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true,
+ "peer": true,
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^4.1.1"
diff --git a/package.json b/package.json
index 61bb926e518..43637b008f2 100644
--- a/package.json
+++ b/package.json
@@ -101,7 +101,6 @@
"mouse-event-offset": "^3.0.2",
"mouse-wheel": "^1.2.0",
"native-promise-only": "^0.8.1",
- "parse-svg-path": "^0.1.2",
"point-in-polygon": "^1.1.0",
"polybooljs": "^1.2.2",
"probe-image-size": "^7.2.3",
@@ -162,6 +161,7 @@
"minify-stream": "^2.1.0",
"npm-link-check": "^5.0.1",
"open": "^8.4.2",
+ "parse-svg-path": "^0.1.2",
"pixelmatch": "^5.3.0",
"prepend-file": "^2.0.1",
"prettysize": "^2.0.0",
diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js
index 861df3131a5..b3fc188d5d8 100644
--- a/src/components/drawing/index.js
+++ b/src/components/drawing/index.js
@@ -111,9 +111,18 @@ drawing.translatePoint = function (d, sel, xa, ya) {
var y = ya.c2p(d.y);
if (isNumeric(x) && isNumeric(y) && sel.node()) {
- // for multiline text this works better
if (sel.node().nodeName === 'text') {
sel.attr('x', x).attr('y', y);
+ } else if (sel.node().nodeName === 'use') {
+ // For