Skip to content

Commit d8739bd

Browse files
authored
Merge pull request #1 from gatopeich/copilot/add-custom-svg-markers
Allow custom SVG marker functions directly as marker.symbol values
2 parents 67b0cfa + 42ab66d commit d8739bd

File tree

4 files changed

+459
-6
lines changed

4 files changed

+459
-6
lines changed

CUSTOM_MARKER_FUNCTIONS.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# Custom Marker Functions
2+
3+
This document describes how to use custom SVG marker functions in plotly.js scatter plots.
4+
5+
## Overview
6+
7+
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.
8+
9+
## Usage
10+
11+
### Basic Example
12+
13+
```javascript
14+
// Define a custom marker function
15+
function heartMarker(r, angle, standoff) {
16+
var x = r * 0.6;
17+
var y = r * 0.8;
18+
return 'M0,' + (-y/2) +
19+
'C' + (-x) + ',' + (-y) + ' ' + (-x*2) + ',' + (-y/3) + ' ' + (-x*2) + ',0' +
20+
'C' + (-x*2) + ',' + (y/2) + ' 0,' + (y) + ' 0,' + (y*1.5) +
21+
'C0,' + (y) + ' ' + (x*2) + ',' + (y/2) + ' ' + (x*2) + ',0' +
22+
'C' + (x*2) + ',' + (-y/3) + ' ' + (x) + ',' + (-y) + ' 0,' + (-y/2) + 'Z';
23+
}
24+
25+
// Use it directly in a plot
26+
Plotly.newPlot('myDiv', [{
27+
type: 'scatter',
28+
x: [1, 2, 3, 4, 5],
29+
y: [2, 3, 4, 3, 2],
30+
mode: 'markers',
31+
marker: {
32+
symbol: heartMarker, // Pass the function directly!
33+
size: 15,
34+
color: 'red'
35+
}
36+
}]);
37+
```
38+
39+
### Multiple Custom Markers
40+
41+
You can use different custom markers for different points by passing an array:
42+
43+
```javascript
44+
function heartMarker(r) {
45+
var x = r * 0.6, y = r * 0.8;
46+
return 'M0,' + (-y/2) + 'C...Z';
47+
}
48+
49+
function starMarker(r) {
50+
var points = 5;
51+
var outerRadius = r;
52+
var innerRadius = r * 0.4;
53+
var path = 'M';
54+
55+
for (var i = 0; i < points * 2; i++) {
56+
var radius = i % 2 === 0 ? outerRadius : innerRadius;
57+
var ang = (i * Math.PI) / points - Math.PI / 2;
58+
var x = radius * Math.cos(ang);
59+
var y = radius * Math.sin(ang);
60+
path += (i === 0 ? '' : 'L') + x.toFixed(2) + ',' + y.toFixed(2);
61+
}
62+
path += 'Z';
63+
return path;
64+
}
65+
66+
Plotly.newPlot('myDiv', [{
67+
type: 'scatter',
68+
x: [1, 2, 3, 4, 5],
69+
y: [2, 3, 4, 3, 2],
70+
mode: 'markers',
71+
marker: {
72+
symbol: [heartMarker, starMarker, heartMarker, starMarker, heartMarker],
73+
size: 18,
74+
color: ['red', 'gold', 'pink', 'orange', 'crimson']
75+
}
76+
}]);
77+
```
78+
79+
### Mixing with Built-in Symbols
80+
81+
Custom functions work seamlessly with built-in symbol names:
82+
83+
```javascript
84+
function customDiamond(r) {
85+
var rd = r * 1.5;
86+
return 'M' + rd + ',0L0,' + rd + 'L-' + rd + ',0L0,-' + rd + 'Z';
87+
}
88+
89+
Plotly.newPlot('myDiv', [{
90+
type: 'scatter',
91+
x: [1, 2, 3, 4],
92+
y: [1, 2, 3, 4],
93+
mode: 'markers',
94+
marker: {
95+
symbol: ['circle', customDiamond, 'square', customDiamond],
96+
size: 15
97+
}
98+
}]);
99+
```
100+
101+
## Function Signature
102+
103+
Your custom marker function should have the following signature:
104+
105+
```javascript
106+
function customMarker(r, angle, standoff) {
107+
// r: radius/size of the marker
108+
// angle: rotation angle in degrees (for directional markers)
109+
// standoff: standoff distance from the point (for advanced use)
110+
111+
// Return an SVG path string
112+
return 'M...Z';
113+
}
114+
```
115+
116+
### Parameters
117+
118+
- **r** (number): The radius/size of the marker. Your path should scale proportionally with this value.
119+
- **angle** (number, optional): The rotation angle in degrees. Most simple markers can ignore this.
120+
- **standoff** (number, optional): The standoff distance. Most markers can ignore this.
121+
122+
### Return Value
123+
124+
The function must return a valid SVG path string. The path should:
125+
- Be centered at (0, 0)
126+
- Scale proportionally with the radius `r`
127+
- Use standard SVG path commands (M, L, C, Q, A, Z, etc.)
128+
129+
## SVG Path Commands
130+
131+
Here are the common SVG path commands you can use:
132+
133+
- `M x,y`: Move to absolute position (x, y)
134+
- `m dx,dy`: Move to relative position (dx, dy)
135+
- `L x,y`: Line to absolute position
136+
- `l dx,dy`: Line to relative position
137+
- `H x`: Horizontal line to x
138+
- `h dx`: Horizontal line by dx
139+
- `V y`: Vertical line to y
140+
- `v dy`: Vertical line by dy
141+
- `C x1,y1 x2,y2 x,y`: Cubic Bézier curve
142+
- `Q x1,y1 x,y`: Quadratic Bézier curve
143+
- `A rx,ry rotation large-arc sweep x,y`: Elliptical arc
144+
- `Z`: Close path
145+
146+
## Examples
147+
148+
### Simple Triangle
149+
150+
```javascript
151+
function triangleMarker(r) {
152+
var h = r * 1.5;
153+
return 'M0,-' + h + 'L' + r + ',' + (h/2) + 'L-' + r + ',' + (h/2) + 'Z';
154+
}
155+
```
156+
157+
### Pentagon
158+
159+
```javascript
160+
function pentagonMarker(r) {
161+
var points = 5;
162+
var path = 'M';
163+
for (var i = 0; i < points; i++) {
164+
var angle = (i * 2 * Math.PI / points) - Math.PI / 2;
165+
var x = r * Math.cos(angle);
166+
var y = r * Math.sin(angle);
167+
path += (i === 0 ? '' : 'L') + x.toFixed(2) + ',' + y.toFixed(2);
168+
}
169+
return path + 'Z';
170+
}
171+
```
172+
173+
### Arrow
174+
175+
```javascript
176+
function arrowMarker(r) {
177+
var headWidth = r;
178+
var headLength = r * 1.5;
179+
return 'M0,-' + headLength +
180+
'L-' + headWidth + ',0' +
181+
'L' + headWidth + ',0Z';
182+
}
183+
```
184+
185+
## Notes
186+
187+
- Custom marker functions work with all marker styling options (color, size, line, etc.)
188+
- The function is called for each point that uses it
189+
- Functions are passed through as-is and not stored in any registry
190+
- This approach is simpler than the registration-based API
191+
- For best performance, define your functions once outside the plot call
192+
193+
## Browser Compatibility
194+
195+
Custom marker functions work in all browsers that support plotly.js and SVG path rendering.

