Skip to content

[BUG]: scattergl with a discrete string array marker.color logs "Invalid color specifier" and blackens hover/legend color (regression in v4) #8025

Description

@jakob-keller

Disclaimer: I used Kiro CLI to troubleshoot and prepare this issue report.

Description

When a scattergl trace has marker.color set to an array of CSS color strings (discrete colors, no colorscale), hovering a point logs a warning and the hover label color defaults to black:

WARN: Invalid color specifier: "rgb(23, 103, 194),rgb(23, 103, 194),…,#FF4136,…". Defaulting to "#000"

The entire color array is stringified and passed to the color parser as a single value. The markers themselves render with correct per-point colors; only the hover/legend representative color is wrong, plus the console warning.
This is closely related to #2953 (closed) — same code path — but in v4 it now also emits an invalid-color warning rather than silently falling back.

Root cause

src/traces/scattergl/hover.jscalcHover() correctly computes the per-point marker color as di.mc = marker.color[id], but then calls getTraceColor(trace, di).
src/traces/scatter/get_trace_color.js reads:

var mc = di.mcc || (trace.marker || {}).color;

di.mcc is only populated for numeric color arrays mapped through a colorscale. For a discrete string array, mcc is undefined, so mc falls back to the whole trace.marker.color array, and Color.opacity(mc) receives the entire array joined as one string.
SVG scatter is unaffected because its calcdata pipeline sets mcc per point; scattergl's calcHover sets di.mc (already indexed) but never di.mcc.

Steps to reproduce

Plotly.newPlot('graph', [{
  type: 'scattergl',
  mode: 'markers',
  x: [1, 2, 3],
  y: [1, 2, 3],
  marker: { color: ['rgb(23, 103, 194)', '#FF4136', 'rgb(23, 103, 194)'], size: 20 }
}]);
// Hover any point → console warns "Invalid color specifier: …"

Notes

Version

plotly.js v4.0.0

Proposed fix (PR diff)

The minimal, safe change is one line in src/traces/scatter/get_trace_color.js:

--- a/src/traces/scatter/get_trace_color.js
+++ b/src/traces/scatter/get_trace_color.js
@@ module.exports = function getTraceColor(trace, di) {
     } else {
-        var mc = di.mcc || (trace.marker || {}).color;
+        // di.mcc is the colorscale-mapped per-point color (numeric color
+        // arrays). di.mc is the per-point color set by the scattergl hover
+        // path for discrete string color arrays. Fall back to the trace-level
+        // marker.color only when neither per-point value is available, so we
+        // never pass an entire color array to Color.opacity().
+        var mc = di.mcc || di.mc || (trace.marker || {}).color;
         var mlc = di.mlcc || ((trace.marker || {}).line || {}).color;

Why this is correct and low-risk:

  • di.mc is already the per-point value the hover code computed (marker.color[id]), so for the string-array case it becomes the right single color instead of the whole array.
  • For SVG scatter, di.mcc is set and takes precedence, so behavior is unchanged.
  • For the numeric+colorscale case, di.mcc still wins — unchanged.
  • The only behavioral change is the previously-broken gl string-array path, which now returns the correct point color.

Optionally, add a mock/test under test/jasmine/tests/scattergl_test.js asserting that hovering a scattergl trace with a string-array marker.color produces no Lib.warn and the returned hover color equals the point's color.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugsomething broken

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions