From 58fd10e829eff7fc4460958a9410ba2fbc4af8a6 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 25 May 2020 17:08:16 -0400 Subject: [PATCH 1/7] pick hover color from array on scattergl --- src/components/fx/hover.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 45617636789..d236f03231a 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -1103,8 +1103,13 @@ function createHoverText(hoverData, opts, gd) { hoverLabels.each(function(d) { var g = d3.select(this).attr('transform', ''); + var dColor = d.color; + if(Array.isArray(dColor)) { + dColor = dColor[d.index]; + } + // combine possible non-opaque trace color with bgColor - var color0 = d.bgcolor || d.color; + var color0 = d.bgcolor || dColor; // color for 'nums' part of the label var numsColor = Color.combine( Color.opacity(color0) ? color0 : Color.defaultLine, @@ -1112,7 +1117,7 @@ function createHoverText(hoverData, opts, gd) { ); // color for 'name' part of the label var nameColor = Color.combine( - Color.opacity(d.color) ? d.color : Color.defaultLine, + Color.opacity(dColor) ? dColor : Color.defaultLine, bgColor ); // find a contrasting color for border and text From 6074b33c0d9836c979ff93e2fb240cadf825be1b Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 25 May 2020 17:54:30 -0400 Subject: [PATCH 2/7] keep scatter3d d.color as an array --- src/traces/scatter3d/convert.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js index a5ab89ceef5..b2ef3aa288e 100644 --- a/src/traces/scatter3d/convert.js +++ b/src/traces/scatter3d/convert.js @@ -331,11 +331,11 @@ function convertPlotlyOptions(scene, data) { return params; } -function arrayToColor(color) { - if(Array.isArray(color)) { +function _arrayToColor(color) { + if(Lib.isArrayOrTypedArray(color)) { var c = color[0]; - if(Array.isArray(c)) color = c; + if(Lib.isArrayOrTypedArray(c)) color = c; return 'rgb(' + color.slice(0, 3).map(function(x) { return Math.round(x * 255); @@ -345,6 +345,16 @@ function arrayToColor(color) { return null; } +function arrayToColor(colors) { + if(!Lib.isArrayOrTypedArray(colors)) { + return _arrayToColor(colors); + } + + return colors.map(function(color) { + return _arrayToColor(color); + }); +} + proto.update = function(data) { var gl = this.scene.glplot.gl; var lineOptions; From 6eb862e21599eb65b775e20c1d43a081f710767b Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 25 May 2020 18:06:33 -0400 Subject: [PATCH 3/7] use pointNumber from eventData --- src/components/fx/hover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index d236f03231a..3b43a069bc3 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -1105,7 +1105,7 @@ function createHoverText(hoverData, opts, gd) { var dColor = d.color; if(Array.isArray(dColor)) { - dColor = dColor[d.index]; + dColor = dColor[d.eventData[0].pointNumber]; } // combine possible non-opaque trace color with bgColor From b9ddbc4177fdc1b4f54e387342b987dc950325e6 Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 26 May 2020 13:16:41 -0400 Subject: [PATCH 4/7] handle single color converted to rgba array --- src/traces/scatter3d/convert.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js index b2ef3aa288e..4421b16e65b 100644 --- a/src/traces/scatter3d/convert.js +++ b/src/traces/scatter3d/convert.js @@ -350,6 +350,10 @@ function arrayToColor(colors) { return _arrayToColor(colors); } + if((colors.length === 4) && (typeof colors[0] === 'number')) { + return _arrayToColor(colors); + } + return colors.map(function(color) { return _arrayToColor(color); }); From c757ef749395b4f3615720ada29f6ca5cdc93bac Mon Sep 17 00:00:00 2001 From: Mojtaba Samimi <33888540+archmoj@users.noreply.github.com> Date: Tue, 15 Sep 2020 10:12:25 -0400 Subject: [PATCH 5/7] simplify call Co-authored-by: Alex Johnson --- src/traces/scatter3d/convert.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js index 4421b16e65b..adf2a3eeff0 100644 --- a/src/traces/scatter3d/convert.js +++ b/src/traces/scatter3d/convert.js @@ -354,9 +354,7 @@ function arrayToColor(colors) { return _arrayToColor(colors); } - return colors.map(function(color) { - return _arrayToColor(color); - }); + return colors.map(_arrayToColor); } proto.update = function(data) { From 33fd07d9dc501d24b6be4e426918b6bb0dcd9a5f Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 15 Sep 2020 10:34:26 -0400 Subject: [PATCH 6/7] return null to avoid an extra call --- src/traces/scatter3d/convert.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js index adf2a3eeff0..2e82ee0259d 100644 --- a/src/traces/scatter3d/convert.js +++ b/src/traces/scatter3d/convert.js @@ -347,7 +347,7 @@ function _arrayToColor(color) { function arrayToColor(colors) { if(!Lib.isArrayOrTypedArray(colors)) { - return _arrayToColor(colors); + return null; } if((colors.length === 4) && (typeof colors[0] === 'number')) { From d693e098d8b7a19187d791adaa485e1661b7b0a0 Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 15 Sep 2020 13:44:24 -0400 Subject: [PATCH 7/7] add jasmine tests --- test/jasmine/tests/gl2d_click_test.js | 26 ++ test/jasmine/tests/gl3d_hover_click_test.js | 353 +++++++++++++++++++- 2 files changed, 378 insertions(+), 1 deletion(-) diff --git a/test/jasmine/tests/gl2d_click_test.js b/test/jasmine/tests/gl2d_click_test.js index 20d3fa33b75..8f5c996e072 100644 --- a/test/jasmine/tests/gl2d_click_test.js +++ b/test/jasmine/tests/gl2d_click_test.js @@ -23,6 +23,7 @@ Plotly.register([ require('@lib/contourgl') ]); +var mock0 = require('@mocks/gl2d_scatter-continuous-clustering.json'); var mock1 = require('@mocks/gl2d_14.json'); var mock2 = require('@mocks/gl2d_pointcloud-basic.json'); @@ -445,6 +446,31 @@ describe('Test hover and click interactions', function() { .then(done); }); + it('@gl scatter3d should propagate marker colors to hover labels', function(done) { + var _mock = Lib.extendDeep({}, mock0); + _mock.layout.width = 800; + _mock.layout.height = 600; + + var run = makeRunner([700, 300], { + x: 15075859, + y: 79183, + curveNumber: 0, + pointNumber: 0, + bgcolor: 'rgb(202, 178, 214)', + bordercolor: 'rgb(68, 68, 68)', + fontSize: 13, + fontFamily: 'Arial', + fontColor: 'rgb(68, 68, 68)' + }, { + msg: 'pointcloud' + }); + + Plotly.newPlot(gd, _mock) + .then(run) + .catch(failTest) + .then(done); + }); + it('@gl should output correct event data for heatmapgl', function(done) { var _mock = Lib.extendDeep({}, mock3); _mock.data[0].type = 'heatmapgl'; diff --git a/test/jasmine/tests/gl3d_hover_click_test.js b/test/jasmine/tests/gl3d_hover_click_test.js index 57f74f1b03f..49df2b4c4df 100644 --- a/test/jasmine/tests/gl3d_hover_click_test.js +++ b/test/jasmine/tests/gl3d_hover_click_test.js @@ -68,7 +68,7 @@ describe('Test gl3d trace click/hover:', function() { expect(ptData.pointNumber).toEqual(pointNumber, 'pointNumber'); Object.keys(extra || {}).forEach(function(k) { - expect(ptData[k]).toBe(extra[k], k + ' val'); + expect(ptData[k]).toEqual(extra[k], k + ' val'); }); } @@ -929,4 +929,355 @@ describe('Test gl3d trace click/hover:', function() { .catch(failTest) .then(done); }); + + describe('propagate colors to hover labels', function() { + ['marker', 'line'].forEach(function(t) { + it('@gl scatter3d ' + t + ' colors', function(done) { + var orange = new Uint8Array(3); + orange[0] = 255; + orange[1] = 127; + orange[2] = 0; + + var color = [ + 'red', + [0, 255, 0], + 'rgba(0,0,255,0.5)', + orange, + 'yellow', + // left undefined + ]; + + var _mock = { + data: [{ + type: 'scatter3d', + x: [-1, -2, -3, -4, -5, -6], + y: [1, 2, 3, 4, 5, 6], + z: [0, 0, 0, 0, 0, 0] + }], + layout: { + margin: { t: 50, b: 50, l: 50, r: 50 }, + width: 600, + height: 400, + scene: { aspectratio: { x: 2, y: 2, z: 0.5} } + } + }; + + if(t === 'marker') { + _mock.data[0].mode = 'markers'; + _mock.data[0].marker = { + color: color, + size: 20 + }; + } else { + _mock.data[0].mode = 'lines'; + _mock.data[0].line = { + color: color, + width: 40 + }; + } + + Plotly.newPlot(gd, _mock) + .then(delay(100)) + .then(function() { + gd.on('plotly_hover', function(eventData) { + ptData = eventData.points[0]; + }); + }) + .then(delay(100)) + .then(function() { + mouseEvent('mouseover', 80, 200); + }) + .then(delay(100)) + .then(function() { + assertHoverText('x: −1', 'y: 1', 'z: 0'); + assertEventData(-1, 1, 0, 0, 0, t === 'marker' ? { + 'marker.color': 'red' + } : { + 'line.color': 'red' + }); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(255, 0, 0)', + bordercolor: 'rgb(255, 255, 255)', + fontColor: 'rgb(255, 255, 255)', + fontSize: 13, + fontFamily: 'Arial' + }, 'red'); + }) + .then(delay(100)) + .then(function() { + mouseEvent('mouseover', 169, 200); + }) + .then(delay(100)) + .then(function() { + assertHoverText('x: −2', 'y: 2', 'z: 0'); + assertEventData(-2, 2, 0, 0, 1, t === 'marker' ? { + 'marker.color': [0, 255, 0] + } : { + 'line.color': [0, 255, 0] + }); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(0, 255, 0)', + bordercolor: 'rgb(68, 68, 68)', + fontColor: 'rgb(68, 68, 68)', + fontSize: 13, + fontFamily: 'Arial' + }, 'green'); + }) + .then(delay(100)) + .then(function() { + mouseEvent('mouseover', 258, 200); + }) + .then(delay(100)) + .then(function() { + assertHoverText('x: −3', 'y: 3', 'z: 0'); + assertEventData(-3, 3, 0, 0, 2, t === 'marker' ? { + 'marker.color': 'rgba(0,0,255,0.5)' + } : { + 'line.color': 'rgba(0,0,255,0.5)' + }); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(0, 0, 255)', + bordercolor: 'rgb(255, 255, 255)', + fontColor: 'rgb(255, 255, 255)', + fontSize: 13, + fontFamily: 'Arial' + }, 'blue'); + }) + .then(delay(100)) + .then(function() { + mouseEvent('mouseover', 347, 200); + }) + .then(delay(100)) + .then(function() { + assertHoverText('x: −4', 'y: 4', 'z: 0'); + assertEventData(-4, 4, 0, 0, 3, t === 'marker' ? { + 'marker.color': orange + } : { + 'line.color': orange + }); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(255, 127, 0)', + bordercolor: 'rgb(68, 68, 68)', + fontColor: 'rgb(68, 68, 68)', + fontSize: 13, + fontFamily: 'Arial' + }, 'orange'); + }) + .then(delay(100)) + .then(function() { + mouseEvent('mouseover', 436, 200); + }) + .then(delay(100)) + .then(function() { + assertHoverText('x: −5', 'y: 5', 'z: 0'); + assertEventData(-5, 5, 0, 0, 4, t === 'marker' ? { + 'marker.color': 'yellow' + } : { + 'line.color': 'yellow' + }); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(255, 255, 0)', + bordercolor: 'rgb(68, 68, 68)', + fontColor: 'rgb(68, 68, 68)', + fontSize: 13, + fontFamily: 'Arial' + }, 'yellow'); + }) + .then(delay(100)) + .then(function() { + mouseEvent('mouseover', 525, 200); + }) + .then(delay(100)) + .then(function() { + assertHoverText('x: −6', 'y: 6', 'z: 0'); + assertEventData(-6, 6, 0, 0, 5, t === 'marker' ? { + 'marker.color': undefined + } : { + 'line.color': undefined + }); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(68, 68, 68)', + bordercolor: 'rgb(255, 255, 255)', + fontColor: 'rgb(255, 255, 255)', + fontSize: 13, + fontFamily: 'Arial' + }, 'undefined'); + }) + .catch(failTest) + .then(done); + }); + + it('@gl scatter3d ' + t + ' colorscale', function(done) { + var color = [ + 2, + 1, + 0, + -1, + -2, + // left undefined + ]; + + var _mock = { + data: [{ + type: 'scatter3d', + x: [-1, -2, -3, -4, -5, -6], + y: [1, 2, 3, 4, 5, 6], + z: [0, 0, 0, 0, 0, 0] + }], + layout: { + margin: { t: 50, b: 50, l: 50, r: 50 }, + width: 600, + height: 400, + scene: { aspectratio: { x: 2, y: 2, z: 0.5} } + } + }; + + if(t === 'marker') { + _mock.data[0].mode = 'markers'; + _mock.data[0].marker = { + colorscale: 'Portland', + color: color, + size: 20 + }; + } else { + _mock.data[0].mode = 'lines'; + _mock.data[0].line = { + colorscale: 'Portland', + color: color, + width: 40 + }; + } + + Plotly.newPlot(gd, _mock) + .then(delay(100)) + .then(function() { + gd.on('plotly_hover', function(eventData) { + ptData = eventData.points[0]; + }); + }) + .then(delay(100)) + .then(function() { + mouseEvent('mouseover', 80, 200); + }) + .then(delay(100)) + .then(function() { + assertHoverText('x: −1', 'y: 1', 'z: 0'); + assertEventData(-1, 1, 0, 0, 0, t === 'marker' ? { + 'marker.color': 2 + } : { + 'line.color': 2 + }); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(217, 30, 30)', + bordercolor: 'rgb(255, 255, 255)', + fontColor: 'rgb(255, 255, 255)', + fontSize: 13, + fontFamily: 'Arial' + }, '1st point'); + }) + .then(delay(100)) + .then(function() { + mouseEvent('mouseover', 169, 200); + }) + .then(delay(100)) + .then(function() { + assertHoverText('x: −2', 'y: 2', 'z: 0'); + assertEventData(-2, 2, 0, 0, 1, t === 'marker' ? { + 'marker.color': 1 + } : { + 'line.color': 1 + }); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(242, 143, 56)', + bordercolor: 'rgb(68, 68, 68)', + fontColor: 'rgb(68, 68, 68)', + fontSize: 13, + fontFamily: 'Arial' + }, '2nd point'); + }) + .then(delay(100)) + .then(function() { + mouseEvent('mouseover', 258, 200); + }) + .then(delay(100)) + .then(function() { + assertHoverText('x: −3', 'y: 3', 'z: 0'); + assertEventData(-3, 3, 0, 0, 2, t === 'marker' ? { + 'marker.color': 0 + } : { + 'line.color': 0 + }); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(242, 211, 56)', + bordercolor: 'rgb(68, 68, 68)', + fontColor: 'rgb(68, 68, 68)', + fontSize: 13, + fontFamily: 'Arial' + }, '3rd point'); + }) + .then(delay(100)) + .then(function() { + mouseEvent('mouseover', 347, 200); + }) + .then(delay(100)) + .then(function() { + assertHoverText('x: −4', 'y: 4', 'z: 0'); + assertEventData(-4, 4, 0, 0, 3, t === 'marker' ? { + 'marker.color': -1 + } : { + 'line.color': -1 + }); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(10, 136, 186)', + bordercolor: 'rgb(255, 255, 255)', + fontColor: 'rgb(255, 255, 255)', + fontSize: 13, + fontFamily: 'Arial' + }, '4th point'); + }) + .then(delay(100)) + .then(function() { + mouseEvent('mouseover', 436, 200); + }) + .then(delay(100)) + .then(function() { + assertHoverText('x: −5', 'y: 5', 'z: 0'); + assertEventData(-5, 5, 0, 0, 4, t === 'marker' ? { + 'marker.color': -2 + } : { + 'line.color': -2 + }); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(12, 51, 131)', + bordercolor: 'rgb(255, 255, 255)', + fontColor: 'rgb(255, 255, 255)', + fontSize: 13, + fontFamily: 'Arial' + }, '5th point'); + }) + .then(delay(100)) + .then(function() { + mouseEvent('mouseover', 525, 200); + }) + .then(delay(100)) + .then(function() { + assertHoverText('x: −6', 'y: 6', 'z: 0'); + assertEventData(-6, 6, 0, 0, 5, t === 'marker' ? { + 'marker.color': undefined + } : { + 'line.color': undefined + }); + assertHoverLabelStyle(d3.selectAll('g.hovertext'), { + bgcolor: 'rgb(68, 68, 68)', + bordercolor: 'rgb(255, 255, 255)', + fontColor: 'rgb(255, 255, 255)', + fontSize: 13, + fontFamily: 'Arial' + }, '6th point'); + }) + .catch(failTest) + .then(done); + }); + }); + }); });