Skip to content
Merged
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
1 change: 1 addition & 0 deletions draftlogs/8027_fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Resolve the per-point marker color for hover labels in `scattergl`, `quiver` traces [[#8027](https://github.com/plotly/plotly.js/pull/8027)]
9 changes: 8 additions & 1 deletion src/components/color/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ const snap = (c) => ({ ...c, r: snap01(c.r), g: snap01(c.g), b: snap01(c.b) });
const formatRgb = (c) => culoriFormatRgb(snap(c));
const formatHex = (c) => culoriFormatHex(snap(c));

const describe = (v) => {
if (typeof v === 'string') return `"${v}"`;
if (isArrayOrTypedArray(v)) return `${v.constructor?.name ?? 'Array'}(${v.length})`;
if (typeof v === 'object') return v.constructor?.name ?? 'Object';
return `${typeof v} ${v}`;
};

/**
* Parse a color specifier string and return it as a culori rgb color object.
*
Expand All @@ -74,7 +81,7 @@ const formatHex = (c) => culoriFormatHex(snap(c));
const parse = (cstr, silent) => {
const c = toColor(cstr);
if (!c) {
if (!silent && cstr != null) warn(`Invalid color specifier: "${cstr}". Defaulting to "#000"`);
if (!silent && cstr != null) warn(`Invalid color specifier: ${describe(cstr)}. Defaulting to "#000"`);
return BLACK;
}
// `toRgb` omits alpha when it's 1; make sure it's added since we expect it
Expand Down
5 changes: 5 additions & 0 deletions src/traces/quiver/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ module.exports = function calc(gd, trace) {

if(hasMarkerColorArray) {
var ci = markerColor[i];

// Keep the per-point color under the name that the shared hover
// code reads. Without it, `getTraceColor` reads the whole array.
cdi.mc = ci;

if(isNumeric(ci)) {
if(ci < cMin) cMin = ci;
if(ci > cMax) cMax = ci;
Expand Down
7 changes: 6 additions & 1 deletion src/traces/quiver/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ function colorscaleStroke(paths, trace) {
var vVal = (trace.v && trace.v[cdi.i]) || 0;
value = Math.sqrt(uVal * uVal + vVal * vVal);
}
return colorFunc(value);

// Keep the mapped color on the point, under the name that the shared
// hover code reads, so that the hover label matches the arrow
cdi.mcc = colorFunc(value);

return cdi.mcc;
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/traces/scatter/get_trace_color.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = function getTraceColor(trace, di) {
} else if(trace.mode === 'none') {
return trace.fill ? trace.fillcolor : '';
} else {
var mc = di.mcc || (trace.marker || {}).color;
var mc = di.mcc || di.mc || (trace.marker || {}).color;
var mlc = di.mlcc || ((trace.marker || {}).line || {}).color;

tc = (mc && Color.opacity(mc)) ? mc :
Expand Down
4 changes: 4 additions & 0 deletions src/traces/scattergl/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
var Registry = require('../../registry');
var Lib = require('../../lib');
var getTraceColor = require('../scatter/get_trace_color');
const Drawing = require('../../components/drawing');

function hoverPoints(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
Expand Down Expand Up @@ -144,6 +145,9 @@ function calcHover(pointData, x, y, trace) {
di.mx = Lib.isArrayOrTypedArray(marker.symbol) ? marker.symbol[id] : marker.symbol;
di.ma = Lib.isArrayOrTypedArray(marker.angle) ? marker.angle[id] : marker.angle;
di.mc = Lib.isArrayOrTypedArray(marker.color) ? marker.color[id] : marker.color;

// scattergl has no render step that sets `mcc`, so map the per-point color here
di.mcc = Drawing.tryColorscale(marker, '')(di.mc);
}

var line = marker && marker.line;
Expand Down
47 changes: 47 additions & 0 deletions test/jasmine/tests/gl2d_click_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,53 @@ describe('Test hover and click interactions', function() {
.then(done, done.fail);
});

it('@gl should use the per-point marker color for the hover label', async () => {
await Plotly.newPlot(
gd,
[
{
type: 'scattergl',
mode: 'markers',
x: [1, 2, 3],
y: [1, 2, 3],
marker: {
color: ['#1767C2', '#FF4136', '#2ECC40'],
size: 20
}
}
],
{
hovermode: 'closest',
width: 400,
height: 400,
xaxis: { showspikes: true },
yaxis: { showspikes: true }
}
);

Plotly.Fx.hover(gd, { xval: 2, yval: 2 }, 'xy');
Lib.clearThrottle();

const withArray = d3Select('g.hovertext path').node();
expect(window.getComputedStyle(withArray).fill).toBe('rgb(255, 65, 54)', 'color array');

const spikeStrokes = Array.from(document.querySelectorAll('line.spikeline')).map(
(line) => window.getComputedStyle(line).stroke
);
expect(spikeStrokes.filter((stroke) => stroke === 'rgb(255, 65, 54)').length).toBe(2, 'spikelines');

await Plotly.restyle(gd, {
'marker.color': [[0, 5, 10]],
'marker.colorscale': 'Viridis'
});

Plotly.Fx.hover(gd, { xval: 2, yval: 2 }, 'xy');
Lib.clearThrottle();

const withColorscale = d3Select('g.hovertext path').node();
expect(window.getComputedStyle(withColorscale).fill).toBe('rgb(33, 145, 140)', 'colorscale');
});

it('@gl should show correct label for scattergl when hovertext is set', function(done) {
var _mock = Lib.extendDeep({}, mock1);
_mock.data[0].hovertext = 'text';
Expand Down
36 changes: 35 additions & 1 deletion test/jasmine/tests/quiver_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,41 @@ describe('Test quiver interactions', function() {
.then(done, done.fail);
});

it('should use the per-point arrow color for the hover label', async () => {
const fig = {
data: [
{
type: 'quiver',
x: [1, 2, 3],
y: [1, 2, 3],
u: [1, 0, -1],
v: [0, 1, 0],
marker: {
color: [0, 5, 10],
colorscale: 'Viridis',
showscale: false
}
}
],
layout: {
margin: { l: 0, t: 0, r: 0, b: 0 },
width: 400,
height: 400
}
};

await Plotly.newPlot(gd, fig);

mouseEvent('mousemove', 200, 200);
await delay(20)();

const label = document.querySelector('g.hovertext path');
const arrow = gd.querySelectorAll('g.trace.quiver path.js-line')[1];

expect(window.getComputedStyle(label).fill).toBe('rgb(33, 145, 140)', 'hover label');
expect(window.getComputedStyle(arrow).stroke).toBe('rgb(33, 145, 140)', 'arrow');
});

it('should render multiple quiver traces', function(done) {
Plotly.newPlot(gd, [{
type: 'quiver',
Expand Down Expand Up @@ -344,4 +379,3 @@ describe('Test quiver interactions', function() {
.then(done, done.fail);
});
});

Loading