Skip to content

Commit 90c697a

Browse files
Copilotgatopeich
andcommitted
Add comprehensive documentation for custom marker API
Co-authored-by: gatopeich <[email protected]>
1 parent 343dd6c commit 90c697a

File tree

1 file changed

+221
-0
lines changed

1 file changed

+221
-0
lines changed

CUSTOM_MARKERS.md

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
# Custom Marker API
2+
3+
This document describes the custom marker API added to plotly.js that allows users to register custom SVG marker symbols dynamically.
4+
5+
## Overview
6+
7+
The custom marker API enables developers to extend plotly.js with their own marker shapes beyond the built-in symbols. Custom markers work seamlessly with all existing marker features including size, color, line styling, and automatic variants.
8+
9+
## API Reference
10+
11+
### `Plotly.Drawing.addCustomMarker(name, drawFunc, opts)`
12+
13+
Registers a new custom marker symbol.
14+
15+
#### Parameters
16+
17+
- **name** (string, required): The name of the new marker symbol. This name will be used to reference the marker in plots (e.g., `marker.symbol: 'mymarker'`).
18+
19+
- **drawFunc** (function, required): A function that generates the SVG path string for the marker. The function receives three parameters:
20+
- `r` (number): The radius/size of the marker
21+
- `angle` (number): The rotation angle in degrees (for directional markers)
22+
- `standoff` (number): The standoff distance from the point
23+
24+
The function should return a valid SVG path string (e.g., `"M0,0L10,0L5,8.66Z"`).
25+
26+
- **opts** (object, optional): Configuration options:
27+
- `backoff` (number): Backoff distance for this symbol when used with lines. Default: 0
28+
- `needLine` (boolean): Whether this symbol requires a line stroke. Default: false
29+
- `noDot` (boolean): If true, skips creating `-dot` and `-open-dot` variants. Default: false
30+
- `noFill` (boolean): If true, the symbol should not be filled. Default: false
31+
32+
#### Returns
33+
34+
- (number): The symbol number assigned to the new marker. Returns the existing symbol number if the marker name is already registered.
35+
36+
#### Marker Variants
37+
38+
Unless `opts.noDot` is true, the following variants are automatically created:
39+
40+
- `name`: Base marker (filled)
41+
- `name-open`: Open marker (outline only, no fill)
42+
- `name-dot`: Base marker with a dot in the center
43+
- `name-open-dot`: Open marker with a dot in the center
44+
45+
## Examples
46+
47+
### Basic Heart-Shaped Marker
48+
49+
```javascript
50+
// Define the marker path function
51+
function heartMarker(r, angle, standoff) {
52+
var x = r * 0.6;
53+
var y = r * 0.8;
54+
return 'M0,' + (-y/2) +
55+
'C' + (-x) + ',' + (-y) + ' ' + (-x*2) + ',' + (-y/3) + ' ' + (-x*2) + ',0' +
56+
'C' + (-x*2) + ',' + (y/2) + ' 0,' + (y) + ' 0,' + (y*1.5) +
57+
'C0,' + (y) + ' ' + (x*2) + ',' + (y/2) + ' ' + (x*2) + ',0' +
58+
'C' + (x*2) + ',' + (-y/3) + ' ' + (x) + ',' + (-y) + ' 0,' + (-y/2) + 'Z';
59+
}
60+
61+
// Register the marker
62+
Plotly.Drawing.addCustomMarker('heart', heartMarker);
63+
64+
// Use it in a plot
65+
Plotly.newPlot('myDiv', [{
66+
type: 'scatter',
67+
x: [1, 2, 3, 4, 5],
68+
y: [2, 3, 4, 3, 2],
69+
mode: 'markers',
70+
marker: {
71+
symbol: 'heart',
72+
size: 15,
73+
color: 'red'
74+
}
75+
}]);
76+
```
77+
78+
### 5-Point Star Marker
79+
80+
```javascript
81+
function star5Marker(r, angle, standoff) {
82+
var points = 5;
83+
var outerRadius = r;
84+
var innerRadius = r * 0.4;
85+
var path = 'M';
86+
87+
for (var i = 0; i < points * 2; i++) {
88+
var radius = i % 2 === 0 ? outerRadius : innerRadius;
89+
var ang = (i * Math.PI) / points - Math.PI / 2;
90+
var x = radius * Math.cos(ang);
91+
var y = radius * Math.sin(ang);
92+
path += (i === 0 ? '' : 'L') + x.toFixed(2) + ',' + y.toFixed(2);
93+
}
94+
path += 'Z';
95+
return path;
96+
}
97+
98+
Plotly.Drawing.addCustomMarker('star5', star5Marker);
99+
100+
Plotly.newPlot('myDiv', [{
101+
type: 'scatter',
102+
x: [1, 2, 3, 4, 5],
103+
y: [2, 3, 4, 3, 2],
104+
mode: 'markers',
105+
marker: {
106+
symbol: 'star5',
107+
size: 18,
108+
color: 'gold'
109+
}
110+
}]);
111+
```
112+
113+
### Using Marker Variants
114+
115+
```javascript
116+
// Once registered, all variants are available
117+
Plotly.newPlot('myDiv', [{
118+
type: 'scatter',
119+
x: [1, 2, 3, 4],
120+
y: [1, 2, 3, 4],
121+
mode: 'markers',
122+
marker: {
123+
symbol: ['heart', 'heart-open', 'heart-dot', 'heart-open-dot'],
124+
size: 15,
125+
color: ['red', 'pink', 'crimson', 'lightcoral']
126+
}
127+
}]);
128+
```
129+
130+
### Line Marker with Options
131+
132+
```javascript
133+
// Custom line marker that doesn't need dot variants
134+
function horizontalLine(r, angle, standoff) {
135+
return 'M-' + r + ',0L' + r + ',0';
136+
}
137+
138+
Plotly.Drawing.addCustomMarker('hline', horizontalLine, {
139+
noDot: true, // Don't create -dot variants
140+
needLine: true, // This marker needs stroke
141+
noFill: true // This marker should not be filled
142+
});
143+
144+
Plotly.newPlot('myDiv', [{
145+
type: 'scatter',
146+
x: [1, 2, 3],
147+
y: [2, 3, 4],
148+
mode: 'markers',
149+
marker: {
150+
symbol: 'hline',
151+
size: 15,
152+
line: { color: 'blue', width: 2 }
153+
}
154+
}]);
155+
```
156+
157+
### Arrow Marker with Backoff
158+
159+
```javascript
160+
// Custom arrow marker with backoff for better line connection
161+
function arrowMarker(r, angle, standoff) {
162+
var headAngle = Math.PI / 4;
163+
var x = 2 * r * Math.cos(headAngle);
164+
var y = 2 * r * Math.sin(headAngle);
165+
166+
return 'M0,0L' + (-x) + ',' + y + 'L' + x + ',' + y + 'Z';
167+
}
168+
169+
Plotly.Drawing.addCustomMarker('myarrow', arrowMarker, {
170+
backoff: 0.5 // Backoff distance for line connection
171+
});
172+
173+
Plotly.newPlot('myDiv', [{
174+
type: 'scatter',
175+
x: [1, 2, 3, 4],
176+
y: [1, 2, 3, 4],
177+
mode: 'markers+lines',
178+
marker: {
179+
symbol: 'myarrow',
180+
size: 15
181+
}
182+
}]);
183+
```
184+
185+
## SVG Path Reference
186+
187+
The `drawFunc` should return a valid SVG path string. Here are the common SVG path commands:
188+
189+
- `M x,y`: Move to absolute position (x, y)
190+
- `m dx,dy`: Move to relative position (dx, dy)
191+
- `L x,y`: Line to absolute position
192+
- `l dx,dy`: Line to relative position
193+
- `H x`: Horizontal line to x
194+
- `h dx`: Horizontal line by dx
195+
- `V y`: Vertical line to y
196+
- `v dy`: Vertical line by dy
197+
- `C x1,y1 x2,y2 x,y`: Cubic Bézier curve
198+
- `Q x1,y1 x,y`: Quadratic Bézier curve
199+
- `A rx,ry rotation large-arc sweep x,y`: Elliptical arc
200+
- `Z`: Close path
201+
202+
The path should typically:
203+
- Be centered at (0, 0)
204+
- Scale proportionally with the radius `r`
205+
- Return to the start point (close the path with 'Z' for filled shapes)
206+
207+
## Notes
208+
209+
- Custom markers are registered globally and persist for the lifetime of the page
210+
- Marker names are case-sensitive
211+
- Attempting to register a marker with the same name twice will return the existing symbol number without creating a duplicate
212+
- The `angle` and `standoff` parameters are provided for advanced use cases (e.g., directional markers on maps)
213+
- For most simple shapes, you can ignore the `angle` and `standoff` parameters
214+
215+
## Browser Compatibility
216+
217+
Custom markers work in all browsers that support plotly.js and SVG path rendering.
218+
219+
## Demo
220+
221+
See `devtools/custom_marker_demo.html` for a complete working example.

0 commit comments

Comments
 (0)