diff --git a/draftlogs/6216_add.md b/draftlogs/6216_add.md new file mode 100644 index 00000000000..56f7e9d7766 --- /dev/null +++ b/draftlogs/6216_add.md @@ -0,0 +1 @@ + - Add `unselected.line.color` and `unselected.line.opacity` options to `parcoords` trace [[#6216](https://github.com/plotly/plotly.js/pull/6216)] diff --git a/src/traces/parcoords/attributes.js b/src/traces/parcoords/attributes.js index 3dbe1670d09..a4b50111e9a 100644 --- a/src/traces/parcoords/attributes.js +++ b/src/traces/parcoords/attributes.js @@ -133,5 +133,33 @@ module.exports = { autoColorDflt: false, editTypeOverride: 'calc' }) - ) + ), + + unselected: { + line: { + color: { + valType: 'color', + dflt: '#777', + editType: 'plot', + description: [ + 'Sets the base color of unselected lines.', + 'in connection with `unselected.line.opacity`.' + ].join(' ') + }, + opacity: { + valType: 'number', + min: 0, + max: 1, + dflt: 'auto', + editType: 'plot', + description: [ + 'Sets the opacity of unselected lines.', + 'The default *auto* decreases the opacity smoothly as the number of lines increases.', + 'Use *1* to achieve exact `unselected.line.color`.' + ].join(' ') + }, + editType: 'plot' + }, + editType: 'plot' + } }; diff --git a/src/traces/parcoords/constants.js b/src/traces/parcoords/constants.js index 717d819ad83..e276c715a51 100644 --- a/src/traces/parcoords/constants.js +++ b/src/traces/parcoords/constants.js @@ -11,7 +11,6 @@ module.exports = { layers: ['contextLineLayer', 'focusLineLayer', 'pickLineLayer'], axisTitleOffset: 28, axisExtentOffset: 10, - deselectedLineColor: '#777', bar: { width: 4, // Visible width of the filter bar captureWidth: 10, // Mouse-sensitive width for interaction (Fitts law) diff --git a/src/traces/parcoords/defaults.js b/src/traces/parcoords/defaults.js index 5c7b6569c11..dc8a310179f 100644 --- a/src/traces/parcoords/defaults.js +++ b/src/traces/parcoords/defaults.js @@ -107,4 +107,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('labelangle'); coerce('labelside'); + + coerce('unselected.line.color'); + coerce('unselected.line.opacity'); }; diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js index ab753558c67..120bdf8ba88 100644 --- a/src/traces/parcoords/lines.js +++ b/src/traces/parcoords/lines.js @@ -183,6 +183,7 @@ function makeItem( var layoutWidth = model.layoutWidth * plotGlPixelRatio; var deselectedLinesColor = model.deselectedLines.color; + var deselectedLinesOpacity = model.deselectedLines.opacity; var itemModel = Lib.extendFlat({ key: crossfilterDimensionIndex, @@ -206,8 +207,8 @@ function makeItem( deselectedLinesColor[0] / 255, deselectedLinesColor[1] / 255, deselectedLinesColor[2] / 255, - deselectedLinesColor[3] < 1 ? - deselectedLinesColor[3] : + deselectedLinesOpacity !== 'auto' ? + deselectedLinesColor[3] * deselectedLinesOpacity : Math.max(1 / 255, Math.pow(1 / model.lines.color.length, 1 / 3)) ], diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js index 17ab829bc14..7969c468e6e 100644 --- a/src/traces/parcoords/parcoords.js +++ b/src/traces/parcoords/parcoords.js @@ -149,7 +149,10 @@ function model(layout, d, i) { var trace = cd0.trace; var lineColor = helpers.convertTypedArray(cd0.lineColor); var line = trace.line; - var deselectedLines = {color: rgba(c.deselectedLineColor)}; + var deselectedLines = { + color: rgba(trace.unselected.line.color), + opacity: trace.unselected.line.opacity + }; var cOpts = Colorscale.extractOpts(line); var cscale = cOpts.reversescale ? Colorscale.flipScale(cd0.cscale) : cd0.cscale; var domain = trace.domain; diff --git a/test/image/baselines/gl2d_parcoords_256_colors.png b/test/image/baselines/gl2d_parcoords_256_colors.png index afd4bbd1ad8..a1d44ef9348 100644 Binary files a/test/image/baselines/gl2d_parcoords_256_colors.png and b/test/image/baselines/gl2d_parcoords_256_colors.png differ diff --git a/test/image/baselines/gl2d_parcoords_constraints.png b/test/image/baselines/gl2d_parcoords_constraints.png index 56e3d280e44..d0e161d6597 100644 Binary files a/test/image/baselines/gl2d_parcoords_constraints.png and b/test/image/baselines/gl2d_parcoords_constraints.png differ diff --git a/test/image/baselines/gl2d_parcoords_dark_background.png b/test/image/baselines/gl2d_parcoords_dark_background.png index 8ae5d8436bc..df8f4100db1 100644 Binary files a/test/image/baselines/gl2d_parcoords_dark_background.png and b/test/image/baselines/gl2d_parcoords_dark_background.png differ diff --git a/test/image/mocks/gl2d_parcoords_256_colors.json b/test/image/mocks/gl2d_parcoords_256_colors.json index 609b3134531..b1c8f6d03a1 100644 --- a/test/image/mocks/gl2d_parcoords_256_colors.json +++ b/test/image/mocks/gl2d_parcoords_256_colors.json @@ -2,6 +2,11 @@ "data": [ { "type": "parcoords", + "unselected": { + "line": { + "color": "blue" + } + }, "line": { "colorscale": [ [ diff --git a/test/image/mocks/gl2d_parcoords_constraints.json b/test/image/mocks/gl2d_parcoords_constraints.json index d9316195616..a339717a25e 100644 --- a/test/image/mocks/gl2d_parcoords_constraints.json +++ b/test/image/mocks/gl2d_parcoords_constraints.json @@ -1,5 +1,6 @@ { "layout": { + "paper_bgcolor": "lightgray", "width": 1000, "height": 400 }, @@ -24,6 +25,13 @@ ] }, + "unselected": { + "line": { + "opacity": 1, + "color": "white" + } + }, + "dimensions": [ { "label": "±1.1", diff --git a/test/image/mocks/gl2d_parcoords_dark_background.json b/test/image/mocks/gl2d_parcoords_dark_background.json index b3f39f4730c..81c1dca8d6c 100644 --- a/test/image/mocks/gl2d_parcoords_dark_background.json +++ b/test/image/mocks/gl2d_parcoords_dark_background.json @@ -20,6 +20,12 @@ "color": [-41, -1317, -164, -1856, -79, -931, -191, -2983, -341, -3846, -278, -3019, -523, -2357, -985, -3447, -211, -2842, -458, -4388, -784, -2563, -935, -2253, -313, -3667, -1479, -1030, -632, -623, -1945, -1324, -1221, -878, -947, -1615, -697, -575, -482, -905, -869, -433, -484, -349, -667, -248, -1135, -888, -1019, -223, -2067, -729, -579, -659, -334, -340, -554, -455, -377, -375, -453, -834, -934, -334, -369, -290, -354, -497, -490, -329, -729, -1794, -151, -1162, -3935, -1013, -509, -825, -997, -320, -680, -422, -785, -542, -563, -489, -1283, -680, -78, -450, -514, -449, -548, -661, -641, -503, -570, -257, -394, -450] }, + "unselected": { + "line": { + "opacity": 0.5 + } + }, + "dimensions": [ { "constraintrange": [100000, 150000], diff --git a/test/jasmine/tests/parcoords_test.js b/test/jasmine/tests/parcoords_test.js index cb02e44a018..2b1198cb3e1 100644 --- a/test/jasmine/tests/parcoords_test.js +++ b/test/jasmine/tests/parcoords_test.js @@ -892,6 +892,128 @@ describe('parcoords Lifecycle methods', function() { }) .then(done, done.fail); }); + + it('@gl unselected.line.color `Plotly.restyle` should change context layer line.color', function(done) { + var testLayer = '.gl-canvas-context'; + + var list1 = []; + var list2 = []; + for(var i = 0; i <= 100; i++) { + list1[i] = i; + list2[i] = 100 - i; + } + + Plotly.newPlot(gd, [{ + type: 'parcoords', + dimensions: [{ + constraintrange: [1, 10], + values: list1 + }, { + values: list2 + }], + line: {color: '#0F0'}, + unselected: {line: {color: '#F00'}} + }], { + margin: { + t: 0, + b: 0, + l: 0, + r: 0 + }, + width: 300, + height: 200 + }) + .then(function() { + var rgb = getAvgPixelByChannel(testLayer); + expect(rgb[0]).not.toBe(0, 'red'); + expect(rgb[1]).toBe(0, 'no green'); + expect(rgb[2]).toBe(0, 'no blue'); + + return Plotly.restyle(gd, 'unselected.line.color', '#00F'); + }) + .then(function() { + var rgb = getAvgPixelByChannel(testLayer); + expect(rgb[0]).toBe(0, 'no red'); + expect(rgb[1]).toBe(0, 'no green'); + expect(rgb[2]).not.toBe(0, 'blue'); + + return Plotly.restyle(gd, 'unselected.line.color', 'rgba(0,0,0,0)'); + }) + .then(function() { + var rgb = getAvgPixelByChannel(testLayer); + expect(rgb[0]).toBe(0, 'no red'); + expect(rgb[1]).toBe(0, 'no green'); + expect(rgb[2]).toBe(0, 'no blue'); + }) + .then(done, done.fail); + }); + + it('@gl unselected.line.color `Plotly.react` should change line.color and unselected.line.color', function(done) { + var unselectedLayer = '.gl-canvas-context'; + var selectedLayer = '.gl-canvas-focus'; + + var list1 = []; + var list2 = []; + for(var i = 0; i <= 100; i++) { + list1[i] = i; + list2[i] = 100 - i; + } + + var fig = { + data: [{ + type: 'parcoords', + dimensions: [{ + constraintrange: [1, 10], + values: list1 + }, { + values: list2 + }], + line: {color: '#0F0'}, + unselected: {line: {color: '#F00'}} + }], + layout: { + margin: { + t: 0, + b: 0, + l: 0, + r: 0 + }, + width: 300, + height: 200 + } + }; + + var rgb; + + Plotly.newPlot(gd, fig) + .then(function() { + rgb = getAvgPixelByChannel(unselectedLayer); + expect(rgb[0]).not.toBe(0, 'red'); + expect(rgb[1]).toBe(0, 'no green'); + expect(rgb[2]).toBe(0, 'no blue'); + + rgb = getAvgPixelByChannel(selectedLayer); + expect(rgb[0]).toBe(0, 'no red'); + expect(rgb[1]).not.toBe(0, 'green'); + expect(rgb[2]).toBe(0, 'no blue'); + + fig.data[0].line.color = '#FF0'; + fig.data[0].unselected.line.color = '#00F'; + return Plotly.react(gd, fig); + }) + .then(function() { + rgb = getAvgPixelByChannel(selectedLayer); + expect(rgb[0]).not.toBe(0, 'red'); + expect(rgb[1]).not.toBe(0, 'green'); + expect(rgb[2]).toBe(0, 'no blue'); + + rgb = getAvgPixelByChannel(unselectedLayer); + expect(rgb[0]).toBe(0, 'no red'); + expect(rgb[1]).toBe(0, 'no green'); + expect(rgb[2]).not.toBe(0, 'blue'); + }) + .then(done, done.fail); + }); }); describe('parcoords hover', function() { diff --git a/test/plot-schema.json b/test/plot-schema.json index e293afda4dc..ed718216944 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -40355,6 +40355,28 @@ "editType": "none", "valType": "any" }, + "unselected": { + "editType": "plot", + "line": { + "color": { + "description": "Sets the base color of unselected lines. in connection with `unselected.line.opacity`.", + "dflt": "#777", + "editType": "plot", + "valType": "color" + }, + "editType": "plot", + "opacity": { + "description": "Sets the opacity of unselected lines. The default *auto* decreases the opacity smoothly as the number of lines increases. Use *1* to achieve exact `unselected.line.color`.", + "dflt": "auto", + "editType": "plot", + "max": 1, + "min": 0, + "valType": "number" + }, + "role": "object" + }, + "role": "object" + }, "visible": { "description": "Determines whether or not this trace is visible. If *legendonly*, the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible).", "dflt": true,