devtools/custom_marker_demo.html

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Custom Marker Functions Demo</title>
6+
<script src="dist/plotly.js"></script>
7+
<style>
8+
body {
9+
font-family: Arial, sans-serif;
10+
margin: 20px;
11+
background-color: #f5f5f5;
12+
}
13+
.container {
14+
max-width: 1200px;
15+
margin: 0 auto;
16+
background-color: white;
17+
padding: 20px;
18+
border-radius: 8px;
19+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
20+
}
21+
h1 {
22+
color: #333;
23+
border-bottom: 2px solid #3b82f6;
24+
padding-bottom: 10px;
25+
}
26+
.plot {
27+
width: 100%;
28+
height: 500px;
29+
margin: 20px 0;
30+
}
31+
.code-block {
32+
background-color: #f8f9fa;
33+
border: 1px solid #dee2e6;
34+
border-radius: 4px;
35+
padding: 15px;
36+
margin: 15px 0;
37+
overflow-x: auto;
38+
}
39+
pre {
40+
margin: 0;
41+
font-family: 'Courier New', monospace;
42+
font-size: 14px;
43+
}
44+
.info {
45+
background-color: #e7f3ff;
46+
border-left: 4px solid #3b82f6;
47+
padding: 15px;
48+
margin: 15px 0;
49+
}
50+
</style>
51+
</head>
52+
<body>
53+
<div class="container">
54+
<h1>Custom Marker Functions Demo</h1>
55+
56+
<div class="info">
57+
<strong>New Feature:</strong> You can now pass custom functions directly as
58+
<code>marker.symbol</code> values to create custom marker shapes!
59+
</div>
60+
61+
<h2>Example: Custom Marker Functions</h2>
62+
<div id="plot1" class="plot"></div>
63+
64+
<h3>Code:</h3>
65+
<div class="code-block">
66+
<pre>// Define custom marker functions
67+
function heartMarker(r) {
68+
var x = r * 0.6, y = r * 0.8;
69+
return 'M0,' + (-y/2) +
70+
'C' + (-x) + ',' + (-y) + ' ' + (-x*2) + ',' + (-y/3) + ' ' + (-x*2) + ',0' +
71+
'C' + (-x*2) + ',' + (y/2) + ' 0,' + (y) + ' 0,' + (y*1.5) +
72+
'C0,' + (y) + ' ' + (x*2) + ',' + (y/2) + ' ' + (x*2) + ',0' +
73+
'C' + (x*2) + ',' + (-y/3) + ' ' + (x) + ',' + (-y) + ' 0,' + (-y/2) + 'Z';
74+
}
75+
76+
function star5Marker(r) {
77+
var points = 5, path = 'M';
78+
for (var i = 0; i < points * 2; i++) {
79+
var radius = i % 2 === 0 ? r : r * 0.4;
80+
var ang = (i * Math.PI) / points - Math.PI / 2;
81+
path += (i === 0 ? '' : 'L') +
82+
(radius * Math.cos(ang)).toFixed(2) + ',' +
83+
(radius * Math.sin(ang)).toFixed(2);
84+
}
85+
return path + 'Z';
86+
}
87+
88+
// Use them directly in a plot
89+
Plotly.newPlot('plot1', [{
90+
x: [1, 2, 3, 4, 5],
91+
y: [2, 3, 4, 3, 2],
92+
mode: 'markers+lines',
93+
marker: {
94+
symbol: [heartMarker, star5Marker, 'circle', star5Marker, heartMarker],
95+
size: 20,
96+
color: ['red', 'gold', 'blue', 'orange', 'crimson']
97+
}
98+
}]);</pre>
99+
</div>
100+
</div>
101+
102+
<script>
103+
// Define custom heart marker
104+
function heartMarker(r) {
105+
var x = r * 0.6;
106+
var y = r * 0.8;
107+
return 'M0,' + (-y/2) +
108+
'C' + (-x) + ',' + (-y) + ' ' + (-x*2) + ',' + (-y/3) + ' ' + (-x*2) + ',0' +
109+
'C' + (-x*2) + ',' + (y/2) + ' 0,' + (y) + ' 0,' + (y*1.5) +
110+
'C0,' + (y) + ' ' + (x*2) + ',' + (y/2) + ' ' + (x*2) + ',0' +
111+
'C' + (x*2) + ',' + (-y/3) + ' ' + (x) + ',' + (-y) + ' 0,' + (-y/2) + 'Z';
112+
}
113+
114+
// Define custom 5-point star marker
115+
function star5Marker(r) {
116+
var points = 5;
117+
var outerRadius = r;
118+
var innerRadius = r * 0.4;
119+
var path = 'M';
120+
121+
for (var i = 0; i < points * 2; i++) {
122+
var radius = i % 2 === 0 ? outerRadius : innerRadius;
123+
var ang = (i * Math.PI) / points - Math.PI / 2;
124+
var x = radius * Math.cos(ang);
125+
var y = radius * Math.sin(ang);
126+
path += (i === 0 ? '' : 'L') + x.toFixed(2) + ',' + y.toFixed(2);
127+
}
128+
path += 'Z';
129+
return path;
130+
}
131+
132+
// Create the plot
133+
var trace = {
134+
x: [1, 2, 3, 4, 5],
135+
y: [2, 3, 4, 3, 2],
136+
mode: 'markers+lines',
137+
name: 'Custom Markers',
138+
marker: {
139+
symbol: [heartMarker, star5Marker, 'circle', star5Marker, heartMarker],
140+
size: 20,
141+
color: ['#e74c3c', '#f39c12', '#3498db', '#ff9800', '#c0392b'],
142+
line: {
143+
color: '#34495e',
144+
width: 2
145+
}
146+
},
147+
line: {
148+
color: '#95a5a6',
149+
width: 2,
150+
dash: 'dot'
151+
}
152+
};
153+
154+
var layout = {
155+
title: 'Custom Marker Functions Demo',
156+
xaxis: {
157+
title: 'X Axis',
158+
gridcolor: '#ecf0f1'
159+
},
160+
yaxis: {
161+
title: 'Y Axis',
162+
gridcolor: '#ecf0f1'
163+
},
164+
plot_bgcolor: '#fafafa',
165+
showlegend: true
166+
};
167+
168+
Plotly.newPlot('plot1', [trace], layout);
169+
console.log('Plot created successfully!');
170+
</script>
171+
</body>
172+
</html>

0 commit comments

Comments
 (0)