Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions CUSTOM_MARKER_FUNCTIONS.md
Original file line number Diff line number Diff line change
@@ -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
172 changes: 172 additions & 0 deletions devtools/custom_marker_demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Custom Marker Functions Demo</title>
<script src="dist/plotly.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #333;
border-bottom: 2px solid #3b82f6;
padding-bottom: 10px;
}
.plot {
width: 100%;
height: 500px;
margin: 20px 0;
}
.code-block {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 15px;
margin: 15px 0;
overflow-x: auto;
}
pre {
margin: 0;
font-family: 'Courier New', monospace;
font-size: 14px;
}
.info {
background-color: #e7f3ff;
border-left: 4px solid #3b82f6;
padding: 15px;
margin: 15px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>Custom Marker Functions Demo</h1>

<div class="info">
<strong>New Feature:</strong> You can now pass custom functions directly as
<code>marker.symbol</code> values to create custom marker shapes!
</div>

<h2>Example: Custom Marker Functions</h2>
<div id="plot1" class="plot"></div>

<h3>Code:</h3>
<div class="code-block">
<pre>// 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']
}
}]);</pre>
</div>
</div>

<script>
// Define custom heart marker
function heartMarker(r) {
var x = r * 0.6;
var 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';
}

// Define custom 5-point star marker
function star5Marker(r) {
var points = 5;
var outerRadius = r;
var innerRadius = r * 0.4;
var path = 'M';

for (var i = 0; i < points * 2; i++) {
var radius = i % 2 === 0 ? outerRadius : innerRadius;
var ang = (i * Math.PI) / points - Math.PI / 2;
var x = radius * Math.cos(ang);
var y = radius * Math.sin(ang);
path += (i === 0 ? '' : 'L') + x.toFixed(2) + ',' + y.toFixed(2);
}
path += 'Z';
return path;
}

// Create the plot
var trace = {
x: [1, 2, 3, 4, 5],
y: [2, 3, 4, 3, 2],
mode: 'markers+lines',
name: 'Custom Markers',
marker: {
symbol: [heartMarker, star5Marker, 'circle', star5Marker, heartMarker],
size: 20,
color: ['#e74c3c', '#f39c12', '#3498db', '#ff9800', '#c0392b'],
line: {
color: '#34495e',
width: 2
}
},
line: {
color: '#95a5a6',
width: 2,
dash: 'dot'
}
};

var layout = {
title: 'Custom Marker Functions Demo',
xaxis: {
title: 'X Axis',
gridcolor: '#ecf0f1'
},
yaxis: {
title: 'Y Axis',
gridcolor: '#ecf0f1'
},
plot_bgcolor: '#fafafa',
showlegend: true
};

Plotly.newPlot('plot1', [trace], layout);
console.log('Plot created successfully!');
</script>
</body>
</html>
Loading