Skip to content

Conversation

Copy link

Copilot AI commented Nov 19, 2025

Simplified Custom Marker API

This PR enables passing custom functions directly as marker.symbol values to create custom SVG marker shapes for scatter plots and related trace types, without requiring any registration step.

Changes Made

Core Implementation (src/components/drawing/index.js)

  • Modified makePointPath() to accept either a symbol number or a custom function
  • Updated singlePointStyle() to detect function symbols and pass them through directly
  • Updated selectedPointStyle() to handle function symbols
  • Added function check in backoff calculation to avoid calling symbolNumber() on custom functions

Tests (test/jasmine/tests/drawing_test.js)

  • Tests for passing functions as marker.symbol
  • Tests for arrays of custom functions
  • Tests for mixing custom functions with built-in symbols

Documentation

  • CUSTOM_MARKER_FUNCTIONS.md: API reference with examples
  • devtools/custom_marker_demo.html: Interactive demo

Usage

// Define custom marker function
function heartMarker(r, angle, standoff) {
    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';
}

// Pass function directly as marker.symbol
Plotly.newPlot('div', [{
    type: 'scatter',
    mode: 'markers',
    marker: {
        symbol: heartMarker,  // No registration needed!
        size: 15
    }
}]);

Functions can be used in arrays and mixed with built-in symbol names:

marker: {
    symbol: ['circle', heartMarker, 'square', heartMarker]
}

Benefits

  • ✅ Simpler - no registration required
  • ✅ More flexible - functions can be defined inline or reused
  • ✅ Works with arrays of symbols
  • ✅ Mixes seamlessly with built-in symbol names
  • ✅ Fully backward compatible
Original prompt

I want to add custom SVG markers to the scatter plot (at least) here by having a function like this:

function add_custom_marker(name, fun) {
const drawing = window.Drawing;
if (name in drawing.symbolNames) return;
const n = drawing.symbolNames.length;
const symDef = { f:fun, };

drawing.symbolList.push(n, String(n), name, n + 100, String(n + 100));
drawing.symbolNames[n] = name;
drawing.symbolFuncs[n] = symDef.f;

return n;

}


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 4 commits November 19, 2025 11:24
Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com>
Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com>
Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com>
Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com>
Copilot AI changed the title [WIP] Add custom SVG markers function for scatter plot Add API for dynamic custom SVG marker registration Nov 19, 2025
Copilot AI requested a review from gatopeich November 19, 2025 11:33
Copilot finished work on behalf of gatopeich November 19, 2025 11:33
@gatopeich
Copy link
Owner

@copilot It would be simpler to pass the custom symbol function directly instead of symbol name. Then the drawing code can check if "symbol" attribute is callable, and call it instead of looking into the index.

Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 21, 2025

@copilot It would be simpler to pass the custom symbol function directly instead of symbol name. Then the drawing code can check if "symbol" attribute is callable, and call it instead of looking into ...

Good point! I've simplified the implementation to allow passing custom marker functions directly as marker.symbol values.

The new approach:

  • No registration needed - just pass the function directly
  • Works with arrays: symbol: [myFunc1, 'circle', myFunc2]
  • Much simpler API overall

Example:

function customMarker(r) {
    return 'M' + r + ',0L0,' + r + 'L-' + r + ',0L0,-' + r + 'Z';
}

Plotly.newPlot('div', [{
    marker: { symbol: customMarker }  // Direct function!
}]);

Changes in commit 86086e0.

Copilot AI changed the title Add API for dynamic custom SVG marker registration Allow custom SVG marker functions directly as marker.symbol values Nov 21, 2025
Copilot finished work on behalf of gatopeich November 21, 2025 00:07
if (Lib.isArrayOrTypedArray(endMarkerSize)) endMarkerSize = endMarkerSize[endI];

b = endMarker ? drawing.symbolBackOffs[drawing.symbolNumber(endMarkerSymbol)] * endMarkerSize : 0;
b = endMarker ? (drawing.symbolBackOffs[drawing.symbolNumber(endMarkerSymbol)] || 0) * endMarkerSize : 0;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if end symbol is function before calling symbolNumber() instead of falsy check

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address this

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 099b0d6. Now checking typeof endMarkerSymbol !== 'function' before calling symbolNumber(), consistent with how it's handled elsewhere in the code.

…calculation

Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com>
Copilot AI requested a review from gatopeich November 22, 2025 05:51
Copilot finished work on behalf of gatopeich November 22, 2025 05:51
Copilot finished work on behalf of gatopeich November 22, 2025 05:53
@gatopeich gatopeich marked this pull request as ready for review November 22, 2025 05:54
var endMarkerSize = endMarker.size;
if (Lib.isArrayOrTypedArray(endMarkerSize)) endMarkerSize = endMarkerSize[endI];

b = endMarker ? drawing.symbolBackOffs[drawing.symbolNumber(endMarkerSymbol)] * endMarkerSize : 0;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just add check && typeof endMarkerSymbol !== 'function' instead of this messy change.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified in 42ab66d. Now using the ternary operator with the function check instead of introducing extra variables.

Co-authored-by: gatopeich <7722268+gatopeich@users.noreply.github.com>
Copilot AI requested a review from gatopeich November 22, 2025 06:04
Copilot finished work on behalf of gatopeich November 22, 2025 06:04
@gatopeich
Copy link
Owner

@copilot Remove "|| 0" from src/components/drawing/index.js:1509

Copy link
Owner

@gatopeich gatopeich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay we'll leave the additional falsy check since it was present in other places too.

@gatopeich gatopeich merged commit d8739bd into master Nov 22, 2025
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants