diff --git a/src/traces/parcoords/attributes.js b/src/traces/parcoords/attributes.js
index 52f3a12a7f5..67b9f25c437 100644
--- a/src/traces/parcoords/attributes.js
+++ b/src/traces/parcoords/attributes.js
@@ -17,18 +17,46 @@ var extendFlat = require('../../lib/extend').extendFlat;
var templatedArray = require('../../plot_api/plot_template').templatedArray;
module.exports = {
- domain: domainAttrs({name: 'parcoords', trace: true, editType: 'calc'}),
+ domain: domainAttrs({name: 'parcoords', trace: true, editType: 'plot'}),
+
+ labelangle: {
+ valType: 'angle',
+ dflt: 0,
+ role: 'info',
+ editType: 'plot',
+ description: [
+ 'Sets the angle of the labels with respect to the horizontal.',
+ 'For example, a `tickangle` of -90 draws the labels vertically.',
+ 'Tilted labels with *labelangle* may be positioned better',
+ 'inside margins when `labelposition` is set to *bottom*.'
+ ].join(' ')
+ },
+
+ labelside: {
+ valType: 'enumerated',
+ role: 'info',
+ values: ['top', 'bottom'],
+ dflt: 'top',
+ editType: 'plot',
+ description: [
+ 'Specifies the location of the `label`.',
+ '*top* positions labels above, next to the title',
+ '*bottom* positions labels below the graph',
+ 'Tilted labels with *labelangle* may be positioned better',
+ 'inside margins when `labelposition` is set to *bottom*.'
+ ].join(' ')
+ },
labelfont: fontAttrs({
- editType: 'calc',
+ editType: 'plot',
description: 'Sets the font for the `dimension` labels.'
}),
tickfont: fontAttrs({
- editType: 'calc',
+ editType: 'plot',
description: 'Sets the font for the `dimension` tick values.'
}),
rangefont: fontAttrs({
- editType: 'calc',
+ editType: 'plot',
description: 'Sets the font for the `dimension` range values.'
}),
@@ -36,49 +64,41 @@ module.exports = {
label: {
valType: 'string',
role: 'info',
- editType: 'calc',
+ editType: 'plot',
description: 'The shown name of the dimension.'
},
// TODO: better way to determine ordinal vs continuous axes,
// so users can use tickvals/ticktext with a continuous axis.
tickvals: extendFlat({}, axesAttrs.tickvals, {
- editType: 'calc',
+ editType: 'plot',
description: [
'Sets the values at which ticks on this axis appear.'
].join(' ')
}),
ticktext: extendFlat({}, axesAttrs.ticktext, {
- editType: 'calc',
+ editType: 'plot',
description: [
'Sets the text displayed at the ticks position via `tickvals`.'
].join(' ')
}),
- tickformat: {
- valType: 'string',
- dflt: '3s',
- role: 'style',
- editType: 'calc',
- description: [
- 'Sets the tick label formatting rule using d3 formatting mini-language',
- 'which is similar to those of Python. See',
- 'https://github.com/d3/d3-format/blob/master/README.md#locale_format'
- ].join(' ')
- },
+ tickformat: extendFlat({}, axesAttrs.tickformat, {
+ editType: 'plot'
+ }),
visible: {
valType: 'boolean',
dflt: true,
role: 'info',
- editType: 'calc',
+ editType: 'plot',
description: 'Shows the dimension when set to `true` (the default). Hides the dimension for `false`.'
},
range: {
valType: 'info_array',
role: 'info',
items: [
- {valType: 'number', editType: 'calc'},
- {valType: 'number', editType: 'calc'}
+ {valType: 'number', editType: 'plot'},
+ {valType: 'number', editType: 'plot'}
],
- editType: 'calc',
+ editType: 'plot',
description: [
'The domain range that represents the full, shown axis extent. Defaults to the `values` extent.',
'Must be an array of `[fromValue, toValue]` with finite numbers as elements.'
@@ -90,10 +110,10 @@ module.exports = {
freeLength: true,
dimensions: '1-2',
items: [
- {valType: 'number', editType: 'calc'},
- {valType: 'number', editType: 'calc'}
+ {valType: 'number', editType: 'plot'},
+ {valType: 'number', editType: 'plot'}
],
- editType: 'calc',
+ editType: 'plot',
description: [
'The domain range to which the filter on the dimension is constrained. Must be an array',
'of `[fromValue, toValue]` with `fromValue <= toValue`, or if `multiselect` is not',
@@ -104,7 +124,7 @@ module.exports = {
valType: 'boolean',
dflt: true,
role: 'info',
- editType: 'calc',
+ editType: 'plot',
description: 'Do we allow multiple selection ranges or just a single range?'
},
values: {
diff --git a/src/traces/parcoords/axisbrush.js b/src/traces/parcoords/axisbrush.js
index b0add8c017e..e67c4c108e2 100644
--- a/src/traces/parcoords/axisbrush.js
+++ b/src/traces/parcoords/axisbrush.js
@@ -419,7 +419,7 @@ function getBrushExtent(brush) {
function brushClear(brush) {
brush.filterSpecified = false;
- brush.svgBrush.extent = [[0, 1]];
+ brush.svgBrush.extent = [[-Infinity, Infinity]];
}
function axisBrushMoved(callback) {
@@ -458,6 +458,14 @@ function makeFilter() {
filter = a
.map(function(d) { return d.slice().sort(sortAsc); })
.sort(startAsc);
+
+ // handle unselected case
+ if(filter.length === 1 &&
+ filter[0][0] === -Infinity &&
+ filter[0][1] === Infinity) {
+ filter = [[0, -1]];
+ }
+
consolidated = dedupeRealRanges(filter);
bounds = filter.reduce(function(p, n) {
return [Math.min(p[0], n[0]), Math.max(p[1], n[1])];
diff --git a/src/traces/parcoords/base_plot.js b/src/traces/parcoords/base_plot.js
index a954649ec28..d6d9bfb7278 100644
--- a/src/traces/parcoords/base_plot.js
+++ b/src/traces/parcoords/base_plot.js
@@ -13,18 +13,16 @@ var getModuleCalcData = require('../../plots/get_data').getModuleCalcData;
var parcoordsPlot = require('./plot');
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
-var PARCOORDS = 'parcoords';
-
-exports.name = PARCOORDS;
+exports.name = 'parcoords';
exports.plot = function(gd) {
- var calcData = getModuleCalcData(gd.calcdata, PARCOORDS)[0];
+ var calcData = getModuleCalcData(gd.calcdata, 'parcoords')[0];
if(calcData.length) parcoordsPlot(gd, calcData);
};
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
- var hadParcoords = (oldFullLayout._has && oldFullLayout._has(PARCOORDS));
- var hasParcoords = (newFullLayout._has && newFullLayout._has(PARCOORDS));
+ var hadParcoords = (oldFullLayout._has && oldFullLayout._has('parcoords'));
+ var hasParcoords = (newFullLayout._has && newFullLayout._has('parcoords'));
if(hadParcoords && !hasParcoords) {
oldFullLayout._paperdiv.selectAll('.parcoords').remove();
diff --git a/src/traces/parcoords/calc.js b/src/traces/parcoords/calc.js
index 11078ac5d57..2588e84d727 100644
--- a/src/traces/parcoords/calc.js
+++ b/src/traces/parcoords/calc.js
@@ -8,20 +8,15 @@
'use strict';
+var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray;
var Colorscale = require('../../components/colorscale');
-var Lib = require('../../lib');
var wrap = require('../../lib/gup').wrap;
module.exports = function calc(gd, trace) {
- for(var i = 0; i < trace.dimensions.length; i++) {
- trace.dimensions[i].values = convertTypedArray(trace.dimensions[i].values);
- }
- trace.line.color = convertTypedArray(trace.line.color);
-
var lineColor;
var cscale;
- if(Colorscale.hasColorscale(trace, 'line') && Array.isArray(trace.line.color)) {
+ if(Colorscale.hasColorscale(trace, 'line') && isArrayOrTypedArray(trace.line.color)) {
lineColor = trace.line.color;
cscale = Colorscale.extractOpts(trace.line).colorscale;
@@ -45,7 +40,3 @@ function constHalf(len) {
}
return out;
}
-
-function convertTypedArray(a) {
- return Lib.isTypedArray(a) ? Array.prototype.slice.call(a) : a;
-}
diff --git a/src/traces/parcoords/constants.js b/src/traces/parcoords/constants.js
index 3577b913f28..7e855efff65 100644
--- a/src/traces/parcoords/constants.js
+++ b/src/traces/parcoords/constants.js
@@ -19,6 +19,7 @@ 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 5d96dd4c8e4..ce3212b241a 100644
--- a/src/traces/parcoords/defaults.js
+++ b/src/traces/parcoords/defaults.js
@@ -13,6 +13,7 @@ var hasColorscale = require('../../components/colorscale/helpers').hasColorscale
var colorscaleDefaults = require('../../components/colorscale/defaults');
var handleDomainDefaults = require('../../plots/domain').defaults;
var handleArrayContainerDefaults = require('../../plots/array_container_defaults');
+var Axes = require('../../plots/cartesian/axes');
var attributes = require('./attributes');
var axisBrush = require('./axisbrush');
@@ -37,7 +38,7 @@ function handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) {
return Infinity;
}
-function dimensionDefaults(dimensionIn, dimensionOut) {
+function dimensionDefaults(dimensionIn, dimensionOut, parentOut, opts) {
function coerce(attr, dflt) {
return Lib.coerce(dimensionIn, dimensionOut, attributes.dimensions, attr, dflt);
}
@@ -53,7 +54,17 @@ function dimensionDefaults(dimensionIn, dimensionOut) {
coerce('tickvals');
coerce('ticktext');
coerce('tickformat');
- coerce('range');
+ var range = coerce('range');
+
+ dimensionOut._ax = {
+ _id: 'y',
+ type: 'linear',
+ showexponent: 'all',
+ exponentformat: 'B',
+ range: range
+ };
+
+ Axes.setConvert(dimensionOut._ax, opts.layout);
coerce('multiselect');
var constraintRange = coerce('constraintrange');
@@ -76,6 +87,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
var dimensions = handleArrayContainerDefaults(traceIn, traceOut, {
name: 'dimensions',
+ layout: layout,
handleItemDefaults: dimensionDefaults
});
@@ -100,4 +112,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
Lib.coerceFont(coerce, 'labelfont', fontDflt);
Lib.coerceFont(coerce, 'tickfont', fontDflt);
Lib.coerceFont(coerce, 'rangefont', fontDflt);
+
+ coerce('labelangle');
+ coerce('labelside');
};
diff --git a/src/traces/parcoords/helpers.js b/src/traces/parcoords/helpers.js
new file mode 100644
index 00000000000..d7625fc8894
--- /dev/null
+++ b/src/traces/parcoords/helpers.js
@@ -0,0 +1,23 @@
+/**
+* Copyright 2012-2019, Plotly, Inc.
+* All rights reserved.
+*
+* This source code is licensed under the MIT license found in the
+* LICENSE file in the root directory of this source tree.
+*/
+
+'use strict';
+
+var isTypedArray = require('../../lib').isTypedArray;
+
+exports.convertTypedArray = function(a) {
+ return isTypedArray(a) ? Array.prototype.slice.call(a) : a;
+};
+
+exports.isOrdinal = function(dimension) {
+ return !!dimension.tickvals;
+};
+
+exports.isVisible = function(dimension) {
+ return dimension.visible || !('visible' in dimension);
+};
diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js
index f7a5a927e50..08d6cafd814 100644
--- a/src/traces/parcoords/lines.js
+++ b/src/traces/parcoords/lines.js
@@ -10,31 +10,19 @@
var glslify = require('glslify');
var vertexShaderSource = glslify('./shaders/vertex.glsl');
-var contextShaderSource = glslify('./shaders/context_vertex.glsl');
-var pickVertexShaderSource = glslify('./shaders/pick_vertex.glsl');
var fragmentShaderSource = glslify('./shaders/fragment.glsl');
+var maxDim = require('./constants').maxDimensionCount;
var Lib = require('../../lib');
// don't change; otherwise near/far plane lines are lost
var depthLimitEpsilon = 1e-6;
-// just enough buffer for an extra bit at single-precision floating point
-// which on [0, 1] is 6e-8 (1/2^24)
-var filterEpsilon = 1e-7;
// precision of multiselect is the full range divided into this many parts
var maskHeight = 2048;
-var gpuDimensionCount = 64;
-var sectionVertexCount = 2;
-var vec4NumberCount = 4;
-var bitsPerByte = 8;
-var channelCount = gpuDimensionCount / bitsPerByte; // == 8 bytes needed to have 64 bits
-
-var contextColor = [119, 119, 119]; // middle gray to not drawn the focus; looks good on a black or white background
-
var dummyPixel = new Uint8Array(4);
-var pickPixel = new Uint8Array(4);
+var dataPixel = new Uint8Array(4);
var paletteTextureConfig = {
shape: [256, 1],
@@ -65,12 +53,8 @@ function renderBlock(regl, glAes, renderState, blockLineCount, sampleCount, item
var rafKey = item.key;
function render(blockNumber) {
- var count;
-
- count = Math.min(blockLineCount, sampleCount - blockNumber * blockLineCount);
+ var count = Math.min(blockLineCount, sampleCount - blockNumber * blockLineCount);
- item.offset = sectionVertexCount * blockNumber * blockLineCount;
- item.count = sectionVertexCount * count;
if(blockNumber === 0) {
// stop drawing possibly stale glyphs before clearing
window.cancelAnimationFrame(renderState.currentRafs[rafKey]);
@@ -82,6 +66,8 @@ function renderBlock(regl, glAes, renderState, blockLineCount, sampleCount, item
return;
}
+ item.count = 2 * count;
+ item.offset = 2 * blockNumber * blockLineCount;
glAes(item);
if(blockNumber * blockLineCount + count < sampleCount) {
@@ -109,13 +95,11 @@ function adjustDepth(d) {
return Math.max(depthLimitEpsilon, Math.min(1 - depthLimitEpsilon, d));
}
-function palette(unitToColor, context, opacity) {
- var result = [];
- for(var j = 0; j < 256; j++) {
- var c = unitToColor(j / 255);
- result.push((context ? contextColor : c).concat(opacity));
+function palette(unitToColor, opacity) {
+ var result = new Array(256);
+ for(var i = 0; i < 256; i++) {
+ result[i] = unitToColor(i / 255).concat(opacity);
}
-
return result;
}
@@ -124,65 +108,133 @@ function palette(unitToColor, context, opacity) {
// with the end result that each line will be of a unique color, making it possible for the pick handler
// to uniquely identify which line is hovered over (bijective mapping).
// The inverse, i.e. readPixel is invoked from 'parcoords.js'
-function calcPickColor(j, rgbIndex) {
- return (j >>> 8 * rgbIndex) % 256 / 255;
+function calcPickColor(i, rgbIndex) {
+ return (i >>> 8 * rgbIndex) % 256 / 255;
}
-function makePoints(sampleCount, dimensions, color) {
- var dimensionCount = dimensions.length;
-
- var points = [];
- for(var j = 0; j < sampleCount; j++) {
- for(var i = 0; i < gpuDimensionCount; i++) {
- points.push(i < dimensionCount ?
- dimensions[i].paddedUnitValues[j] :
- i === (gpuDimensionCount - 1) ?
- adjustDepth(color[j]) :
- i >= gpuDimensionCount - 4 ?
- calcPickColor(j, gpuDimensionCount - 2 - i) :
- 0.5);
+function makePoints(sampleCount, dims, color) {
+ var points = new Array(sampleCount * (maxDim + 4));
+ var n = 0;
+ for(var i = 0; i < sampleCount; i++) {
+ for(var k = 0; k < maxDim; k++) {
+ points[n++] = (k < dims.length) ? dims[k].paddedUnitValues[i] : 0.5;
}
+ points[n++] = calcPickColor(i, 2);
+ points[n++] = calcPickColor(i, 1);
+ points[n++] = calcPickColor(i, 0);
+ points[n++] = adjustDepth(color[i]);
}
-
return points;
}
-function makeVecAttr(sampleCount, points, vecIndex) {
- var i, j, k;
- var pointPairs = [];
-
- for(j = 0; j < sampleCount; j++) {
- for(k = 0; k < sectionVertexCount; k++) {
- for(i = 0; i < vec4NumberCount; i++) {
- pointPairs.push(points[j * gpuDimensionCount + vecIndex * vec4NumberCount + i]);
- if(vecIndex * vec4NumberCount + i === gpuDimensionCount - 1 && k % 2 === 0) {
- pointPairs[pointPairs.length - 1] *= -1;
+function makeVecAttr(vecIndex, sampleCount, points) {
+ var pointPairs = new Array(sampleCount * 8);
+ var n = 0;
+ for(var i = 0; i < sampleCount; i++) {
+ for(var j = 0; j < 2; j++) {
+ for(var k = 0; k < 4; k++) {
+ var q = vecIndex * 4 + k;
+ var v = points[i * 64 + q];
+ if(q === 63 && j === 0) {
+ v *= -1;
}
+ pointPairs[n++] = v;
}
}
}
-
return pointPairs;
}
+function pad2(num) {
+ var s = '0' + num;
+ return s.substr(s.length - 2);
+}
+
+function getAttrName(i) {
+ return (i < maxDim) ? 'p' + pad2(i + 1) + '_' + pad2(i + 4) : 'colors';
+}
+
function setAttributes(attributes, sampleCount, points) {
- for(var i = 0; i < 16; i++) {
- attributes['p' + i.toString(16)](makeVecAttr(sampleCount, points, i));
+ for(var i = 0; i <= maxDim; i += 4) {
+ attributes[getAttrName(i)](makeVecAttr(i / 4, sampleCount, points));
}
}
function emptyAttributes(regl) {
var attributes = {};
- for(var i = 0; i < 16; i++) {
- attributes['p' + i.toString(16)] = regl.buffer({usage: 'dynamic', type: 'float', data: new Uint8Array(0)});
+ for(var i = 0; i <= maxDim; i += 4) {
+ attributes[getAttrName(i)] = regl.buffer({usage: 'dynamic', type: 'float', data: new Uint8Array(0)});
}
return attributes;
}
+function makeItem(model, leftmost, rightmost, itemNumber, i0, i1, x, y, panelSizeX, panelSizeY, crossfilterDimensionIndex, drwLayer, constraints) {
+ var dims = [[], []];
+ for(var k = 0; k < 64; k++) {
+ dims[0][k] = (k === i0) ? 1 : 0;
+ dims[1][k] = (k === i1) ? 1 : 0;
+ }
+
+ var overdrag = model.lines.canvasOverdrag;
+ var domain = model.domain;
+ var canvasWidth = model.canvasWidth;
+ var canvasHeight = model.canvasHeight;
+
+ var deselectedLinesColor = model.deselectedLines.color;
+
+ var itemModel = Lib.extendFlat({
+ key: crossfilterDimensionIndex,
+ resolution: [canvasWidth, canvasHeight],
+ viewBoxPos: [x + overdrag, y],
+ viewBoxSize: [panelSizeX, panelSizeY],
+ i0: i0,
+ i1: i1,
+
+ dim0A: dims[0].slice(0, 16),
+ dim0B: dims[0].slice(16, 32),
+ dim0C: dims[0].slice(32, 48),
+ dim0D: dims[0].slice(48, 64),
+ dim1A: dims[1].slice(0, 16),
+ dim1B: dims[1].slice(16, 32),
+ dim1C: dims[1].slice(32, 48),
+ dim1D: dims[1].slice(48, 64),
+
+ drwLayer: drwLayer,
+ contextColor: [
+ deselectedLinesColor[0] / 255,
+ deselectedLinesColor[1] / 255,
+ deselectedLinesColor[2] / 255,
+ deselectedLinesColor[3] < 1 ?
+ deselectedLinesColor[3] :
+ Math.max(1 / 255, Math.pow(1 / model.lines.color.length, 1 / 3))
+ ],
+
+ scissorX: (itemNumber === leftmost ? 0 : x + overdrag) + (model.pad.l - overdrag) + model.layoutWidth * domain.x[0],
+ scissorWidth: (itemNumber === rightmost ? canvasWidth - x + overdrag : panelSizeX + 0.5) + (itemNumber === leftmost ? x + overdrag : 0),
+ scissorY: y + model.pad.b + model.layoutHeight * domain.y[0],
+ scissorHeight: panelSizeY,
+
+ viewportX: model.pad.l - overdrag + model.layoutWidth * domain.x[0],
+ viewportY: model.pad.b + model.layoutHeight * domain.y[0],
+ viewportWidth: canvasWidth,
+ viewportHeight: canvasHeight
+ }, constraints);
+
+ return itemModel;
+}
+
+function expandedPixelRange(bounds) {
+ var dh = maskHeight - 1;
+ return [
+ Math.max(0, Math.floor(bounds[0] * dh), 0),
+ Math.min(dh, Math.ceil(bounds[1] * dh), dh)
+ ];
+}
+
module.exports = function(canvasGL, d) {
// context & pick describe which canvas we're talking about - won't change with new data
- var context = d.context;
- var pick = d.pick;
+ var isContext = d.context;
+ var isPick = d.pick;
var regl = d.regl;
@@ -201,6 +253,8 @@ module.exports = function(canvasGL, d) {
var maskTexture;
var paletteTexture = regl.texture(paletteTextureConfig);
+ var prevAxisOrder = [];
+
update(d);
var glAes = regl({
@@ -208,7 +262,7 @@ module.exports = function(canvasGL, d) {
profile: false,
blend: {
- enable: context,
+ enable: isContext,
func: {
srcRGB: 'src alpha',
dstRGB: 'one minus src alpha',
@@ -223,7 +277,7 @@ module.exports = function(canvasGL, d) {
},
depth: {
- enable: !context,
+ enable: !isContext,
mask: true,
func: 'less',
range: [0, 1]
@@ -254,7 +308,7 @@ module.exports = function(canvasGL, d) {
dither: false,
- vert: pick ? pickVertexShaderSource : context ? contextShaderSource : vertexShaderSource,
+ vert: vertexShaderSource,
frag: fragmentShaderSource,
@@ -263,16 +317,16 @@ module.exports = function(canvasGL, d) {
attributes: attributes,
uniforms: {
resolution: regl.prop('resolution'),
- viewBoxPosition: regl.prop('viewBoxPosition'),
+ viewBoxPos: regl.prop('viewBoxPos'),
viewBoxSize: regl.prop('viewBoxSize'),
+ dim0A: regl.prop('dim0A'),
dim1A: regl.prop('dim1A'),
- dim2A: regl.prop('dim2A'),
+ dim0B: regl.prop('dim0B'),
dim1B: regl.prop('dim1B'),
- dim2B: regl.prop('dim2B'),
+ dim0C: regl.prop('dim0C'),
dim1C: regl.prop('dim1C'),
- dim2C: regl.prop('dim2C'),
+ dim0D: regl.prop('dim0D'),
dim1D: regl.prop('dim1D'),
- dim2D: regl.prop('dim2D'),
loA: regl.prop('loA'),
hiA: regl.prop('hiA'),
loB: regl.prop('loB'),
@@ -282,9 +336,10 @@ module.exports = function(canvasGL, d) {
loD: regl.prop('loD'),
hiD: regl.prop('hiD'),
palette: paletteTexture,
+ contextColor: regl.prop('contextColor'),
mask: regl.prop('maskTexture'),
- maskHeight: regl.prop('maskHeight'),
- colorClamp: regl.prop('colorClamp')
+ drwLayer: regl.prop('drwLayer'),
+ maskHeight: regl.prop('maskHeight')
},
offset: regl.prop('offset'),
count: regl.prop('count')
@@ -297,129 +352,58 @@ module.exports = function(canvasGL, d) {
sampleCount = initialDims[0] ? initialDims[0].values.length : 0;
var lines = model.lines;
- var color = pick ? lines.color.map(function(_, i) {return i / lines.color.length;}) : lines.color;
- var contextOpacity = Math.max(1 / 255, Math.pow(1 / color.length, 1 / 3));
+ var color = isPick ? lines.color.map(function(_, i) {return i / lines.color.length;}) : lines.color;
var points = makePoints(sampleCount, initialDims, color);
setAttributes(attributes, sampleCount, points);
- paletteTexture = regl.texture(Lib.extendFlat({
- data: palette(model.unitToColor, context, Math.round((context ? contextOpacity : 1) * 255))
- }, paletteTextureConfig));
- }
-
- var colorClamp = [0, 1];
-
- function setColorDomain(unitDomain) {
- colorClamp[0] = unitDomain[0];
- colorClamp[1] = unitDomain[1];
- }
-
- var previousAxisOrder = [];
-
- function makeItem(i, ii, x, y, panelSizeX, canvasPanelSizeY, crossfilterDimensionIndex, I, leftmost, rightmost, constraints) {
- var loHi, abcd, d, index;
- var leftRight = [i, ii];
-
- var dims = [0, 1].map(function() {return [0, 1, 2, 3].map(function() {return new Float32Array(16);});});
-
- for(loHi = 0; loHi < 2; loHi++) {
- index = leftRight[loHi];
- for(abcd = 0; abcd < 4; abcd++) {
- for(d = 0; d < 16; d++) {
- dims[loHi][abcd][d] = d + 16 * abcd === index ? 1 : 0;
- }
- }
+ if(!isContext && !isPick) {
+ paletteTexture = regl.texture(Lib.extendFlat({
+ data: palette(model.unitToColor, 255)
+ }, paletteTextureConfig));
}
-
- var overdrag = model.lines.canvasOverdrag;
- var domain = model.domain;
- var canvasWidth = model.canvasWidth;
- var canvasHeight = model.canvasHeight;
-
- var itemModel = Lib.extendFlat({
- key: crossfilterDimensionIndex,
- resolution: [canvasWidth, canvasHeight],
- viewBoxPosition: [x + overdrag, y],
- viewBoxSize: [panelSizeX, canvasPanelSizeY],
- i: i,
- ii: ii,
-
- dim1A: dims[0][0],
- dim1B: dims[0][1],
- dim1C: dims[0][2],
- dim1D: dims[0][3],
- dim2A: dims[1][0],
- dim2B: dims[1][1],
- dim2C: dims[1][2],
- dim2D: dims[1][3],
-
- colorClamp: colorClamp,
-
- scissorX: (I === leftmost ? 0 : x + overdrag) + (model.pad.l - overdrag) + model.layoutWidth * domain.x[0],
- scissorWidth: (I === rightmost ? canvasWidth - x + overdrag : panelSizeX + 0.5) + (I === leftmost ? x + overdrag : 0),
- scissorY: y + model.pad.b + model.layoutHeight * domain.y[0],
- scissorHeight: canvasPanelSizeY,
-
- viewportX: model.pad.l - overdrag + model.layoutWidth * domain.x[0],
- viewportY: model.pad.b + model.layoutHeight * domain.y[0],
- viewportWidth: canvasWidth,
- viewportHeight: canvasHeight
- }, constraints);
-
- return itemModel;
}
- function makeConstraints() {
- var loHi, abcd, d;
+ function makeConstraints(isContext) {
+ var i, j, k;
- var lims = [0, 1].map(function() {return [0, 1, 2, 3].map(function() {return new Float32Array(16);});});
+ var limits = [[], []];
+ for(k = 0; k < 64; k++) {
+ var p = (!isContext && k < initialDims.length) ?
+ initialDims[k].brush.filter.getBounds() : [-Infinity, Infinity];
- for(loHi = 0; loHi < 2; loHi++) {
- for(abcd = 0; abcd < 4; abcd++) {
- for(d = 0; d < 16; d++) {
- var dimP = d + 16 * abcd;
- var lim;
- if(dimP < initialDims.length) {
- lim = initialDims[dimP].brush.filter.getBounds()[loHi];
- } else lim = loHi;
- lims[loHi][abcd][d] = lim + (2 * loHi - 1) * filterEpsilon;
- }
- }
+ limits[0][k] = p[0];
+ limits[1][k] = p[1];
}
- function expandedPixelRange(dim, bounds) {
- var maskHMinus = maskHeight - 1;
- return [
- Math.max(0, Math.floor(bounds[0] * maskHMinus)),
- Math.min(maskHMinus, Math.ceil(bounds[1] * maskHMinus))
- ];
+ var len = maskHeight * 8;
+ var mask = new Array(len);
+ for(i = 0; i < len; i++) {
+ mask[i] = 255;
}
-
- var mask = Array.apply(null, new Array(maskHeight * channelCount)).map(function() {
- return 255;
- });
- for(var dimIndex = 0; dimIndex < initialDims.length; dimIndex++) {
- var bitIndex = dimIndex % bitsPerByte;
- var byteIndex = (dimIndex - bitIndex) / bitsPerByte;
- var bitMask = Math.pow(2, bitIndex);
- var dim = initialDims[dimIndex];
- var ranges = dim.brush.filter.get();
- if(ranges.length < 2) continue; // bail if the bounding box based filter is sufficient
-
- var prevEnd = expandedPixelRange(dim, ranges[0])[1];
- for(var ri = 1; ri < ranges.length; ri++) {
- var nextRange = expandedPixelRange(dim, ranges[ri]);
- for(var pi = prevEnd + 1; pi < nextRange[0]; pi++) {
- mask[pi * channelCount + byteIndex] &= ~bitMask;
+ if(!isContext) {
+ for(i = 0; i < initialDims.length; i++) {
+ var u = i % 8;
+ var v = (i - u) / 8;
+ var bitMask = Math.pow(2, u);
+ var dim = initialDims[i];
+ var ranges = dim.brush.filter.get();
+ if(ranges.length < 2) continue; // bail if the bounding box based filter is sufficient
+
+ var prevEnd = expandedPixelRange(ranges[0])[1];
+ for(j = 1; j < ranges.length; j++) {
+ var nextRange = expandedPixelRange(ranges[j]);
+ for(k = prevEnd + 1; k < nextRange[0]; k++) {
+ mask[k * 8 + v] &= ~bitMask;
+ }
+ prevEnd = Math.max(prevEnd, nextRange[1]);
}
- prevEnd = Math.max(prevEnd, nextRange[1]);
}
}
var textureData = {
// 8 units x 8 bits = 64 bits, just sufficient for the almost 64 dimensions we support
- shape: [channelCount, maskHeight],
+ shape: [8, maskHeight],
format: 'alpha',
type: 'uint8',
mag: 'nearest',
@@ -432,34 +416,34 @@ module.exports = function(canvasGL, d) {
return {
maskTexture: maskTexture,
maskHeight: maskHeight,
- loA: lims[0][0],
- loB: lims[0][1],
- loC: lims[0][2],
- loD: lims[0][3],
- hiA: lims[1][0],
- hiB: lims[1][1],
- hiC: lims[1][2],
- hiD: lims[1][3]
+ loA: limits[0].slice(0, 16),
+ loB: limits[0].slice(16, 32),
+ loC: limits[0].slice(32, 48),
+ loD: limits[0].slice(48, 64),
+ hiA: limits[1].slice(0, 16),
+ hiB: limits[1].slice(16, 32),
+ hiC: limits[1].slice(32, 48),
+ hiD: limits[1].slice(48, 64),
};
}
function renderGLParcoords(panels, setChanged, clearOnly) {
var panelCount = panels.length;
- var I;
+ var i;
var leftmost;
var rightmost;
var lowestX = Infinity;
var highestX = -Infinity;
- for(I = 0; I < panelCount; I++) {
- if(panels[I].dim2.canvasX > highestX) {
- highestX = panels[I].dim2.canvasX;
- rightmost = I;
+ for(i = 0; i < panelCount; i++) {
+ if(panels[i].dim0.canvasX < lowestX) {
+ lowestX = panels[i].dim0.canvasX;
+ leftmost = i;
}
- if(panels[I].dim1.canvasX < lowestX) {
- lowestX = panels[I].dim1.canvasX;
- leftmost = I;
+ if(panels[i].dim1.canvasX > highestX) {
+ highestX = panels[i].dim1.canvasX;
+ rightmost = i;
}
}
@@ -467,24 +451,37 @@ module.exports = function(canvasGL, d) {
// clear canvas here, as the panel iteration below will not enter the loop body
clear(regl, 0, 0, model.canvasWidth, model.canvasHeight);
}
- var constraints = context ? {} : makeConstraints();
-
- for(I = 0; I < panelCount; I++) {
- var panel = panels[I];
- var dim1 = panel.dim1;
- var i = dim1.crossfilterDimensionIndex;
- var x = panel.canvasX;
- var y = panel.canvasY;
- var dim2 = panel.dim2;
- var ii = dim2.crossfilterDimensionIndex;
- var panelSizeX = panel.panelSizeX;
- var panelSizeY = panel.panelSizeY;
- var xTo = x + panelSizeX;
- if(setChanged || !previousAxisOrder[i] || previousAxisOrder[i][0] !== x || previousAxisOrder[i][1] !== xTo) {
- previousAxisOrder[i] = [x, xTo];
- var item = makeItem(i, ii, x, y, panelSizeX, panelSizeY, dim1.crossfilterDimensionIndex, I, leftmost, rightmost, constraints);
+ var constraints = makeConstraints(isContext);
+
+ for(i = 0; i < panelCount; i++) {
+ var p = panels[i];
+ var i0 = p.dim0.crossfilterDimensionIndex;
+ var i1 = p.dim1.crossfilterDimensionIndex;
+ var x = p.canvasX;
+ var y = p.canvasY;
+ var nextX = x + p.panelSizeX;
+ if(setChanged ||
+ !prevAxisOrder[i0] ||
+ prevAxisOrder[i0][0] !== x ||
+ prevAxisOrder[i0][1] !== nextX
+ ) {
+ prevAxisOrder[i0] = [x, nextX];
+
+ var item = makeItem(
+ model,
+ leftmost, rightmost, i, i0, i1, x, y,
+ p.panelSizeX, p.panelSizeY,
+ p.dim0.crossfilterDimensionIndex,
+ isContext ? 0 : isPick ? 2 : 1,
+ constraints
+ );
+
renderState.clearOnly = clearOnly;
- renderBlock(regl, glAes, renderState, setChanged ? model.lines.blockLineCount : sampleCount, sampleCount, item);
+
+ var blockLineCount = setChanged ? model.lines.blockLineCount : sampleCount;
+ renderBlock(
+ regl, glAes, renderState, blockLineCount, sampleCount, item
+ );
}
}
}
@@ -495,9 +492,9 @@ module.exports = function(canvasGL, d) {
y: canvasY,
width: 1,
height: 1,
- data: pickPixel
+ data: dataPixel
});
- return pickPixel;
+ return dataPixel;
}
function readPixels(canvasX, canvasY, width, height) {
@@ -520,7 +517,6 @@ module.exports = function(canvasGL, d) {
}
return {
- setColorDomain: setColorDomain,
render: renderGLParcoords,
readPixel: readPixel,
readPixels: readPixels,
diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js
index c3622cb2649..b3a59b9eda4 100644
--- a/src/traces/parcoords/parcoords.js
+++ b/src/traces/parcoords/parcoords.js
@@ -11,7 +11,9 @@
var d3 = require('d3');
var rgba = require('color-rgba');
+var Axes = require('../../plots/cartesian/axes');
var Lib = require('../../lib');
+var svgTextUtils = require('../../lib/svg_text_utils');
var Drawing = require('../../components/drawing');
var Colorscale = require('../../components/colorscale');
@@ -20,12 +22,11 @@ var keyFun = gup.keyFun;
var repeat = gup.repeat;
var unwrap = gup.unwrap;
+var helpers = require('./helpers');
var c = require('./constants');
var brush = require('./axisbrush');
var lineLayerMaker = require('./lines');
-function visible(dimension) { return !('visible' in dimension) || dimension.visible; }
-
function dimensionExtent(dimension) {
var lo = dimension.range ? dimension.range[0] : Lib.aggNums(Math.min, null, dimension.values, dimension._length);
var hi = dimension.range ? dimension.range[1] : Lib.aggNums(Math.max, null, dimension.values, dimension._length);
@@ -82,7 +83,9 @@ function domainScale(height, padding, dimension, tickvals, ticktext) {
.range([height - padding, padding]);
}
-function unitToPaddedPx(height, padding) { return d3.scale.linear().range([padding, height - padding]); }
+function unitToPaddedPx(height, padding) {
+ return d3.scale.linear().range([padding, height - padding]);
+}
function domainToPaddedUnitScale(dimension, padFraction) {
return d3.scale.linear()
@@ -134,13 +137,16 @@ function someFiltersActive(view) {
function model(layout, d, i) {
var cd0 = unwrap(d);
var trace = cd0.trace;
- var lineColor = cd0.lineColor;
+ var lineColor = helpers.convertTypedArray(cd0.lineColor);
var line = trace.line;
+ var deselectedLines = {color: rgba(c.deselectedLineColor)};
var cOpts = Colorscale.extractOpts(line);
var cscale = cOpts.reversescale ? Colorscale.flipScale(cd0.cscale) : cd0.cscale;
var domain = trace.domain;
var dimensions = trace.dimensions;
var width = layout.width;
+ var labelAngle = trace.labelangle;
+ var labelSide = trace.labelside;
var labelFont = trace.labelfont;
var tickFont = trace.tickfont;
var rangeFont = trace.rangefont;
@@ -164,11 +170,14 @@ function model(layout, d, i) {
return {
key: i,
- colCount: dimensions.filter(visible).length,
+ colCount: dimensions.filter(helpers.isVisible).length,
dimensions: dimensions,
tickDistance: c.tickDistance,
unitToColor: unitToColorScale(cscale),
lines: lines,
+ deselectedLines: deselectedLines,
+ labelAngle: labelAngle,
+ labelSide: labelSide,
labelFont: labelFont,
tickFont: tickFont,
rangeFont: rangeFont,
@@ -206,7 +215,7 @@ function viewModel(state, callbacks, model) {
var uniqueKeys = {};
- viewModel.dimensions = dimensions.filter(visible).map(function(dimension, i) {
+ viewModel.dimensions = dimensions.filter(helpers.isVisible).map(function(dimension, i) {
var domainToPaddedUnit = domainToPaddedUnitScale(dimension, unitPad);
var foundKey = uniqueKeys[dimension.label];
uniqueKeys[dimension.label] = (foundKey || 0) + 1;
@@ -218,7 +227,7 @@ function viewModel(state, callbacks, model) {
}
var filterRange = filterRangeSpecified ?
specifiedConstraint.map(function(d) { return d.map(domainToPaddedUnit); }) :
- [[0, 1]];
+ [[-Infinity, Infinity]];
var brushMove = function() {
var p = viewModel;
p.focusLayer && p.focusLayer.render(p.panels, true);
@@ -266,13 +275,16 @@ function viewModel(state, callbacks, model) {
}
} else tickvals = undefined;
+ truncatedValues = helpers.convertTypedArray(truncatedValues);
+ truncatedValues = helpers.convertTypedArray(truncatedValues);
+
return {
key: key,
label: dimension.label,
tickFormat: dimension.tickformat,
tickvals: tickvals,
ticktext: ticktext,
- ordinal: !!tickvals,
+ ordinal: helpers.isOrdinal(dimension),
multiselect: dimension.multiselect,
xIndex: i,
crossfilterDimensionIndex: i,
@@ -336,19 +348,88 @@ function parcoordsInteractionState() {
};
}
-module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, callbacks) {
+function calcTilt(angle, position) {
+ var dir = (position === 'top') ? 1 : -1;
+ var radians = angle * Math.PI / 180;
+ var dx = Math.sin(radians);
+ var dy = Math.cos(radians);
+ return {
+ dir: dir,
+ dx: dx,
+ dy: dy,
+ degrees: angle
+ };
+}
+
+function updatePanelLayout(yAxis, vm) {
+ var panels = vm.panels || (vm.panels = []);
+ var data = yAxis.data();
+ for(var i = 0; i < data.length - 1; i++) {
+ var p = panels[i] || (panels[i] = {});
+ var dim0 = data[i];
+ var dim1 = data[i + 1];
+ p.dim0 = dim0;
+ p.dim1 = dim1;
+ p.canvasX = dim0.canvasX;
+ p.panelSizeX = dim1.canvasX - dim0.canvasX;
+ p.panelSizeY = vm.model.canvasHeight;
+ p.y = 0;
+ p.canvasY = 0;
+ }
+}
+
+function calcAllTicks(cd) {
+ for(var i = 0; i < cd.length; i++) {
+ for(var j = 0; j < cd[i].length; j++) {
+ var dimensions = cd[i][j].trace.dimensions;
+ for(var k = 0; k < dimensions.length; k++) {
+ var dim = dimensions[k]._ax;
+
+ if(dim) {
+ if(!dim.range) dim.range = [0, 1];
+ if(!dim.dtick) dim.dtick = 0.1;
+ dim.tickformat = dimensions[k].tickformat;
+
+ Axes.calcTicks(dim);
+
+ dim.cleanRange();
+ }
+ }
+ }
+ }
+}
+
+module.exports = function parcoords(gd, cdModule, layout, callbacks) {
var state = parcoordsInteractionState();
- var vm = styledData
+ var fullLayout = gd._fullLayout;
+ var svg = fullLayout._toppaper;
+ var glContainer = fullLayout._glcontainer;
+
+ function linearFormat(dim, v) {
+ return Axes.tickText(dim._ax, v, false).text;
+ }
+
+ function extremeText(d, isTop) {
+ if(d.ordinal) return '';
+ var domain = d.domainScale.domain();
+ var v = (domain[isTop ? domain.length - 1 : 0]);
+
+ return linearFormat(d.model.dimensions[d.visibleIndex], v);
+ }
+
+ calcAllTicks(cdModule);
+
+ var vm = cdModule
.filter(function(d) { return unwrap(d).trace.visible; })
.map(model.bind(0, layout))
.map(viewModel.bind(0, state, callbacks));
- parcoordsLineLayers.each(function(d, i) {
+ glContainer.each(function(d, i) {
return Lib.extendFlat(d, vm[i]);
});
- var parcoordsLineLayer = parcoordsLineLayers.selectAll('.gl-canvas')
+ var glLayers = glContainer.selectAll('.gl-canvas')
.each(function(d) {
// FIXME: figure out how to handle multiple instances
d.viewModel = vm[0];
@@ -357,7 +438,7 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
var lastHovered = null;
- var pickLayer = parcoordsLineLayer.filter(function(d) {return d.pick;});
+ var pickLayer = glLayers.filter(function(d) {return d.pick;});
// emit hover / unhover event
pickLayer
@@ -397,26 +478,26 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
}
});
- parcoordsLineLayer
- .style('opacity', function(d) {return d.pick ? 0.01 : 1;});
+ glLayers
+ .style('opacity', function(d) {return d.pick ? 0 : 1;});
svg.style('background', 'rgba(255, 255, 255, 0)');
- var parcoordsControlOverlay = svg.selectAll('.' + c.cn.parcoords)
+ var controlOverlay = svg.selectAll('.' + c.cn.parcoords)
.data(vm, keyFun);
- parcoordsControlOverlay.exit().remove();
+ controlOverlay.exit().remove();
- parcoordsControlOverlay.enter()
+ controlOverlay.enter()
.append('g')
.classed(c.cn.parcoords, true)
.style('shape-rendering', 'crispEdges')
.style('pointer-events', 'none');
- parcoordsControlOverlay.attr('transform', function(d) {
+ controlOverlay.attr('transform', function(d) {
return 'translate(' + d.model.translateX + ',' + d.model.translateY + ')';
});
- var parcoordsControlView = parcoordsControlOverlay.selectAll('.' + c.cn.parcoordsControlView)
+ var parcoordsControlView = controlOverlay.selectAll('.' + c.cn.parcoordsControlView)
.data(repeat, keyFun);
parcoordsControlView.enter()
@@ -430,24 +511,6 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
var yAxis = parcoordsControlView.selectAll('.' + c.cn.yAxis)
.data(function(vm) { return vm.dimensions; }, keyFun);
- function updatePanelLayout(yAxis, vm) {
- var panels = vm.panels || (vm.panels = []);
- var dimData = yAxis.data();
- var panelCount = dimData.length - 1;
- for(var p = 0; p < panelCount; p++) {
- var panel = panels[p] || (panels[p] = {});
- var dim1 = dimData[p];
- var dim2 = dimData[p + 1];
- panel.dim1 = dim1;
- panel.dim2 = dim2;
- panel.canvasX = dim1.canvasX;
- panel.panelSizeX = dim2.canvasX - dim1.canvasX;
- panel.panelSizeY = vm.model.canvasHeight;
- panel.y = 0;
- panel.canvasY = 0;
- }
- }
-
yAxis.enter()
.append('g')
.classed(c.cn.yAxis, true);
@@ -456,7 +519,7 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
updatePanelLayout(yAxis, vm);
});
- parcoordsLineLayer
+ glLayers
.each(function(d) {
if(d.viewModel) {
if(!d.lineLayer || callbacks) { // recreate in case of having callbacks e.g. restyle. Should we test for callback to be a restyle?
@@ -486,18 +549,18 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
d.canvasX = d.x * d.model.canvasPixelRatio;
yAxis
.sort(function(a, b) { return a.x - b.x; })
- .each(function(dd, i) {
- dd.xIndex = i;
- dd.x = d === dd ? dd.x : dd.xScale(dd.xIndex);
- dd.canvasX = dd.x * dd.model.canvasPixelRatio;
+ .each(function(e, i) {
+ e.xIndex = i;
+ e.x = d === e ? e.x : e.xScale(e.xIndex);
+ e.canvasX = e.x * e.model.canvasPixelRatio;
});
updatePanelLayout(yAxis, p);
- yAxis.filter(function(dd) { return Math.abs(d.xIndex - dd.xIndex) !== 0; })
+ yAxis.filter(function(e) { return Math.abs(d.xIndex - e.xIndex) !== 0; })
.attr('transform', function(d) { return 'translate(' + d.xScale(d.xIndex) + ', 0)'; });
d3.select(this).attr('transform', 'translate(' + d.x + ', 0)');
- yAxis.each(function(dd, i, ii) { if(ii === d.parent.key) p.dimensions[i] = dd; });
+ yAxis.each(function(e, i0, i1) { if(i1 === d.parent.key) p.dimensions[i0] = e; });
p.contextLayer && p.contextLayer.render(p.panels, false, !someFiltersActive(p));
p.focusLayer.render && p.focusLayer.render(p.panels);
})
@@ -514,7 +577,7 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
state.linePickActive(true);
if(callbacks && callbacks.axesMoved) {
- callbacks.axesMoved(p.key, p.dimensions.map(function(dd) {return dd.crossfilterDimensionIndex;}));
+ callbacks.axesMoved(p.key, p.dimensions.map(function(e) {return e.crossfilterDimensionIndex;}));
}
})
);
@@ -552,7 +615,9 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
.tickValues(d.ordinal ? // and this works for ordinal scales
sdom :
null)
- .tickFormat(d.ordinal ? function(d) { return d; } : null)
+ .tickFormat(function(v) {
+ return helpers.isOrdinal(d) ? v : linearFormat(d.model.dimensions[d.visibleIndex], v);
+ })
.scale(scale));
Drawing.font(axis.selectAll('text'), d.model.tickFont);
});
@@ -587,9 +652,32 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
.style('pointer-events', 'auto');
axisTitle
- .attr('transform', 'translate(0,' + -c.axisTitleOffset + ')')
.text(function(d) { return d.label; })
- .each(function(d) { Drawing.font(d3.select(this), d.model.labelFont); });
+ .each(function(d) {
+ var e = d3.select(this);
+ Drawing.font(e, d.model.labelFont);
+ svgTextUtils.convertToTspans(e, gd);
+ })
+ .attr('transform', function(d) {
+ var tilt = calcTilt(d.model.labelAngle, d.model.labelSide);
+ var r = c.axisTitleOffset;
+ return (
+ (tilt.dir > 0 ? '' : 'translate(0,' + (2 * r + d.model.height) + ')') +
+ 'rotate(' + tilt.degrees + ')' +
+ 'translate(' + (-r * tilt.dx) + ',' + (-r * tilt.dy) + ')'
+ );
+ })
+ .attr('text-anchor', function(d) {
+ var tilt = calcTilt(d.model.labelAngle, d.model.labelSide);
+ var adx = Math.abs(tilt.dx);
+ var ady = Math.abs(tilt.dy);
+
+ if(2 * adx > ady) {
+ return (tilt.dir * tilt.dx < 0) ? 'start' : 'end';
+ } else {
+ return 'middle';
+ }
+ });
var axisExtent = axisOverlays.selectAll('.' + c.cn.axisExtent)
.data(repeat, keyFun);
@@ -611,12 +699,6 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
var axisExtentTopText = axisExtentTop.selectAll('.' + c.cn.axisExtentTopText)
.data(repeat, keyFun);
- function extremeText(d, isTop) {
- if(d.ordinal) return '';
- var domain = d.domainScale.domain();
- return d3.format(d.tickFormat)(domain[isTop ? domain.length - 1 : 0]);
- }
-
axisExtentTopText.enter()
.append('text')
.classed(c.cn.axisExtentTopText, true)
@@ -648,7 +730,7 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
.call(styleExtentTexts);
axisExtentBottomText
- .text(function(d) { return extremeText(d); })
+ .text(function(d) { return extremeText(d, false); })
.each(function(d) { Drawing.font(d3.select(this), d.model.rangeFont); });
brush.ensureAxisBrush(axisOverlays);
diff --git a/src/traces/parcoords/plot.js b/src/traces/parcoords/plot.js
index 4a30b185fa3..2e779376389 100644
--- a/src/traces/parcoords/plot.js
+++ b/src/traces/parcoords/plot.js
@@ -10,58 +10,75 @@
var parcoords = require('./parcoords');
var prepareRegl = require('../../lib/prepare_regl');
+var isVisible = require('./helpers').isVisible;
+
+function newIndex(visibleIndices, orig, dim) {
+ var origIndex = orig.indexOf(dim);
+ var currentIndex = visibleIndices.indexOf(origIndex);
+ if(currentIndex === -1) {
+ // invisible dimensions initially go to the end
+ currentIndex += orig.length;
+ }
+ return currentIndex;
+}
+
+function sorter(visibleIndices, orig) {
+ return function sorter(d1, d2) {
+ return (
+ newIndex(visibleIndices, orig, d1) -
+ newIndex(visibleIndices, orig, d2)
+ );
+ };
+}
-module.exports = function plot(gd, cdparcoords) {
+module.exports = function plot(gd, cdModule) {
var fullLayout = gd._fullLayout;
- var svg = fullLayout._toppaper;
- var root = fullLayout._paperdiv;
- var container = fullLayout._glcontainer;
var success = prepareRegl(gd);
if(!success) return;
- var gdDimensions = {};
- var gdDimensionsOriginalOrder = {};
+ var currentDims = {};
+ var initialDims = {};
var fullIndices = {};
var inputIndices = {};
var size = fullLayout._size;
- cdparcoords.forEach(function(d, i) {
+ cdModule.forEach(function(d, i) {
var trace = d[0].trace;
fullIndices[i] = trace.index;
var iIn = inputIndices[i] = trace._fullInput.index;
- gdDimensions[i] = gd.data[iIn].dimensions;
- gdDimensionsOriginalOrder[i] = gd.data[iIn].dimensions.slice();
+ currentDims[i] = gd.data[iIn].dimensions;
+ initialDims[i] = gd.data[iIn].dimensions.slice();
});
- var filterChanged = function(i, originalDimensionIndex, newRanges) {
+ var filterChanged = function(i, initialDimIndex, newRanges) {
// Have updated `constraintrange` data on `gd.data` and raise `Plotly.restyle` event
// without having to incur heavy UI blocking due to an actual `Plotly.restyle` call
- var gdDimension = gdDimensionsOriginalOrder[i][originalDimensionIndex];
+ var dim = initialDims[i][initialDimIndex];
var newConstraints = newRanges.map(function(r) { return r.slice(); });
// Store constraint range in preGUI
// This one doesn't work if it's stored in pieces in _storeDirectGUIEdit
// because it's an array of variable dimensionality. So store the whole
// thing at once manually.
- var aStr = 'dimensions[' + originalDimensionIndex + '].constraintrange';
+ var aStr = 'dimensions[' + initialDimIndex + '].constraintrange';
var preGUI = fullLayout._tracePreGUI[gd._fullData[fullIndices[i]]._fullInput.uid];
if(preGUI[aStr] === undefined) {
- var initialVal = gdDimension.constraintrange;
+ var initialVal = dim.constraintrange;
preGUI[aStr] = initialVal || null;
}
- var fullDimension = gd._fullData[fullIndices[i]].dimensions[originalDimensionIndex];
+ var fullDimension = gd._fullData[fullIndices[i]].dimensions[initialDimIndex];
if(!newConstraints.length) {
- delete gdDimension.constraintrange;
+ delete dim.constraintrange;
delete fullDimension.constraintrange;
newConstraints = null;
} else {
if(newConstraints.length === 1) newConstraints = newConstraints[0];
- gdDimension.constraintrange = newConstraints;
+ dim.constraintrange = newConstraints;
fullDimension.constraintrange = newConstraints.slice();
// wrap in another array for restyle event data
newConstraints = [newConstraints];
@@ -84,40 +101,20 @@ module.exports = function plot(gd, cdparcoords) {
// Have updated order data on `gd.data` and raise `Plotly.restyle` event
// without having to incur heavy UI blocking due to an actual `Plotly.restyle` call
- function visible(dimension) {return !('visible' in dimension) || dimension.visible;}
-
- function newIdx(visibleIndices, orig, dim) {
- var origIndex = orig.indexOf(dim);
- var currentIndex = visibleIndices.indexOf(origIndex);
- if(currentIndex === -1) {
- // invisible dimensions initially go to the end
- currentIndex += orig.length;
- }
- return currentIndex;
- }
-
- function sorter(orig) {
- return function sorter(d1, d2) {
- var i1 = newIdx(visibleIndices, orig, d1);
- var i2 = newIdx(visibleIndices, orig, d2);
- return i1 - i2;
- };
- }
-
// drag&drop sorting of the visible dimensions
- var orig = sorter(gdDimensionsOriginalOrder[i].filter(visible));
- gdDimensions[i].sort(orig);
+ var orig = sorter(visibleIndices, initialDims[i].filter(isVisible));
+ currentDims[i].sort(orig);
// invisible dimensions are not interpreted in the context of drag&drop sorting as an invisible dimension
// cannot be dragged; they're interspersed into their original positions by this subsequent merging step
- gdDimensionsOriginalOrder[i].filter(function(d) {return !visible(d);})
+ initialDims[i].filter(function(d) {return !isVisible(d);})
.sort(function(d) {
// subsequent splicing to be done left to right, otherwise indices may be incorrect
- return gdDimensionsOriginalOrder[i].indexOf(d);
+ return initialDims[i].indexOf(d);
})
.forEach(function(d) {
- gdDimensions[i].splice(gdDimensions[i].indexOf(d), 1); // remove from the end
- gdDimensions[i].splice(gdDimensionsOriginalOrder[i].indexOf(d), 0, d); // insert at original index
+ currentDims[i].splice(currentDims[i].indexOf(d), 1); // remove from the end
+ currentDims[i].splice(initialDims[i].indexOf(d), 0, d); // insert at original index
});
// TODO: we can't really store this part of the interaction state
@@ -127,18 +124,16 @@ module.exports = function plot(gd, cdparcoords) {
// Registry.call('_storeDirectGUIEdit',
// gd.data[inputIndices[i]],
// fullLayout._tracePreGUI[gd._fullData[fullIndices[i]]._fullInput.uid],
- // {dimensions: gdDimensions[i]}
+ // {dimensions: currentDims[i]}
// );
- gd.emit('plotly_restyle', [{dimensions: [gdDimensions[i]]}, [inputIndices[i]]]);
+ gd.emit('plotly_restyle', [{dimensions: [currentDims[i]]}, [inputIndices[i]]]);
};
parcoords(
- root,
- svg,
- container,
- cdparcoords,
- {
+ gd,
+ cdModule,
+ { // layout
width: size.w,
height: size.h,
margin: {
@@ -148,10 +143,11 @@ module.exports = function plot(gd, cdparcoords) {
l: size.l
}
},
- {
+ { // callbacks
filterChanged: filterChanged,
hover: hover,
unhover: unhover,
axesMoved: axesMoved
- });
+ }
+ );
};
diff --git a/src/traces/parcoords/shaders/context_vertex.glsl b/src/traces/parcoords/shaders/context_vertex.glsl
deleted file mode 100644
index 75fe0d39aae..00000000000
--- a/src/traces/parcoords/shaders/context_vertex.glsl
+++ /dev/null
@@ -1,44 +0,0 @@
-precision highp float;
-
-attribute vec4 p0, p1, p2, p3,
- p4, p5, p6, p7,
- p8, p9, pa, pb,
- pc, pd, pe;
-
-attribute vec4 pf;
-
-uniform mat4 dim1A, dim2A, dim1B, dim2B, dim1C, dim2C, dim1D, dim2D;
-
-uniform vec2 resolution,
- viewBoxPosition,
- viewBoxSize;
-
-uniform sampler2D palette;
-
-uniform vec2 colorClamp;
-
-varying vec4 fragColor;
-
-#pragma glslify: unfilteredPosition = require("./unfiltered_position.glsl")
-
-void main() {
-
- float prominence = abs(pf[3]);
-
- mat4 p[4];
- p[0] = mat4(p0, p1, p2, p3);
- p[1] = mat4(p4, p5, p6, p7);
- p[2] = mat4(p8, p9, pa, pb);
- p[3] = mat4(pc, pd, pe, abs(pf));
-
- gl_Position = unfilteredPosition(
- 1.0 - prominence,
- resolution, viewBoxPosition, viewBoxSize,
- p,
- sign(pf[3]),
- dim1A, dim2A, dim1B, dim2B, dim1C, dim2C, dim1D, dim2D
- );
-
- float clampedColorIndex = clamp((prominence - colorClamp[0]) / (colorClamp[1] - colorClamp[0]), 0.0, 1.0);
- fragColor = texture2D(palette, vec2((clampedColorIndex * 255.0 + 0.5) / 256.0, 0.5));
-}
diff --git a/src/traces/parcoords/shaders/fragment.glsl b/src/traces/parcoords/shaders/fragment.glsl
index 52ab460d1ae..449912abfcd 100644
--- a/src/traces/parcoords/shaders/fragment.glsl
+++ b/src/traces/parcoords/shaders/fragment.glsl
@@ -1,4 +1,4 @@
-precision lowp float;
+precision highp float;
varying vec4 fragColor;
diff --git a/src/traces/parcoords/shaders/pick_vertex.glsl b/src/traces/parcoords/shaders/pick_vertex.glsl
deleted file mode 100644
index 9ba6798c75a..00000000000
--- a/src/traces/parcoords/shaders/pick_vertex.glsl
+++ /dev/null
@@ -1,47 +0,0 @@
-precision highp float;
-
-attribute vec4 p0, p1, p2, p3,
- p4, p5, p6, p7,
- p8, p9, pa, pb,
- pc, pd, pe;
-
-attribute vec4 pf;
-
-uniform mat4 dim1A, dim2A, dim1B, dim2B, dim1C, dim2C, dim1D, dim2D,
- loA, hiA, loB, hiB, loC, hiC, loD, hiD;
-
-uniform vec2 resolution,
- viewBoxPosition,
- viewBoxSize;
-
-uniform sampler2D mask;
-uniform float maskHeight;
-
-uniform vec2 colorClamp;
-
-varying vec4 fragColor;
-
-#pragma glslify: position = require("./position.glsl")
-
-void main() {
-
- float prominence = abs(pf[3]);
-
- mat4 p[4];
- p[0] = mat4(p0, p1, p2, p3);
- p[1] = mat4(p4, p5, p6, p7);
- p[2] = mat4(p8, p9, pa, pb);
- p[3] = mat4(pc, pd, pe, abs(pf));
-
- gl_Position = position(
- 1.0 - prominence,
- resolution, viewBoxPosition, viewBoxSize,
- p,
- sign(pf[3]),
- dim1A, dim2A, dim1B, dim2B, dim1C, dim2C, dim1D, dim2D,
- loA, hiA, loB, hiB, loC, hiC, loD, hiD,
- mask, maskHeight
- );
-
- fragColor = vec4(pf.rgb, 1.0);
-}
diff --git a/src/traces/parcoords/shaders/position.glsl b/src/traces/parcoords/shaders/position.glsl
deleted file mode 100644
index ba71f660b7f..00000000000
--- a/src/traces/parcoords/shaders/position.glsl
+++ /dev/null
@@ -1,88 +0,0 @@
-#pragma glslify: axisY = require("./y.glsl", mats=mats)
-
-#pragma glslify: export(position)
-
-const int bitsPerByte = 8;
-
-int mod2(int a) {
- return a - 2 * (a / 2);
-}
-
-int mod8(int a) {
- return a - 8 * (a / 8);
-}
-
-vec4 zero = vec4(0, 0, 0, 0);
-vec4 unit = vec4(1, 1, 1, 1);
-vec2 xyProjection = vec2(1, 1);
-
-mat4 mclamp(mat4 m, mat4 lo, mat4 hi) {
- return mat4(clamp(m[0], lo[0], hi[0]),
- clamp(m[1], lo[1], hi[1]),
- clamp(m[2], lo[2], hi[2]),
- clamp(m[3], lo[3], hi[3]));
-}
-
-bool mshow(mat4 p, mat4 lo, mat4 hi) {
- return mclamp(p, lo, hi) == p;
-}
-
-bool withinBoundingBox(
- mat4 d[4],
- mat4 loA, mat4 hiA, mat4 loB, mat4 hiB, mat4 loC, mat4 hiC, mat4 loD, mat4 hiD
- ) {
-
- return mshow(d[0], loA, hiA) &&
- mshow(d[1], loB, hiB) &&
- mshow(d[2], loC, hiC) &&
- mshow(d[3], loD, hiD);
-}
-
-bool withinRasterMask(mat4 d[4], sampler2D mask, float height) {
- bool result = true;
- int bitInByteStepper;
- float valY, valueY, scaleX;
- int hit, bitmask, valX;
- for(int i = 0; i < 4; i++) {
- for(int j = 0; j < 4; j++) {
- for(int k = 0; k < 4; k++) {
- bitInByteStepper = mod8(j * 4 + k);
- valX = i * 2 + j / 2;
- valY = d[i][j][k];
- valueY = valY * (height - 1.0) + 0.5;
- scaleX = (float(valX) + 0.5) / 8.0;
- hit = int(texture2D(mask, vec2(scaleX, (valueY + 0.5) / height))[3] * 255.0) / int(pow(2.0, float(bitInByteStepper)));
- result = result && mod2(hit) == 1;
- }
- }
- }
- return result;
-}
-
-vec4 position(
- float depth,
- vec2 resolution, vec2 viewBoxPosition, vec2 viewBoxSize,
- mat4 dims[4],
- float signum,
- mat4 dim1A, mat4 dim2A, mat4 dim1B, mat4 dim2B, mat4 dim1C, mat4 dim2C, mat4 dim1D, mat4 dim2D,
- mat4 loA, mat4 hiA, mat4 loB, mat4 hiB, mat4 loC, mat4 hiC, mat4 loD, mat4 hiD,
- sampler2D mask, float maskHeight
- ) {
-
- float x = 0.5 * signum + 0.5;
- float y = axisY(x, dims, dim1A, dim2A, dim1B, dim2B, dim1C, dim2C, dim1D, dim2D);
-
- float show = float(
- withinBoundingBox(dims, loA, hiA, loB, hiB, loC, hiC, loD, hiD)
- && withinRasterMask(dims, mask, maskHeight)
- );
-
- vec2 viewBoxXY = viewBoxPosition + viewBoxSize * vec2(x, y);
- float depthOrHide = depth + 2.0 * (1.0 - show);
-
- return vec4(
- xyProjection * (2.0 * viewBoxXY / resolution - 1.0),
- depthOrHide,
- 1.0
- );
-}
diff --git a/src/traces/parcoords/shaders/unfiltered_position.glsl b/src/traces/parcoords/shaders/unfiltered_position.glsl
deleted file mode 100644
index cd4868ce03e..00000000000
--- a/src/traces/parcoords/shaders/unfiltered_position.glsl
+++ /dev/null
@@ -1,24 +0,0 @@
-vec2 xyProjection = vec2(1, 1);
-
-#pragma glslify: axisY = require("./y.glsl", mats=mats)
-
-#pragma glslify: export(position)
-vec4 position(
- float depth,
- vec2 resolution, vec2 viewBoxPosition, vec2 viewBoxSize,
- mat4 dims[4],
- float signum,
- mat4 dim1A, mat4 dim2A, mat4 dim1B, mat4 dim2B, mat4 dim1C, mat4 dim2C, mat4 dim1D, mat4 dim2D
- ) {
-
- float x = 0.5 * signum + 0.5;
- float y = axisY(x, dims, dim1A, dim2A, dim1B, dim2B, dim1C, dim2C, dim1D, dim2D);
-
- vec2 viewBoxXY = viewBoxPosition + viewBoxSize * vec2(x, y);
-
- return vec4(
- xyProjection * (2.0 * viewBoxXY / resolution - 1.0),
- depth,
- 1.0
- );
-}
diff --git a/src/traces/parcoords/shaders/vertex.glsl b/src/traces/parcoords/shaders/vertex.glsl
index 08ea0209a28..373f966c87d 100644
--- a/src/traces/parcoords/shaders/vertex.glsl
+++ b/src/traces/parcoords/shaders/vertex.glsl
@@ -1,49 +1,123 @@
precision highp float;
-attribute vec4 p0, p1, p2, p3,
- p4, p5, p6, p7,
- p8, p9, pa, pb,
- pc, pd, pe;
+varying vec4 fragColor;
-attribute vec4 pf;
+attribute vec4 p01_04, p05_08, p09_12, p13_16,
+ p17_20, p21_24, p25_28, p29_32,
+ p33_36, p37_40, p41_44, p45_48,
+ p49_52, p53_56, p57_60, colors;
-uniform mat4 dim1A, dim2A, dim1B, dim2B, dim1C, dim2C, dim1D, dim2D,
+uniform mat4 dim0A, dim1A, dim0B, dim1B, dim0C, dim1C, dim0D, dim1D,
loA, hiA, loB, hiB, loC, hiC, loD, hiD;
-uniform vec2 resolution,
- viewBoxPosition,
- viewBoxSize;
-
-uniform sampler2D palette;
-uniform sampler2D mask;
+uniform vec2 resolution, viewBoxPos, viewBoxSize;
+uniform sampler2D mask, palette;
uniform float maskHeight;
+uniform float drwLayer; // 0: context, 1: focus, 2: pick
+uniform vec4 contextColor;
-uniform vec2 colorClamp;
+bool isPick = (drwLayer > 1.5);
+bool isContext = (drwLayer < 0.5);
-varying vec4 fragColor;
+const vec4 ZEROS = vec4(0.0, 0.0, 0.0, 0.0);
+const vec4 UNITS = vec4(1.0, 1.0, 1.0, 1.0);
+
+float val(mat4 p, mat4 v) {
+ return dot(matrixCompMult(p, v) * UNITS, UNITS);
+}
-#pragma glslify: position = require("./position.glsl")
+float axisY(float ratio, mat4 A, mat4 B, mat4 C, mat4 D) {
+ float y1 = val(A, dim0A) + val(B, dim0B) + val(C, dim0C) + val(D, dim0D);
+ float y2 = val(A, dim1A) + val(B, dim1B) + val(C, dim1C) + val(D, dim1D);
+ return y1 * (1.0 - ratio) + y2 * ratio;
+}
-void main() {
+int iMod(int a, int b) {
+ return a - b * (a / b);
+}
- float prominence = abs(pf[3]);
-
- mat4 p[4];
- p[0] = mat4(p0, p1, p2, p3);
- p[1] = mat4(p4, p5, p6, p7);
- p[2] = mat4(p8, p9, pa, pb);
- p[3] = mat4(pc, pd, pe, abs(pf));
-
- gl_Position = position(
- 1.0 - prominence,
- resolution, viewBoxPosition, viewBoxSize,
- p,
- sign(pf[3]),
- dim1A, dim2A, dim1B, dim2B, dim1C, dim2C, dim1D, dim2D,
- loA, hiA, loB, hiB, loC, hiC, loD, hiD,
- mask, maskHeight
+bool fOutside(float p, float lo, float hi) {
+ return (lo < hi) && (lo > p || p > hi);
+}
+
+bool vOutside(vec4 p, vec4 lo, vec4 hi) {
+ return (
+ fOutside(p[0], lo[0], hi[0]) ||
+ fOutside(p[1], lo[1], hi[1]) ||
+ fOutside(p[2], lo[2], hi[2]) ||
+ fOutside(p[3], lo[3], hi[3])
);
+}
+
+bool mOutside(mat4 p, mat4 lo, mat4 hi) {
+ return (
+ vOutside(p[0], lo[0], hi[0]) ||
+ vOutside(p[1], lo[1], hi[1]) ||
+ vOutside(p[2], lo[2], hi[2]) ||
+ vOutside(p[3], lo[3], hi[3])
+ );
+}
+
+bool outsideBoundingBox(mat4 A, mat4 B, mat4 C, mat4 D) {
+ return mOutside(A, loA, hiA) ||
+ mOutside(B, loB, hiB) ||
+ mOutside(C, loC, hiC) ||
+ mOutside(D, loD, hiD);
+}
+
+bool outsideRasterMask(mat4 A, mat4 B, mat4 C, mat4 D) {
+ mat4 pnts[4];
+ pnts[0] = A;
+ pnts[1] = B;
+ pnts[2] = C;
+ pnts[3] = D;
+
+ for(int i = 0; i < 4; ++i) {
+ for(int j = 0; j < 4; ++j) {
+ for(int k = 0; k < 4; ++k) {
+ if(0 == iMod(
+ int(255.0 * texture2D(mask,
+ vec2(
+ (float(i * 2 + j / 2) + 0.5) / 8.0,
+ (pnts[i][j][k] * (maskHeight - 1.0) + 1.0) / maskHeight
+ ))[3]
+ ) / int(pow(2.0, float(iMod(j * 4 + k, 8)))),
+ 2
+ )) return true;
+ }
+ }
+ }
+ return false;
+}
+
+vec4 position(bool isContext, float v, mat4 A, mat4 B, mat4 C, mat4 D) {
+ float x = 0.5 * sign(v) + 0.5;
+ float y = axisY(x, A, B, C, D);
+ float z = 1.0 - abs(v);
+
+ z += isContext ? 0.0 : 2.0 * float(
+ outsideBoundingBox(A, B, C, D) ||
+ outsideRasterMask(A, B, C, D)
+ );
+
+ return vec4(
+ 2.0 * (vec2(x, y) * viewBoxSize + viewBoxPos) / resolution - 1.0,
+ z,
+ 1.0
+ );
+}
+
+void main() {
+ mat4 A = mat4(p01_04, p05_08, p09_12, p13_16);
+ mat4 B = mat4(p17_20, p21_24, p25_28, p29_32);
+ mat4 C = mat4(p33_36, p37_40, p41_44, p45_48);
+ mat4 D = mat4(p49_52, p53_56, p57_60, ZEROS);
+
+ float v = colors[3];
+
+ gl_Position = position(isContext, v, A, B, C, D);
- float clampedColorIndex = clamp((prominence - colorClamp[0]) / (colorClamp[1] - colorClamp[0]), 0.0, 1.0);
- fragColor = texture2D(palette, vec2((clampedColorIndex * 255.0 + 0.5) / 256.0, 0.5));
+ fragColor =
+ isContext ? vec4(contextColor) :
+ isPick ? vec4(colors.rgb, 1.0) : texture2D(palette, vec2(abs(v), 0.5));
}
diff --git a/src/traces/parcoords/shaders/y.glsl b/src/traces/parcoords/shaders/y.glsl
deleted file mode 100644
index eade2f60cce..00000000000
--- a/src/traces/parcoords/shaders/y.glsl
+++ /dev/null
@@ -1,17 +0,0 @@
-vec4 unit = vec4(1, 1, 1, 1);
-
-float val(mat4 p, mat4 v) {
- return dot(matrixCompMult(p, v) * unit, unit);
-}
-
-#pragma glslify: export(axisY)
-float axisY(
- float x,
- mat4 d[4],
- mat4 dim1A, mat4 dim2A, mat4 dim1B, mat4 dim2B, mat4 dim1C, mat4 dim2C, mat4 dim1D, mat4 dim2D
- ) {
-
- float y1 = val(d[0], dim1A) + val(d[1], dim1B) + val(d[2], dim1C) + val(d[3], dim1D);
- float y2 = val(d[0], dim2A) + val(d[1], dim2B) + val(d[2], dim2C) + val(d[3], dim2D);
- return y1 * (1.0 - x) + y2 * x;
-}
diff --git a/test/image/baselines/gl2d_parcoords.png b/test/image/baselines/gl2d_parcoords.png
index 0d56d0d358c..d73145f6a5e 100644
Binary files a/test/image/baselines/gl2d_parcoords.png and b/test/image/baselines/gl2d_parcoords.png differ
diff --git a/test/image/baselines/gl2d_parcoords_2.png b/test/image/baselines/gl2d_parcoords_2.png
index 4b55d71b025..63dea7aaff3 100644
Binary files a/test/image/baselines/gl2d_parcoords_2.png and b/test/image/baselines/gl2d_parcoords_2.png differ
diff --git a/test/image/baselines/gl2d_parcoords_256_colors.png b/test/image/baselines/gl2d_parcoords_256_colors.png
new file mode 100644
index 00000000000..04beea0fcd1
Binary files /dev/null and b/test/image/baselines/gl2d_parcoords_256_colors.png differ
diff --git a/test/image/baselines/gl2d_parcoords_60_dims.png b/test/image/baselines/gl2d_parcoords_60_dims.png
new file mode 100644
index 00000000000..ea7eed2f8a7
Binary files /dev/null and b/test/image/baselines/gl2d_parcoords_60_dims.png differ
diff --git a/test/image/baselines/gl2d_parcoords_coloraxis.png b/test/image/baselines/gl2d_parcoords_coloraxis.png
index 1029b7b6ee2..0e199bc39b1 100644
Binary files a/test/image/baselines/gl2d_parcoords_coloraxis.png and b/test/image/baselines/gl2d_parcoords_coloraxis.png differ
diff --git a/test/image/baselines/gl2d_parcoords_constraints.png b/test/image/baselines/gl2d_parcoords_constraints.png
index 3225b29460a..e6ad27a3211 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_large.png b/test/image/baselines/gl2d_parcoords_large.png
index 883e627569b..10c07432e20 100644
Binary files a/test/image/baselines/gl2d_parcoords_large.png and b/test/image/baselines/gl2d_parcoords_large.png differ
diff --git a/test/image/baselines/gl2d_parcoords_out-of-range_selected-all.png b/test/image/baselines/gl2d_parcoords_out-of-range_selected-all.png
new file mode 100644
index 00000000000..1c3539e29de
Binary files /dev/null and b/test/image/baselines/gl2d_parcoords_out-of-range_selected-all.png differ
diff --git a/test/image/baselines/gl2d_parcoords_out-of-range_selected-none.png b/test/image/baselines/gl2d_parcoords_out-of-range_selected-none.png
new file mode 100644
index 00000000000..e4daa92f9a6
Binary files /dev/null and b/test/image/baselines/gl2d_parcoords_out-of-range_selected-none.png differ
diff --git a/test/image/baselines/gl2d_parcoords_rgba_colorscale.png b/test/image/baselines/gl2d_parcoords_rgba_colorscale.png
index 8f48a143593..5e9f663bad7 100644
Binary files a/test/image/baselines/gl2d_parcoords_rgba_colorscale.png and b/test/image/baselines/gl2d_parcoords_rgba_colorscale.png differ
diff --git a/test/image/baselines/gl2d_parcoords_style_labels.png b/test/image/baselines/gl2d_parcoords_style_labels.png
new file mode 100644
index 00000000000..deb74ff3e3d
Binary files /dev/null and b/test/image/baselines/gl2d_parcoords_style_labels.png differ
diff --git a/test/image/baselines/gl2d_parcoords_tick_format.png b/test/image/baselines/gl2d_parcoords_tick_format.png
new file mode 100644
index 00000000000..6cd50ac618c
Binary files /dev/null and b/test/image/baselines/gl2d_parcoords_tick_format.png differ
diff --git a/test/image/mocks/gl2d_parcoords_256_colors.json b/test/image/mocks/gl2d_parcoords_256_colors.json
new file mode 100644
index 00000000000..609b3134531
--- /dev/null
+++ b/test/image/mocks/gl2d_parcoords_256_colors.json
@@ -0,0 +1,1836 @@
+{
+ "data": [
+ {
+ "type": "parcoords",
+ "line": {
+ "colorscale": [
+ [
+ "0.000",
+ "rgb(0,0,255)"
+ ],
+ [
+ "0.004",
+ "rgb(1,254,0)"
+ ],
+ [
+ "0.008",
+ "rgb(2,253,0)"
+ ],
+ [
+ "0.012",
+ "rgb(3,252,0)"
+ ],
+ [
+ "0.016",
+ "rgb(4,251,0)"
+ ],
+ [
+ "0.020",
+ "rgb(5,250,0)"
+ ],
+ [
+ "0.024",
+ "rgb(6,249,0)"
+ ],
+ [
+ "0.027",
+ "rgb(7,248,0)"
+ ],
+ [
+ "0.031",
+ "rgb(8,247,0)"
+ ],
+ [
+ "0.035",
+ "rgb(9,246,0)"
+ ],
+ [
+ "0.039",
+ "rgb(10,245,0)"
+ ],
+ [
+ "0.043",
+ "rgb(11,244,0)"
+ ],
+ [
+ "0.047",
+ "rgb(12,243,0)"
+ ],
+ [
+ "0.051",
+ "rgb(13,242,0)"
+ ],
+ [
+ "0.055",
+ "rgb(14,241,0)"
+ ],
+ [
+ "0.059",
+ "rgb(15,240,0)"
+ ],
+ [
+ "0.063",
+ "rgb(16,239,0)"
+ ],
+ [
+ "0.067",
+ "rgb(17,238,0)"
+ ],
+ [
+ "0.071",
+ "rgb(18,237,0)"
+ ],
+ [
+ "0.075",
+ "rgb(19,236,0)"
+ ],
+ [
+ "0.078",
+ "rgb(20,235,0)"
+ ],
+ [
+ "0.082",
+ "rgb(21,234,0)"
+ ],
+ [
+ "0.086",
+ "rgb(22,233,0)"
+ ],
+ [
+ "0.090",
+ "rgb(23,232,0)"
+ ],
+ [
+ "0.094",
+ "rgb(24,231,0)"
+ ],
+ [
+ "0.098",
+ "rgb(25,230,0)"
+ ],
+ [
+ "0.102",
+ "rgb(26,229,0)"
+ ],
+ [
+ "0.106",
+ "rgb(27,228,0)"
+ ],
+ [
+ "0.110",
+ "rgb(28,227,0)"
+ ],
+ [
+ "0.114",
+ "rgb(29,226,0)"
+ ],
+ [
+ "0.118",
+ "rgb(30,225,0)"
+ ],
+ [
+ "0.122",
+ "rgb(31,224,0)"
+ ],
+ [
+ "0.125",
+ "rgb(32,223,0)"
+ ],
+ [
+ "0.129",
+ "rgb(33,222,0)"
+ ],
+ [
+ "0.133",
+ "rgb(34,221,0)"
+ ],
+ [
+ "0.137",
+ "rgb(35,220,0)"
+ ],
+ [
+ "0.141",
+ "rgb(36,219,0)"
+ ],
+ [
+ "0.145",
+ "rgb(37,218,0)"
+ ],
+ [
+ "0.149",
+ "rgb(38,217,0)"
+ ],
+ [
+ "0.153",
+ "rgb(39,216,0)"
+ ],
+ [
+ "0.157",
+ "rgb(40,215,0)"
+ ],
+ [
+ "0.161",
+ "rgb(41,214,0)"
+ ],
+ [
+ "0.165",
+ "rgb(42,213,0)"
+ ],
+ [
+ "0.169",
+ "rgb(43,212,0)"
+ ],
+ [
+ "0.173",
+ "rgb(44,211,0)"
+ ],
+ [
+ "0.176",
+ "rgb(45,210,0)"
+ ],
+ [
+ "0.180",
+ "rgb(46,209,0)"
+ ],
+ [
+ "0.184",
+ "rgb(47,208,0)"
+ ],
+ [
+ "0.188",
+ "rgb(48,207,0)"
+ ],
+ [
+ "0.192",
+ "rgb(49,206,0)"
+ ],
+ [
+ "0.196",
+ "rgb(50,205,0)"
+ ],
+ [
+ "0.200",
+ "rgb(51,204,0)"
+ ],
+ [
+ "0.204",
+ "rgb(52,203,0)"
+ ],
+ [
+ "0.208",
+ "rgb(53,202,0)"
+ ],
+ [
+ "0.212",
+ "rgb(54,201,0)"
+ ],
+ [
+ "0.216",
+ "rgb(55,200,0)"
+ ],
+ [
+ "0.220",
+ "rgb(56,199,0)"
+ ],
+ [
+ "0.224",
+ "rgb(57,198,0)"
+ ],
+ [
+ "0.227",
+ "rgb(58,197,0)"
+ ],
+ [
+ "0.231",
+ "rgb(59,196,0)"
+ ],
+ [
+ "0.235",
+ "rgb(60,195,0)"
+ ],
+ [
+ "0.239",
+ "rgb(61,194,0)"
+ ],
+ [
+ "0.243",
+ "rgb(62,193,0)"
+ ],
+ [
+ "0.247",
+ "rgb(63,192,0)"
+ ],
+ [
+ "0.251",
+ "rgb(64,191,0)"
+ ],
+ [
+ "0.255",
+ "rgb(65,190,0)"
+ ],
+ [
+ "0.259",
+ "rgb(66,189,0)"
+ ],
+ [
+ "0.263",
+ "rgb(67,188,0)"
+ ],
+ [
+ "0.267",
+ "rgb(68,187,0)"
+ ],
+ [
+ "0.271",
+ "rgb(69,186,0)"
+ ],
+ [
+ "0.275",
+ "rgb(70,185,0)"
+ ],
+ [
+ "0.278",
+ "rgb(71,184,0)"
+ ],
+ [
+ "0.282",
+ "rgb(72,183,0)"
+ ],
+ [
+ "0.286",
+ "rgb(73,182,0)"
+ ],
+ [
+ "0.290",
+ "rgb(74,181,0)"
+ ],
+ [
+ "0.294",
+ "rgb(75,180,0)"
+ ],
+ [
+ "0.298",
+ "rgb(76,179,0)"
+ ],
+ [
+ "0.302",
+ "rgb(77,178,0)"
+ ],
+ [
+ "0.306",
+ "rgb(78,177,0)"
+ ],
+ [
+ "0.310",
+ "rgb(79,176,0)"
+ ],
+ [
+ "0.314",
+ "rgb(80,175,0)"
+ ],
+ [
+ "0.318",
+ "rgb(81,174,0)"
+ ],
+ [
+ "0.322",
+ "rgb(82,173,0)"
+ ],
+ [
+ "0.325",
+ "rgb(83,172,0)"
+ ],
+ [
+ "0.329",
+ "rgb(84,171,0)"
+ ],
+ [
+ "0.333",
+ "rgb(85,170,0)"
+ ],
+ [
+ "0.337",
+ "rgb(86,169,0)"
+ ],
+ [
+ "0.341",
+ "rgb(87,168,0)"
+ ],
+ [
+ "0.345",
+ "rgb(88,167,0)"
+ ],
+ [
+ "0.349",
+ "rgb(89,166,0)"
+ ],
+ [
+ "0.353",
+ "rgb(90,165,0)"
+ ],
+ [
+ "0.357",
+ "rgb(91,164,0)"
+ ],
+ [
+ "0.361",
+ "rgb(92,163,0)"
+ ],
+ [
+ "0.365",
+ "rgb(93,162,0)"
+ ],
+ [
+ "0.369",
+ "rgb(94,161,0)"
+ ],
+ [
+ "0.373",
+ "rgb(95,160,0)"
+ ],
+ [
+ "0.376",
+ "rgb(96,159,0)"
+ ],
+ [
+ "0.380",
+ "rgb(97,158,0)"
+ ],
+ [
+ "0.384",
+ "rgb(98,157,0)"
+ ],
+ [
+ "0.388",
+ "rgb(99,156,0)"
+ ],
+ [
+ "0.392",
+ "rgb(100,155,0)"
+ ],
+ [
+ "0.396",
+ "rgb(101,154,0)"
+ ],
+ [
+ "0.400",
+ "rgb(102,153,0)"
+ ],
+ [
+ "0.404",
+ "rgb(103,152,0)"
+ ],
+ [
+ "0.408",
+ "rgb(104,151,0)"
+ ],
+ [
+ "0.412",
+ "rgb(105,150,0)"
+ ],
+ [
+ "0.416",
+ "rgb(106,149,0)"
+ ],
+ [
+ "0.420",
+ "rgb(107,148,0)"
+ ],
+ [
+ "0.424",
+ "rgb(108,147,0)"
+ ],
+ [
+ "0.427",
+ "rgb(109,146,0)"
+ ],
+ [
+ "0.431",
+ "rgb(110,145,0)"
+ ],
+ [
+ "0.435",
+ "rgb(111,144,0)"
+ ],
+ [
+ "0.439",
+ "rgb(112,143,0)"
+ ],
+ [
+ "0.443",
+ "rgb(113,142,0)"
+ ],
+ [
+ "0.447",
+ "rgb(114,141,0)"
+ ],
+ [
+ "0.451",
+ "rgb(115,140,0)"
+ ],
+ [
+ "0.455",
+ "rgb(116,139,0)"
+ ],
+ [
+ "0.459",
+ "rgb(117,138,0)"
+ ],
+ [
+ "0.463",
+ "rgb(118,137,0)"
+ ],
+ [
+ "0.467",
+ "rgb(119,136,0)"
+ ],
+ [
+ "0.471",
+ "rgb(120,135,0)"
+ ],
+ [
+ "0.475",
+ "rgb(121,134,0)"
+ ],
+ [
+ "0.478",
+ "rgb(122,133,0)"
+ ],
+ [
+ "0.482",
+ "rgb(123,132,0)"
+ ],
+ [
+ "0.486",
+ "rgb(124,131,0)"
+ ],
+ [
+ "0.490",
+ "rgb(125,130,0)"
+ ],
+ [
+ "0.494",
+ "rgb(126,129,0)"
+ ],
+ [
+ "0.498",
+ "rgb(127,128,0)"
+ ],
+ [
+ "0.502",
+ "rgb(128,127,0)"
+ ],
+ [
+ "0.506",
+ "rgb(129,126,0)"
+ ],
+ [
+ "0.510",
+ "rgb(130,125,0)"
+ ],
+ [
+ "0.514",
+ "rgb(131,124,0)"
+ ],
+ [
+ "0.518",
+ "rgb(132,123,0)"
+ ],
+ [
+ "0.522",
+ "rgb(133,122,0)"
+ ],
+ [
+ "0.525",
+ "rgb(134,121,0)"
+ ],
+ [
+ "0.529",
+ "rgb(135,120,0)"
+ ],
+ [
+ "0.533",
+ "rgb(136,119,0)"
+ ],
+ [
+ "0.537",
+ "rgb(137,118,0)"
+ ],
+ [
+ "0.541",
+ "rgb(138,117,0)"
+ ],
+ [
+ "0.545",
+ "rgb(139,116,0)"
+ ],
+ [
+ "0.549",
+ "rgb(140,115,0)"
+ ],
+ [
+ "0.553",
+ "rgb(141,114,0)"
+ ],
+ [
+ "0.557",
+ "rgb(142,113,0)"
+ ],
+ [
+ "0.561",
+ "rgb(143,112,0)"
+ ],
+ [
+ "0.565",
+ "rgb(144,111,0)"
+ ],
+ [
+ "0.569",
+ "rgb(145,110,0)"
+ ],
+ [
+ "0.573",
+ "rgb(146,109,0)"
+ ],
+ [
+ "0.576",
+ "rgb(147,108,0)"
+ ],
+ [
+ "0.580",
+ "rgb(148,107,0)"
+ ],
+ [
+ "0.584",
+ "rgb(149,106,0)"
+ ],
+ [
+ "0.588",
+ "rgb(150,105,0)"
+ ],
+ [
+ "0.592",
+ "rgb(151,104,0)"
+ ],
+ [
+ "0.596",
+ "rgb(152,103,0)"
+ ],
+ [
+ "0.600",
+ "rgb(153,102,0)"
+ ],
+ [
+ "0.604",
+ "rgb(154,101,0)"
+ ],
+ [
+ "0.608",
+ "rgb(155,100,0)"
+ ],
+ [
+ "0.612",
+ "rgb(156,99,0)"
+ ],
+ [
+ "0.616",
+ "rgb(157,98,0)"
+ ],
+ [
+ "0.620",
+ "rgb(158,97,0)"
+ ],
+ [
+ "0.624",
+ "rgb(159,96,0)"
+ ],
+ [
+ "0.627",
+ "rgb(160,95,0)"
+ ],
+ [
+ "0.631",
+ "rgb(161,94,0)"
+ ],
+ [
+ "0.635",
+ "rgb(162,93,0)"
+ ],
+ [
+ "0.639",
+ "rgb(163,92,0)"
+ ],
+ [
+ "0.643",
+ "rgb(164,91,0)"
+ ],
+ [
+ "0.647",
+ "rgb(165,90,0)"
+ ],
+ [
+ "0.651",
+ "rgb(166,89,0)"
+ ],
+ [
+ "0.655",
+ "rgb(167,88,0)"
+ ],
+ [
+ "0.659",
+ "rgb(168,87,0)"
+ ],
+ [
+ "0.663",
+ "rgb(169,86,0)"
+ ],
+ [
+ "0.667",
+ "rgb(170,85,0)"
+ ],
+ [
+ "0.671",
+ "rgb(171,84,0)"
+ ],
+ [
+ "0.675",
+ "rgb(172,83,0)"
+ ],
+ [
+ "0.678",
+ "rgb(173,82,0)"
+ ],
+ [
+ "0.682",
+ "rgb(174,81,0)"
+ ],
+ [
+ "0.686",
+ "rgb(175,80,0)"
+ ],
+ [
+ "0.690",
+ "rgb(176,79,0)"
+ ],
+ [
+ "0.694",
+ "rgb(177,78,0)"
+ ],
+ [
+ "0.698",
+ "rgb(178,77,0)"
+ ],
+ [
+ "0.702",
+ "rgb(179,76,0)"
+ ],
+ [
+ "0.706",
+ "rgb(180,75,0)"
+ ],
+ [
+ "0.710",
+ "rgb(181,74,0)"
+ ],
+ [
+ "0.714",
+ "rgb(182,73,0)"
+ ],
+ [
+ "0.718",
+ "rgb(183,72,0)"
+ ],
+ [
+ "0.722",
+ "rgb(184,71,0)"
+ ],
+ [
+ "0.725",
+ "rgb(185,70,0)"
+ ],
+ [
+ "0.729",
+ "rgb(186,69,0)"
+ ],
+ [
+ "0.733",
+ "rgb(187,68,0)"
+ ],
+ [
+ "0.737",
+ "rgb(188,67,0)"
+ ],
+ [
+ "0.741",
+ "rgb(189,66,0)"
+ ],
+ [
+ "0.745",
+ "rgb(190,65,0)"
+ ],
+ [
+ "0.749",
+ "rgb(191,64,0)"
+ ],
+ [
+ "0.753",
+ "rgb(192,63,0)"
+ ],
+ [
+ "0.757",
+ "rgb(193,62,0)"
+ ],
+ [
+ "0.761",
+ "rgb(194,61,0)"
+ ],
+ [
+ "0.765",
+ "rgb(195,60,0)"
+ ],
+ [
+ "0.769",
+ "rgb(196,59,0)"
+ ],
+ [
+ "0.773",
+ "rgb(197,58,0)"
+ ],
+ [
+ "0.776",
+ "rgb(198,57,0)"
+ ],
+ [
+ "0.780",
+ "rgb(199,56,0)"
+ ],
+ [
+ "0.784",
+ "rgb(200,55,0)"
+ ],
+ [
+ "0.788",
+ "rgb(201,54,0)"
+ ],
+ [
+ "0.792",
+ "rgb(202,53,0)"
+ ],
+ [
+ "0.796",
+ "rgb(203,52,0)"
+ ],
+ [
+ "0.800",
+ "rgb(204,51,0)"
+ ],
+ [
+ "0.804",
+ "rgb(205,50,0)"
+ ],
+ [
+ "0.808",
+ "rgb(206,49,0)"
+ ],
+ [
+ "0.812",
+ "rgb(207,48,0)"
+ ],
+ [
+ "0.816",
+ "rgb(208,47,0)"
+ ],
+ [
+ "0.820",
+ "rgb(209,46,0)"
+ ],
+ [
+ "0.824",
+ "rgb(210,45,0)"
+ ],
+ [
+ "0.827",
+ "rgb(211,44,0)"
+ ],
+ [
+ "0.831",
+ "rgb(212,43,0)"
+ ],
+ [
+ "0.835",
+ "rgb(213,42,0)"
+ ],
+ [
+ "0.839",
+ "rgb(214,41,0)"
+ ],
+ [
+ "0.843",
+ "rgb(215,40,0)"
+ ],
+ [
+ "0.847",
+ "rgb(216,39,0)"
+ ],
+ [
+ "0.851",
+ "rgb(217,38,0)"
+ ],
+ [
+ "0.855",
+ "rgb(218,37,0)"
+ ],
+ [
+ "0.859",
+ "rgb(219,36,0)"
+ ],
+ [
+ "0.863",
+ "rgb(220,35,0)"
+ ],
+ [
+ "0.867",
+ "rgb(221,34,0)"
+ ],
+ [
+ "0.871",
+ "rgb(222,33,0)"
+ ],
+ [
+ "0.875",
+ "rgb(223,32,0)"
+ ],
+ [
+ "0.878",
+ "rgb(224,31,0)"
+ ],
+ [
+ "0.882",
+ "rgb(225,30,0)"
+ ],
+ [
+ "0.886",
+ "rgb(226,29,0)"
+ ],
+ [
+ "0.890",
+ "rgb(227,28,0)"
+ ],
+ [
+ "0.894",
+ "rgb(228,27,0)"
+ ],
+ [
+ "0.898",
+ "rgb(229,26,0)"
+ ],
+ [
+ "0.902",
+ "rgb(230,25,0)"
+ ],
+ [
+ "0.906",
+ "rgb(231,24,0)"
+ ],
+ [
+ "0.910",
+ "rgb(232,23,0)"
+ ],
+ [
+ "0.914",
+ "rgb(233,22,0)"
+ ],
+ [
+ "0.918",
+ "rgb(234,21,0)"
+ ],
+ [
+ "0.922",
+ "rgb(235,20,0)"
+ ],
+ [
+ "0.925",
+ "rgb(236,19,0)"
+ ],
+ [
+ "0.929",
+ "rgb(237,18,0)"
+ ],
+ [
+ "0.933",
+ "rgb(238,17,0)"
+ ],
+ [
+ "0.937",
+ "rgb(239,16,0)"
+ ],
+ [
+ "0.941",
+ "rgb(240,15,0)"
+ ],
+ [
+ "0.945",
+ "rgb(241,14,0)"
+ ],
+ [
+ "0.949",
+ "rgb(242,13,0)"
+ ],
+ [
+ "0.953",
+ "rgb(243,12,0)"
+ ],
+ [
+ "0.957",
+ "rgb(244,11,0)"
+ ],
+ [
+ "0.961",
+ "rgb(245,10,0)"
+ ],
+ [
+ "0.965",
+ "rgb(246,9,0)"
+ ],
+ [
+ "0.969",
+ "rgb(247,8,0)"
+ ],
+ [
+ "0.973",
+ "rgb(248,7,0)"
+ ],
+ [
+ "0.976",
+ "rgb(249,6,0)"
+ ],
+ [
+ "0.980",
+ "rgb(250,5,0)"
+ ],
+ [
+ "0.984",
+ "rgb(251,4,0)"
+ ],
+ [
+ "0.988",
+ "rgb(252,3,0)"
+ ],
+ [
+ "0.992",
+ "rgb(253,2,0)"
+ ],
+ [
+ "0.996",
+ "rgb(254,1,0)"
+ ],
+ [
+ "1.000",
+ "rgb(255,255,0)"
+ ]
+ ],
+ "color": [
+ "0.000",
+ "0.004",
+ "0.008",
+ "0.012",
+ "0.016",
+ "0.020",
+ "0.024",
+ "0.027",
+ "0.031",
+ "0.035",
+ "0.039",
+ "0.043",
+ "0.047",
+ "0.051",
+ "0.055",
+ "0.059",
+ "0.063",
+ "0.067",
+ "0.071",
+ "0.075",
+ "0.078",
+ "0.082",
+ "0.086",
+ "0.090",
+ "0.094",
+ "0.098",
+ "0.102",
+ "0.106",
+ "0.110",
+ "0.114",
+ "0.118",
+ "0.122",
+ "0.125",
+ "0.129",
+ "0.133",
+ "0.137",
+ "0.141",
+ "0.145",
+ "0.149",
+ "0.153",
+ "0.157",
+ "0.161",
+ "0.165",
+ "0.169",
+ "0.173",
+ "0.176",
+ "0.180",
+ "0.184",
+ "0.188",
+ "0.192",
+ "0.196",
+ "0.200",
+ "0.204",
+ "0.208",
+ "0.212",
+ "0.216",
+ "0.220",
+ "0.224",
+ "0.227",
+ "0.231",
+ "0.235",
+ "0.239",
+ "0.243",
+ "0.247",
+ "0.251",
+ "0.255",
+ "0.259",
+ "0.263",
+ "0.267",
+ "0.271",
+ "0.275",
+ "0.278",
+ "0.282",
+ "0.286",
+ "0.290",
+ "0.294",
+ "0.298",
+ "0.302",
+ "0.306",
+ "0.310",
+ "0.314",
+ "0.318",
+ "0.322",
+ "0.325",
+ "0.329",
+ "0.333",
+ "0.337",
+ "0.341",
+ "0.345",
+ "0.349",
+ "0.353",
+ "0.357",
+ "0.361",
+ "0.365",
+ "0.369",
+ "0.373",
+ "0.376",
+ "0.380",
+ "0.384",
+ "0.388",
+ "0.392",
+ "0.396",
+ "0.400",
+ "0.404",
+ "0.408",
+ "0.412",
+ "0.416",
+ "0.420",
+ "0.424",
+ "0.427",
+ "0.431",
+ "0.435",
+ "0.439",
+ "0.443",
+ "0.447",
+ "0.451",
+ "0.455",
+ "0.459",
+ "0.463",
+ "0.467",
+ "0.471",
+ "0.475",
+ "0.478",
+ "0.482",
+ "0.486",
+ "0.490",
+ "0.494",
+ "0.498",
+ "0.502",
+ "0.506",
+ "0.510",
+ "0.514",
+ "0.518",
+ "0.522",
+ "0.525",
+ "0.529",
+ "0.533",
+ "0.537",
+ "0.541",
+ "0.545",
+ "0.549",
+ "0.553",
+ "0.557",
+ "0.561",
+ "0.565",
+ "0.569",
+ "0.573",
+ "0.576",
+ "0.580",
+ "0.584",
+ "0.588",
+ "0.592",
+ "0.596",
+ "0.600",
+ "0.604",
+ "0.608",
+ "0.612",
+ "0.616",
+ "0.620",
+ "0.624",
+ "0.627",
+ "0.631",
+ "0.635",
+ "0.639",
+ "0.643",
+ "0.647",
+ "0.651",
+ "0.655",
+ "0.659",
+ "0.663",
+ "0.667",
+ "0.671",
+ "0.675",
+ "0.678",
+ "0.682",
+ "0.686",
+ "0.690",
+ "0.694",
+ "0.698",
+ "0.702",
+ "0.706",
+ "0.710",
+ "0.714",
+ "0.718",
+ "0.722",
+ "0.725",
+ "0.729",
+ "0.733",
+ "0.737",
+ "0.741",
+ "0.745",
+ "0.749",
+ "0.753",
+ "0.757",
+ "0.761",
+ "0.765",
+ "0.769",
+ "0.773",
+ "0.776",
+ "0.780",
+ "0.784",
+ "0.788",
+ "0.792",
+ "0.796",
+ "0.800",
+ "0.804",
+ "0.808",
+ "0.812",
+ "0.816",
+ "0.820",
+ "0.824",
+ "0.827",
+ "0.831",
+ "0.835",
+ "0.839",
+ "0.843",
+ "0.847",
+ "0.851",
+ "0.855",
+ "0.859",
+ "0.863",
+ "0.867",
+ "0.871",
+ "0.875",
+ "0.878",
+ "0.882",
+ "0.886",
+ "0.890",
+ "0.894",
+ "0.898",
+ "0.902",
+ "0.906",
+ "0.910",
+ "0.914",
+ "0.918",
+ "0.922",
+ "0.925",
+ "0.929",
+ "0.933",
+ "0.937",
+ "0.941",
+ "0.945",
+ "0.949",
+ "0.953",
+ "0.957",
+ "0.961",
+ "0.965",
+ "0.969",
+ "0.973",
+ "0.976",
+ "0.980",
+ "0.984",
+ "0.988",
+ "0.992",
+ "0.996",
+ "1.000"
+ ],
+ "cmax": 1,
+ "cmin": 0
+ },
+ "dimensions": [
+ {
+ "label": "A",
+ "values": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ 100,
+ 101,
+ 102,
+ 103,
+ 104,
+ 105,
+ 106,
+ 107,
+ 108,
+ 109,
+ 110,
+ 111,
+ 112,
+ 113,
+ 114,
+ 115,
+ 116,
+ 117,
+ 118,
+ 119,
+ 120,
+ 121,
+ 122,
+ 123,
+ 124,
+ 125,
+ 126,
+ 127,
+ 128,
+ 129,
+ 130,
+ 131,
+ 132,
+ 133,
+ 134,
+ 135,
+ 136,
+ 137,
+ 138,
+ 139,
+ 140,
+ 141,
+ 142,
+ 143,
+ 144,
+ 145,
+ 146,
+ 147,
+ 148,
+ 149,
+ 150,
+ 151,
+ 152,
+ 153,
+ 154,
+ 155,
+ 156,
+ 157,
+ 158,
+ 159,
+ 160,
+ 161,
+ 162,
+ 163,
+ 164,
+ 165,
+ 166,
+ 167,
+ 168,
+ 169,
+ 170,
+ 171,
+ 172,
+ 173,
+ 174,
+ 175,
+ 176,
+ 177,
+ 178,
+ 179,
+ 180,
+ 181,
+ 182,
+ 183,
+ 184,
+ 185,
+ 186,
+ 187,
+ 188,
+ 189,
+ 190,
+ 191,
+ 192,
+ 193,
+ 194,
+ 195,
+ 196,
+ 197,
+ 198,
+ 199,
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209,
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 217,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 223,
+ 224,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 230,
+ 231,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 240,
+ 241,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 249,
+ 250,
+ 251,
+ 252,
+ 253,
+ 254,
+ 255
+ ]
+ },
+ {
+ "label": "B",
+ "values": [
+ "1.00000",
+ "0.99992",
+ "0.99970",
+ "0.99932",
+ "0.99880",
+ "0.99812",
+ "0.99729",
+ "0.99631",
+ "0.99518",
+ "0.99391",
+ "0.99248",
+ "0.99090",
+ "0.98918",
+ "0.98730",
+ "0.98528",
+ "0.98311",
+ "0.98079",
+ "0.97832",
+ "0.97570",
+ "0.97294",
+ "0.97003",
+ "0.96698",
+ "0.96378",
+ "0.96043",
+ "0.95694",
+ "0.95331",
+ "0.94953",
+ "0.94561",
+ "0.94154",
+ "0.93734",
+ "0.93299",
+ "0.92851",
+ "0.92388",
+ "0.91911",
+ "0.91421",
+ "0.90917",
+ "0.90399",
+ "0.89867",
+ "0.89322",
+ "0.88764",
+ "0.88192",
+ "0.87607",
+ "0.87009",
+ "0.86397",
+ "0.85773",
+ "0.85136",
+ "0.84485",
+ "0.83822",
+ "0.83147",
+ "0.82459",
+ "0.81758",
+ "0.81046",
+ "0.80321",
+ "0.79584",
+ "0.78835",
+ "0.78074",
+ "0.77301",
+ "0.76517",
+ "0.75721",
+ "0.74914",
+ "0.74095",
+ "0.73265",
+ "0.72425",
+ "0.71573",
+ "0.70711",
+ "0.69838",
+ "0.68954",
+ "0.68060",
+ "0.67156",
+ "0.66242",
+ "0.65317",
+ "0.64383",
+ "0.63439",
+ "0.62486",
+ "0.61523",
+ "0.60551",
+ "0.59570",
+ "0.58580",
+ "0.57581",
+ "0.56573",
+ "0.55557",
+ "0.54532",
+ "0.53500",
+ "0.52459",
+ "0.51410",
+ "0.50354",
+ "0.49290",
+ "0.48218",
+ "0.47140",
+ "0.46054",
+ "0.44961",
+ "0.43862",
+ "0.42756",
+ "0.41643",
+ "0.40524",
+ "0.39399",
+ "0.38268",
+ "0.37132",
+ "0.35990",
+ "0.34842",
+ "0.33689",
+ "0.32531",
+ "0.31368",
+ "0.30201",
+ "0.29028",
+ "0.27852",
+ "0.26671",
+ "0.25487",
+ "0.24298",
+ "0.23106",
+ "0.21910",
+ "0.20711",
+ "0.19509",
+ "0.18304",
+ "0.17096",
+ "0.15886",
+ "0.14673",
+ "0.13458",
+ "0.12241",
+ "0.11022",
+ "0.09802",
+ "0.08580",
+ "0.07356",
+ "0.06132",
+ "0.04907",
+ "0.03681",
+ "0.02454",
+ "0.01227",
+ "0.00000",
+ "-0.01227",
+ "-0.02454",
+ "-0.03681",
+ "-0.04907",
+ "-0.06132",
+ "-0.07356",
+ "-0.08580",
+ "-0.09802",
+ "-0.11022",
+ "-0.12241",
+ "-0.13458",
+ "-0.14673",
+ "-0.15886",
+ "-0.17096",
+ "-0.18304",
+ "-0.19509",
+ "-0.20711",
+ "-0.21910",
+ "-0.23106",
+ "-0.24298",
+ "-0.25487",
+ "-0.26671",
+ "-0.27852",
+ "-0.29028",
+ "-0.30201",
+ "-0.31368",
+ "-0.32531",
+ "-0.33689",
+ "-0.34842",
+ "-0.35990",
+ "-0.37132",
+ "-0.38268",
+ "-0.39399",
+ "-0.40524",
+ "-0.41643",
+ "-0.42756",
+ "-0.43862",
+ "-0.44961",
+ "-0.46054",
+ "-0.47140",
+ "-0.48218",
+ "-0.49290",
+ "-0.50354",
+ "-0.51410",
+ "-0.52459",
+ "-0.53500",
+ "-0.54532",
+ "-0.55557",
+ "-0.56573",
+ "-0.57581",
+ "-0.58580",
+ "-0.59570",
+ "-0.60551",
+ "-0.61523",
+ "-0.62486",
+ "-0.63439",
+ "-0.64383",
+ "-0.65317",
+ "-0.66242",
+ "-0.67156",
+ "-0.68060",
+ "-0.68954",
+ "-0.69838",
+ "-0.70711",
+ "-0.71573",
+ "-0.72425",
+ "-0.73265",
+ "-0.74095",
+ "-0.74914",
+ "-0.75721",
+ "-0.76517",
+ "-0.77301",
+ "-0.78074",
+ "-0.78835",
+ "-0.79584",
+ "-0.80321",
+ "-0.81046",
+ "-0.81758",
+ "-0.82459",
+ "-0.83147",
+ "-0.83822",
+ "-0.84485",
+ "-0.85136",
+ "-0.85773",
+ "-0.86397",
+ "-0.87009",
+ "-0.87607",
+ "-0.88192",
+ "-0.88764",
+ "-0.89322",
+ "-0.89867",
+ "-0.90399",
+ "-0.90917",
+ "-0.91421",
+ "-0.91911",
+ "-0.92388",
+ "-0.92851",
+ "-0.93299",
+ "-0.93734",
+ "-0.94154",
+ "-0.94561",
+ "-0.94953",
+ "-0.95331",
+ "-0.95694",
+ "-0.96043",
+ "-0.96378",
+ "-0.96698",
+ "-0.97003",
+ "-0.97294",
+ "-0.97570",
+ "-0.97832",
+ "-0.98079",
+ "-0.98311",
+ "-0.98528",
+ "-0.98730",
+ "-0.98918",
+ "-0.99090",
+ "-0.99248",
+ "-0.99391",
+ "-0.99518",
+ "-0.99631",
+ "-0.99729",
+ "-0.99812",
+ "-0.99880",
+ "-0.99932",
+ "-0.99970",
+ "-1.00000"
+ ],
+ "constraintrange": [
+ [
+ -1,
+ -0.95
+ ],
+ [
+ 0.95,
+ 1
+ ]
+ ]
+ }
+ ]
+ }
+ ],
+ "layout": {
+ "width": 1000,
+ "height": 1000,
+ "title": {
+ "text": "The first color should be blue.
The last color should be yellow."
+ }
+ }
+}
diff --git a/test/image/mocks/gl2d_parcoords_60_dims.json b/test/image/mocks/gl2d_parcoords_60_dims.json
new file mode 100644
index 00000000000..18a3eba7e91
--- /dev/null
+++ b/test/image/mocks/gl2d_parcoords_60_dims.json
@@ -0,0 +1,1612 @@
+{
+ "data": [
+ {
+ "type": "parcoords",
+ "labelangle": -45,
+ "labelside": "bottom",
+ "dimensions": [
+ {
+ "label": "one",
+ "range": [
+ 0,
+ 1
+ ],
+ "values": [
+ "1.00000",
+ "0.99452",
+ "0.95677",
+ "0.86024",
+ "0.69650",
+ "0.48714",
+ "0.28038",
+ "0.12517",
+ "0.04019",
+ "0.00837",
+ "0.00098",
+ "0.00005",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "two",
+ "range": [
+ 0,
+ 2
+ ],
+ "values": [
+ "2.00000",
+ "1.95630",
+ "1.80902",
+ "1.52483",
+ "1.12500",
+ "0.69314",
+ "0.33688",
+ "0.12012",
+ "0.02850",
+ "0.00391",
+ "0.00025",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "three",
+ "range": [
+ 0,
+ 3
+ ],
+ "values": [
+ "3.00000",
+ "2.85317",
+ "2.50370",
+ "1.94856",
+ "1.28514",
+ "0.67997",
+ "0.26927",
+ "0.07272",
+ "0.01172",
+ "0.00091",
+ "0.00002",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "four",
+ "range": [
+ 0,
+ 4
+ ],
+ "values": [
+ "4.00000",
+ "3.65418",
+ "3.00000",
+ "2.11803",
+ "1.21998",
+ "0.53656",
+ "0.16496",
+ "0.03125",
+ "0.00300",
+ "0.00010",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "five",
+ "range": [
+ 0,
+ 5
+ ],
+ "values": [
+ "5.00000",
+ "4.33013",
+ "3.27254",
+ "2.05206",
+ "1.00234",
+ "0.35080",
+ "0.07813",
+ "0.00921",
+ "0.00042",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00001",
+ "0.00008"
+ ]
+ },
+ {
+ "label": "six",
+ "range": [
+ 0,
+ 6
+ ],
+ "values": [
+ "6.00000",
+ "4.85410",
+ "3.31359",
+ "1.79756",
+ "0.71619",
+ "0.18750",
+ "0.02717",
+ "0.00161",
+ "0.00002",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00002",
+ "0.00018",
+ "0.00122"
+ ]
+ },
+ {
+ "label": "seven",
+ "range": [
+ 0,
+ 7
+ ],
+ "values": [
+ "7.00000",
+ "5.20201",
+ "3.13415",
+ "1.42152",
+ "0.43750",
+ "0.07792",
+ "0.00610",
+ "0.00012",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00001",
+ "0.00006",
+ "0.00043",
+ "0.00242",
+ "0.01131"
+ ]
+ },
+ {
+ "label": "eight",
+ "range": [
+ 0,
+ 8
+ ],
+ "values": [
+ "8.00000",
+ "5.35304",
+ "2.76393",
+ "1.00000",
+ "0.21895",
+ "0.02254",
+ "0.00065",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00002",
+ "0.00016",
+ "0.00098",
+ "0.00470",
+ "0.01931",
+ "0.06923"
+ ]
+ },
+ {
+ "label": "nine",
+ "range": [
+ 0,
+ 9
+ ],
+ "values": [
+ "9.00000",
+ "5.29007",
+ "2.25000",
+ "0.60560",
+ "0.08207",
+ "0.00350",
+ "0.00001",
+ "0.00000",
+ "0.00000",
+ "0.00001",
+ "0.00007",
+ "0.00045",
+ "0.00220",
+ "0.00900",
+ "0.03246",
+ "0.10480",
+ "0.30309"
+ ]
+ },
+ {
+ "label": "ten",
+ "range": [
+ 0,
+ 10
+ ],
+ "values": [
+ "10.00000",
+ "5.00000",
+ "1.65435",
+ "0.29508",
+ "0.01869",
+ "0.00012",
+ "0.00000",
+ "0.00000",
+ "0.00003",
+ "0.00026",
+ "0.00124",
+ "0.00488",
+ "0.01701",
+ "0.05391",
+ "0.15669",
+ "0.41626",
+ "1.00113"
+ ]
+ },
+ {
+ "label": "eleven",
+ "range": [
+ 0,
+ 11
+ ],
+ "values": [
+ "11.00000",
+ "4.47410",
+ "1.05041",
+ "0.09886",
+ "0.00131",
+ "0.00000",
+ "0.00001",
+ "0.00018",
+ "0.00091",
+ "0.00335",
+ "0.01074",
+ "0.03183",
+ "0.08862",
+ "0.23193",
+ "0.56598",
+ "1.27160",
+ "2.58866"
+ ]
+ },
+ {
+ "label": "twelve",
+ "range": [
+ 0,
+ 12
+ ],
+ "values": [
+ "12.00000",
+ "3.70820",
+ "0.51873",
+ "0.01371",
+ "0.00000",
+ "0.00015",
+ "0.00097",
+ "0.00323",
+ "0.00899",
+ "0.02344",
+ "0.05907",
+ "0.14448",
+ "0.34046",
+ "0.76319",
+ "1.60181",
+ "3.09125",
+ "5.37629"
+ ]
+ },
+ {
+ "label": "thirteen",
+ "range": [
+ 0,
+ 13
+ ],
+ "values": [
+ "13.00000",
+ "2.70285",
+ "0.14204",
+ "0.00000",
+ "0.00155",
+ "0.00505",
+ "0.01132",
+ "0.02394",
+ "0.05078",
+ "0.10887",
+ "0.23391",
+ "0.49631",
+ "1.02196",
+ "2.00374",
+ "3.66577",
+ "6.12404",
+ "9.12880"
+ ]
+ },
+ {
+ "label": "fourteen",
+ "range": [
+ 0,
+ 14
+ ],
+ "values": [
+ "14.00000",
+ "1.46340",
+ "0.00000",
+ "0.01599",
+ "0.02616",
+ "0.03945",
+ "0.06339",
+ "0.10937",
+ "0.19947",
+ "0.37647",
+ "0.71922",
+ "1.36039",
+ "2.49170",
+ "4.32136",
+ "6.93452",
+ "10.05065",
+ "12.82205"
+ ]
+ },
+ {
+ "label": "fifteen",
+ "range": [
+ 0,
+ 15
+ ],
+ "values": [
+ "15.00000",
+ "0.00000",
+ "0.16389",
+ "0.13481",
+ "0.13678",
+ "0.16698",
+ "0.23437",
+ "0.36360",
+ "0.60281",
+ "1.03694",
+ "1.80164",
+ "3.08268",
+ "5.06819",
+ "7.81220",
+ "11.00912",
+ "13.81359",
+ "15.00000"
+ ]
+ },
+ {
+ "label": "sixteen",
+ "range": [
+ 0,
+ 16
+ ],
+ "values": [
+ "16.00000",
+ "1.67246",
+ "0.69164",
+ "0.47214",
+ "0.43790",
+ "0.50000",
+ "0.65983",
+ "0.96094",
+ "1.48836",
+ "2.37541",
+ "3.79688",
+ "5.91768",
+ "8.76185",
+ "12.00541",
+ "14.81566",
+ "16.00000",
+ "14.65378"
+ ]
+ },
+ {
+ "label": "seventeen",
+ "range": [
+ 0,
+ 17
+ ],
+ "values": [
+ "17.00000",
+ "3.53450",
+ "1.62336",
+ "1.14390",
+ "1.06250",
+ "1.19273",
+ "1.52586",
+ "2.12796",
+ "3.11968",
+ "4.65827",
+ "6.88257",
+ "9.78855",
+ "13.04072",
+ "15.82834",
+ "17.00000",
+ "15.65540",
+ "11.93766"
+ ]
+ },
+ {
+ "label": "eighteen",
+ "range": [
+ 0,
+ 18
+ ],
+ "values": [
+ "18.00000",
+ "5.56231",
+ "2.97782",
+ "2.25000",
+ "2.14856",
+ "2.41450",
+ "3.03189",
+ "4.08297",
+ "5.69531",
+ "7.97708",
+ "10.89772",
+ "14.11630",
+ "16.85174",
+ "18.00000",
+ "16.66761",
+ "12.92226",
+ "8.06443"
+ ]
+ },
+ {
+ "label": "nineteen",
+ "range": [
+ 0,
+ 19
+ ],
+ "values": [
+ "19.00000",
+ "7.72800",
+ "4.75000",
+ "3.85842",
+ "3.80888",
+ "4.30646",
+ "5.32721",
+ "6.94173",
+ "9.21711",
+ "12.09513",
+ "15.23342",
+ "17.88593",
+ "19.00000",
+ "17.69050",
+ "13.94489",
+ "8.95052",
+ "4.47132"
+ ]
+ },
+ {
+ "label": "twenty",
+ "range": [
+ 0,
+ 20
+ ],
+ "values": [
+ "20.00000",
+ "10.00000",
+ "6.90983",
+ "5.99187",
+ "6.09992",
+ "6.93136",
+ "8.43750",
+ "10.62041",
+ "13.38692",
+ "16.39342",
+ "18.93100",
+ "20.00000",
+ "18.72415",
+ "15.00677",
+ "9.90646",
+ "5.15208",
+ "2.00226"
+ ]
+ },
+ {
+ "label": "twenty one",
+ "range": [
+ 0,
+ 21
+ ],
+ "values": [
+ "21.00000",
+ "12.34349",
+ "9.40245",
+ "8.61866",
+ "8.99601",
+ "10.22993",
+ "12.20676",
+ "14.77963",
+ "17.59764",
+ "19.98704",
+ "21.00000",
+ "19.76866",
+ "16.10913",
+ "10.93708",
+ "5.92163",
+ "2.42761",
+ "0.70720"
+ ]
+ },
+ {
+ "label": "twenty two",
+ "range": [
+ 0,
+ 22
+ ],
+ "values": [
+ "22.00000",
+ "14.72087",
+ "12.14981",
+ "11.64919",
+ "12.37500",
+ "13.99824",
+ "16.28023",
+ "18.84749",
+ "21.05414",
+ "22.00000",
+ "20.82410",
+ "17.25325",
+ "12.04755",
+ "6.79070",
+ "2.93665",
+ "0.91577",
+ "0.19037"
+ ]
+ },
+ {
+ "label": "twenty three",
+ "range": [
+ 0,
+ 23
+ ],
+ "values": [
+ "23.00000",
+ "17.09233",
+ "15.05370",
+ "14.93894",
+ "16.01948",
+ "17.89614",
+ "20.14439",
+ "22.13239",
+ "23.00000",
+ "21.89057",
+ "18.44046",
+ "13.24334",
+ "7.77123",
+ "3.54508",
+ "1.18341",
+ "0.26781",
+ "0.03715"
+ ]
+ },
+ {
+ "label": "twenty four",
+ "range": [
+ 0,
+ 24
+ ],
+ "values": [
+ "24.00000",
+ "19.41641",
+ "18.00000",
+ "18.29792",
+ "19.63525",
+ "21.48984",
+ "23.22188",
+ "24.00000",
+ "22.96815",
+ "19.67210",
+ "14.53030",
+ "8.87653",
+ "4.27148",
+ "1.52637",
+ "0.37604",
+ "0.05793",
+ "0.00487"
+ ]
+ },
+ {
+ "label": "twenty five",
+ "range": [
+ 0,
+ 25
+ ],
+ "values": [
+ "25.00000",
+ "21.65064",
+ "20.86413",
+ "21.50597",
+ "22.88535",
+ "24.32270",
+ "25.00000",
+ "24.05694",
+ "20.94957",
+ "15.91464",
+ "10.12142",
+ "5.13780",
+ "1.96531",
+ "0.52710",
+ "0.09018",
+ "0.00863",
+ "0.00038"
+ ]
+ },
+ {
+ "label": "twenty six",
+ "range": [
+ 0,
+ 26
+ ],
+ "values": [
+ "26.00000",
+ "23.75218",
+ "23.51722",
+ "24.33249",
+ "25.43494",
+ "26.00000",
+ "25.15703",
+ "22.27430",
+ "17.40299",
+ "11.52245",
+ "6.16992",
+ "2.52643",
+ "0.73766",
+ "0.14016",
+ "0.01528",
+ "0.00079",
+ "0.00001"
+ ]
+ },
+ {
+ "label": "twenty seven",
+ "range": [
+ 0,
+ 27
+ ],
+ "values": [
+ "27.00000",
+ "25.67853",
+ "25.83286",
+ "26.55870",
+ "27.00000",
+ "26.26851",
+ "23.64777",
+ "19.00238",
+ "13.09800",
+ "7.39843",
+ "3.24295",
+ "1.03079",
+ "0.21752",
+ "0.02699",
+ "0.00165",
+ "0.00004",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "twenty eight",
+ "range": [
+ 0,
+ 28
+ ],
+ "values": [
+ "28.00000",
+ "27.38813",
+ "27.69407",
+ "28.00000",
+ "27.39148",
+ "25.07148",
+ "20.72030",
+ "14.86857",
+ "8.85938",
+ "4.15697",
+ "1.43844",
+ "0.33711",
+ "0.04762",
+ "0.00342",
+ "0.00009",
+ "0.00000",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "twenty nine",
+ "range": [
+ 0,
+ 29
+ ],
+ "values": [
+ "29.00000",
+ "28.84113",
+ "29.00000",
+ "28.52601",
+ "26.54701",
+ "22.56470",
+ "16.85695",
+ "10.59528",
+ "5.32181",
+ "2.00474",
+ "0.52180",
+ "0.08391",
+ "0.00708",
+ "0.00024",
+ "0.00000",
+ "0.00000",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "thirty",
+ "range": [
+ 0,
+ 30
+ ],
+ "values": [
+ "30.00000",
+ "30.00000",
+ "29.67221",
+ "28.07595",
+ "24.54407",
+ "19.08851",
+ "12.65625",
+ "6.80495",
+ "2.79067",
+ "0.80671",
+ "0.14768",
+ "0.01465",
+ "0.00062",
+ "0.00001",
+ "0.00000",
+ "0.00000",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "thirty one",
+ "range": [
+ 0,
+ 31
+ ],
+ "values": [
+ "31.00000",
+ "30.83018",
+ "29.65995",
+ "26.66740",
+ "21.59148",
+ "15.10132",
+ "8.69176",
+ "3.88039",
+ "1.24580",
+ "0.25962",
+ "0.03027",
+ "0.00156",
+ "0.00002",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "thirty two",
+ "range": [
+ 0,
+ 32
+ ],
+ "values": [
+ "32.00000",
+ "31.30072",
+ "28.94427",
+ "24.39723",
+ "18.00000",
+ "11.09017",
+ "5.39002",
+ "1.92188",
+ "0.45593",
+ "0.06250",
+ "0.00397",
+ "0.00008",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "thirty three",
+ "range": [
+ 0,
+ 33
+ ],
+ "values": [
+ "33.00000",
+ "31.38487",
+ "27.54066",
+ "21.43413",
+ "14.13659",
+ "7.47965",
+ "2.96196",
+ "0.79992",
+ "0.12891",
+ "0.01005",
+ "0.00026",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "thirty four",
+ "range": [
+ 0,
+ 34
+ ],
+ "values": [
+ "34.00000",
+ "31.06055",
+ "25.50000",
+ "18.00329",
+ "10.36986",
+ "4.56072",
+ "1.40214",
+ "0.26563",
+ "0.02547",
+ "0.00087",
+ "0.00001",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00002"
+ ]
+ },
+ {
+ "label": "thirty five",
+ "range": [
+ 0,
+ 35
+ ],
+ "values": [
+ "35.00000",
+ "30.31089",
+ "22.90780",
+ "14.36443",
+ "7.01636",
+ "2.45562",
+ "0.54688",
+ "0.06446",
+ "0.00291",
+ "0.00003",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00005",
+ "0.00053"
+ ]
+ },
+ {
+ "label": "thirty six",
+ "range": [
+ 0,
+ 36
+ ],
+ "values": [
+ "36.00000",
+ "29.12461",
+ "19.88151",
+ "10.78537",
+ "4.29712",
+ "1.12500",
+ "0.16300",
+ "0.00969",
+ "0.00013",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00001",
+ "0.00012",
+ "0.00110",
+ "0.00731"
+ ]
+ },
+ {
+ "label": "thirty seven",
+ "range": [
+ 0,
+ 37
+ ],
+ "values": [
+ "37.00000",
+ "27.49636",
+ "16.56622",
+ "7.51377",
+ "2.31250",
+ "0.41188",
+ "0.03222",
+ "0.00062",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00000",
+ "0.00003",
+ "0.00031",
+ "0.00226",
+ "0.01278",
+ "0.05976"
+ ]
+ },
+ {
+ "label": "thirty eight",
+ "range": [
+ 0,
+ 38
+ ],
+ "values": [
+ "38.00000",
+ "25.42696",
+ "13.12868",
+ "4.75000",
+ "1.04001",
+ "0.10708",
+ "0.00307",
+ "0.00001",
+ "0.00000",
+ "0.00000",
+ "0.00001",
+ "0.00009",
+ "0.00078",
+ "0.00464",
+ "0.02233",
+ "0.09172",
+ "0.32882"
+ ]
+ },
+ {
+ "label": "thirty nine",
+ "range": [
+ 0,
+ 39
+ ],
+ "values": [
+ "39.00000",
+ "22.92362",
+ "9.75000",
+ "2.62425",
+ "0.35563",
+ "0.01515",
+ "0.00005",
+ "0.00000",
+ "0.00000",
+ "0.00003",
+ "0.00031",
+ "0.00197",
+ "0.00952",
+ "0.03899",
+ "0.14068",
+ "0.45412",
+ "1.31337"
+ ]
+ },
+ {
+ "label": "forty",
+ "range": [
+ 0,
+ 40
+ ],
+ "values": [
+ "40.00000",
+ "20.00000",
+ "6.61739",
+ "1.18034",
+ "0.07474",
+ "0.00050",
+ "0.00000",
+ "0.00001",
+ "0.00014",
+ "0.00103",
+ "0.00496",
+ "0.01953",
+ "0.06803",
+ "0.21563",
+ "0.62674",
+ "1.66504",
+ "4.00452"
+ ]
+ },
+ {
+ "label": "forty one",
+ "range": [
+ 0,
+ 41
+ ],
+ "values": [
+ "41.00000",
+ "16.67620",
+ "3.91515",
+ "0.36849",
+ "0.00489",
+ "0.00000",
+ "0.00005",
+ "0.00069",
+ "0.00341",
+ "0.01249",
+ "0.04004",
+ "0.11863",
+ "0.33030",
+ "0.86445",
+ "2.10956",
+ "4.73962",
+ "9.64865"
+ ]
+ },
+ {
+ "label": "forty two",
+ "range": [
+ 0,
+ 42
+ ],
+ "values": [
+ "42.00000",
+ "12.97871",
+ "1.81555",
+ "0.04797",
+ "0.00000",
+ "0.00052",
+ "0.00339",
+ "0.01130",
+ "0.03146",
+ "0.08203",
+ "0.20675",
+ "0.50567",
+ "1.19160",
+ "2.67115",
+ "5.60632",
+ "10.81936",
+ "18.81700"
+ ]
+ },
+ {
+ "label": "forty three",
+ "range": [
+ 0,
+ 43
+ ],
+ "values": [
+ "43.00000",
+ "8.94020",
+ "0.46983",
+ "0.00000",
+ "0.00513",
+ "0.01671",
+ "0.03744",
+ "0.07919",
+ "0.16797",
+ "0.36011",
+ "0.77371",
+ "1.64163",
+ "3.38034",
+ "6.62776",
+ "12.12525",
+ "20.25645",
+ "30.19526"
+ ]
+ },
+ {
+ "label": "forty four",
+ "range": [
+ 0,
+ 44
+ ],
+ "values": [
+ "44.00000",
+ "4.59925",
+ "0.00000",
+ "0.05025",
+ "0.08222",
+ "0.12398",
+ "0.19922",
+ "0.34375",
+ "0.62691",
+ "1.18318",
+ "2.26041",
+ "4.27550",
+ "7.83105",
+ "13.58141",
+ "21.79422",
+ "31.58774",
+ "40.29789"
+ ]
+ },
+ {
+ "label": "forty five",
+ "range": [
+ 0,
+ 45
+ ],
+ "values": [
+ "45.00000",
+ "0.00000",
+ "0.49168",
+ "0.40444",
+ "0.41034",
+ "0.50093",
+ "0.70313",
+ "1.09080",
+ "1.80842",
+ "3.11081",
+ "5.40492",
+ "9.24803",
+ "15.20458",
+ "23.43661",
+ "33.02737",
+ "41.44076",
+ "45.00000"
+ ]
+ },
+ {
+ "label": "forty six",
+ "range": [
+ 0,
+ 46
+ ],
+ "values": [
+ "46.00000",
+ "4.80831",
+ "1.98845",
+ "1.35739",
+ "1.25896",
+ "1.43750",
+ "1.89701",
+ "2.76270",
+ "4.27903",
+ "6.82931",
+ "10.91602",
+ "17.01334",
+ "25.19032",
+ "34.51556",
+ "42.59501",
+ "46.00000",
+ "42.12961"
+ ]
+ },
+ {
+ "label": "forty seven",
+ "range": [
+ 0,
+ 47
+ ],
+ "values": [
+ "47.00000",
+ "9.77185",
+ "4.48810",
+ "3.16255",
+ "2.93750",
+ "3.29755",
+ "4.21855",
+ "5.88318",
+ "8.62500",
+ "12.87874",
+ "19.02828",
+ "27.06247",
+ "36.05376",
+ "43.76071",
+ "47.00000",
+ "43.28258",
+ "33.00412"
+ ]
+ },
+ {
+ "label": "forty eight",
+ "range": [
+ 0,
+ 48
+ ],
+ "values": [
+ "48.00000",
+ "14.83282",
+ "7.94087",
+ "6.00000",
+ "5.72949",
+ "6.43866",
+ "8.08504",
+ "10.88792",
+ "15.18750",
+ "21.27221",
+ "29.06059",
+ "37.64346",
+ "44.93797",
+ "48.00000",
+ "44.44697",
+ "34.45936",
+ "21.50515"
+ ]
+ },
+ {
+ "label": "forty nine",
+ "range": [
+ 0,
+ 49
+ ],
+ "values": [
+ "49.00000",
+ "19.93010",
+ "12.25000",
+ "9.95067",
+ "9.82290",
+ "11.10615",
+ "13.73858",
+ "17.90237",
+ "23.77045",
+ "31.19270",
+ "39.28620",
+ "46.12686",
+ "49.00000",
+ "45.62287",
+ "35.96314",
+ "23.08293",
+ "11.53131"
+ ]
+ },
+ {
+ "label": "fifty ",
+ "range": [
+ 0,
+ 50
+ ],
+ "values": [
+ "50.00000",
+ "25.00000",
+ "17.27458",
+ "14.97969",
+ "15.24979",
+ "17.32839",
+ "21.09375",
+ "26.55102",
+ "33.46729",
+ "40.98355",
+ "47.32749",
+ "50.00000",
+ "46.81038",
+ "37.51692",
+ "24.76615",
+ "12.88020",
+ "5.00565"
+ ]
+ },
+ {
+ "label": "fifty one",
+ "range": [
+ 0,
+ 51
+ ],
+ "values": [
+ "51.00000",
+ "29.97705",
+ "22.83452",
+ "20.93103",
+ "21.84745",
+ "24.84410",
+ "29.64498",
+ "35.89339",
+ "42.73713",
+ "48.53995",
+ "51.00000",
+ "48.00959",
+ "39.12217",
+ "26.56149",
+ "14.38111",
+ "5.89562",
+ "1.71748"
+ ]
+ },
+ {
+ "label": "fifty two",
+ "range": [
+ 0,
+ 52
+ ],
+ "values": [
+ "52.00000",
+ "34.79479",
+ "28.71774",
+ "27.53444",
+ "29.25000",
+ "33.08675",
+ "38.48055",
+ "44.54860",
+ "49.76433",
+ "52.00000",
+ "49.22059",
+ "40.78042",
+ "28.47602",
+ "16.05075",
+ "6.94116",
+ "2.16455",
+ "0.44996"
+ ]
+ },
+ {
+ "label": "fifty three",
+ "range": [
+ 0,
+ 53
+ ],
+ "values": [
+ "53.00000",
+ "39.38668",
+ "34.68895",
+ "34.42451",
+ "36.91446",
+ "41.23894",
+ "46.41969",
+ "51.00072",
+ "53.00000",
+ "50.44348",
+ "42.49324",
+ "30.51725",
+ "17.90762",
+ "8.16910",
+ "2.72699",
+ "0.61713",
+ "0.08560"
+ ]
+ },
+ {
+ "label": "fifty four",
+ "range": [
+ 0,
+ 54
+ ],
+ "values": [
+ "54.00000",
+ "43.68692",
+ "40.50000",
+ "41.17032",
+ "44.17932",
+ "48.35214",
+ "52.24923",
+ "54.00000",
+ "51.67834",
+ "44.26223",
+ "32.69317",
+ "19.97218",
+ "9.61084",
+ "3.43434",
+ "0.84610",
+ "0.13033",
+ "0.01096"
+ ]
+ },
+ {
+ "label": "fifty five",
+ "range": [
+ 0,
+ 55
+ ],
+ "values": [
+ "55.00000",
+ "47.63140",
+ "45.90109",
+ "47.31313",
+ "50.34777",
+ "53.50994",
+ "55.00000",
+ "52.92528",
+ "46.08906",
+ "35.01222",
+ "22.26713",
+ "11.30315",
+ "4.32369",
+ "1.15963",
+ "0.19839",
+ "0.01900",
+ "0.00084"
+ ]
+ },
+ {
+ "label": "fifty six",
+ "range": [
+ 0,
+ 56
+ ],
+ "values": [
+ "56.00000",
+ "51.15855",
+ "50.65248",
+ "52.40844",
+ "54.78295",
+ "56.00000",
+ "54.18438",
+ "47.97542",
+ "37.48337",
+ "24.81758",
+ "13.28906",
+ "5.44154",
+ "1.58880",
+ "0.30188",
+ "0.03290",
+ "0.00171",
+ "0.00003"
+ ]
+ },
+ {
+ "label": "fifty seven",
+ "range": [
+ 0,
+ 57
+ ],
+ "values": [
+ "57.00000",
+ "54.21022",
+ "54.53605",
+ "56.06837",
+ "57.00000",
+ "55.45575",
+ "49.92306",
+ "40.11614",
+ "27.65134",
+ "15.61890",
+ "6.84623",
+ "2.17612",
+ "0.45920",
+ "0.05698",
+ "0.00348",
+ "0.00008",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "fifty eight",
+ "range": [
+ 0,
+ 58
+ ],
+ "values": [
+ "58.00000",
+ "56.73256",
+ "57.36628",
+ "58.00000",
+ "56.73948",
+ "51.93378",
+ "42.92062",
+ "30.79918",
+ "18.35156",
+ "8.61087",
+ "2.97963",
+ "0.69831",
+ "0.09864",
+ "0.00708",
+ "0.00020",
+ "0.00000",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "fifty nine",
+ "range": [
+ 0,
+ 59
+ ],
+ "values": [
+ "59.00000",
+ "58.67679",
+ "59.00000",
+ "58.03568",
+ "54.00943",
+ "45.90750",
+ "34.29517",
+ "21.55591",
+ "10.82713",
+ "4.07862",
+ "1.06160",
+ "0.17071",
+ "0.01440",
+ "0.00049",
+ "0.00000",
+ "0.00000",
+ "0.00000"
+ ]
+ },
+ {
+ "label": "sixty",
+ "range": [
+ 0,
+ 60
+ ],
+ "values": [
+ "60.00000",
+ "60.00000",
+ "59.34443",
+ "56.15190",
+ "49.08814",
+ "38.17702",
+ "25.31250",
+ "13.60989",
+ "5.58135",
+ "1.61343",
+ "0.29535",
+ "0.02930",
+ "0.00123",
+ "0.00001",
+ "0.00000",
+ "0.00000",
+ "0.00000"
+ ]
+ }
+ ],
+ "line": {
+ "cmin": -8,
+ "cmax": 8,
+ "color": [
+ -8,
+ -7,
+ -6,
+ -5,
+ -4,
+ -3,
+ -2,
+ -1,
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "colorscale": [
+ [
+ 0,
+ "rgb(255,0,0)"
+ ],
+ [
+ 1,
+ "rgb(0,0,255)"
+ ]
+ ],
+ "showscale": false
+ }
+ }
+ ],
+ "layout": {
+ "width": 1600,
+ "height": 500,
+ "title": {
+ "text": "60 dimensions"
+ }
+ }
+}
diff --git a/test/image/mocks/gl2d_parcoords_out-of-range_selected-all.json b/test/image/mocks/gl2d_parcoords_out-of-range_selected-all.json
new file mode 100644
index 00000000000..b98c6d88d82
--- /dev/null
+++ b/test/image/mocks/gl2d_parcoords_out-of-range_selected-all.json
@@ -0,0 +1,69 @@
+{
+ "data": [
+ {
+ "type": "parcoords",
+ "line": {
+ "colorscale": "Portland",
+ "color": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8
+ ]
+ },
+ "dimensions": [
+ {
+ "label": "A",
+ "constraintrange": [
+ [
+ 2.5,
+ 10
+ ]
+ ],
+ "values": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "range": [
+ 2.5,
+ 10
+ ]
+ },
+ {
+ "label": "B",
+ "values": [
+ 1,
+ 2,
+ 3,
+ 5,
+ 8,
+ 13,
+ 22,
+ 35
+ ],
+ "range": [
+ 0,
+ 20
+ ]
+ }
+ ]
+ }
+ ],
+ "layout": {
+ "width": 400,
+ "height": 400,
+ "title": {
+ "text": "Should not select out of range values"
+ }
+ }
+}
diff --git a/test/image/mocks/gl2d_parcoords_out-of-range_selected-none.json b/test/image/mocks/gl2d_parcoords_out-of-range_selected-none.json
new file mode 100644
index 00000000000..f22a36d6542
--- /dev/null
+++ b/test/image/mocks/gl2d_parcoords_out-of-range_selected-none.json
@@ -0,0 +1,63 @@
+{
+ "data": [
+ {
+ "type": "parcoords",
+ "line": {
+ "colorscale": "Portland",
+ "color": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8
+ ]
+ },
+ "dimensions": [
+ {
+ "label": "A",
+ "values": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "range": [
+ 2.5,
+ 10
+ ]
+ },
+ {
+ "label": "B",
+ "values": [
+ 1,
+ 2,
+ 3,
+ 5,
+ 8,
+ 13,
+ 22,
+ 35
+ ],
+ "range": [
+ 0,
+ 20
+ ]
+ }
+ ]
+ }
+ ],
+ "layout": {
+ "width": 400,
+ "height": 400,
+ "title": {
+ "text": "Should display out of range values"
+ }
+ }
+}
diff --git a/test/image/mocks/gl2d_parcoords_style_labels.json b/test/image/mocks/gl2d_parcoords_style_labels.json
new file mode 100644
index 00000000000..3512dfe7fa1
--- /dev/null
+++ b/test/image/mocks/gl2d_parcoords_style_labels.json
@@ -0,0 +1,95 @@
+{
+ "data": [
+ {
+ "type": "parcoords",
+ "labelfont": {
+ "color": "rgba(255,0,0,0.5)",
+ "family": "Arial, sans-serif",
+ "size": 16
+ },
+ "rangefont": {
+ "color": "hsv(191,191,63)",
+ "family": "Times New Roman",
+ "size": 8
+ },
+ "tickfont": {
+ "color": "green",
+ "family": "'Open sans', verdana, arial, sans-serif",
+ "size": 12
+ },
+ "dimensions": [
+ {
+ "label": "Rien ne se perd,
rien ne se crée,
tout se transforme.",
+ "values": [
+ 1,
+ 2,
+ 3,
+ 5,
+ 8,
+ 13
+ ],
+ "range": [
+ 0,
+ 20
+ ]
+ },
+ {
+ "label": "eiπ = cos i + i sin π",
+ "values": [
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1
+ ],
+ "range": [
+ 1,
+ -1
+ ]
+ },
+ {
+ "label": "Golden ratio
1.618",
+ "values": [
+ 1,
+ 2,
+ 1.5,
+ 1.667,
+ 1.6,
+ 1.625
+ ],
+ "tickvals": [
+ 1,
+ 2,
+ 1.5,
+ 1.667,
+ 1.6,
+ 1.625
+ ],
+ "ticktext": [
+ "1/1 = 1",
+ "2/1 = 2",
+ "3/2 = 1.5",
+ "5/3 = 1.667",
+ "8/5 = 1.6",
+ "13/8 = 1.625"
+ ],
+ "constraintrange": [
+ 1.55,
+ 1.7
+ ]
+ }
+ ]
+ }
+ ],
+ "layout": {
+ "margin": {
+ "t": 50,
+ "b": 25,
+ "l": 100,
+ "r": 100
+ },
+ "width": 500,
+ "height": 400
+ }
+}
diff --git a/test/image/mocks/gl2d_parcoords_tick_format.json b/test/image/mocks/gl2d_parcoords_tick_format.json
new file mode 100644
index 00000000000..7145e205af5
--- /dev/null
+++ b/test/image/mocks/gl2d_parcoords_tick_format.json
@@ -0,0 +1,41 @@
+{
+ "data": [
+ {
+ "type": "parcoords",
+ "labelangle": -90,
+ "labelside": "bottom",
+ "dimensions": [
+ {
+ "label": "Apples",
+ "values": [
+ -1,
+ 1
+ ]
+ },
+ {
+ "tickformat": ".3f",
+ "label": "Bananas",
+ "values": [
+ -0.99999,
+ 0.99999
+ ]
+ },
+ {
+ "tickformat": ".6s",
+ "label": "Cherries",
+ "values": [
+ -0.99999,
+ 1
+ ]
+ }
+ ]
+ }
+ ],
+ "layout": {
+ "width": 400,
+ "height": 400,
+ "title": {
+ "text": "Tick formatting and labelangle"
+ }
+ }
+}
diff --git a/test/jasmine/tests/parcoords_test.js b/test/jasmine/tests/parcoords_test.js
index 8b7876b3098..526f8c5c3df 100644
--- a/test/jasmine/tests/parcoords_test.js
+++ b/test/jasmine/tests/parcoords_test.js
@@ -4,6 +4,7 @@ var d3 = require('d3');
var Plots = require('@src/plots/plots');
var Parcoords = require('@src/traces/parcoords');
var attributes = require('@src/traces/parcoords/attributes');
+var PC = require('@src/traces/parcoords/constants');
var createGraphDiv = require('../assets/create_graph_div');
var delay = require('../assets/delay');
@@ -14,6 +15,8 @@ var click = require('../assets/click');
var supplyAllDefaults = require('../assets/supply_defaults');
var readPixel = require('../assets/read_pixel');
+var mock3 = require('@mocks/gl2d_parcoords_style_labels.json');
+
// mock with two dimensions (one panel); special case, e.g. left and right panel is obv. the same
var mock2 = require('@mocks/gl2d_parcoords_2.json');
@@ -162,7 +165,7 @@ describe('parcoords initialization tests', function() {
expect(fullTrace.dimensions).toEqual([jasmine.objectContaining({
values: [1],
visible: true,
- tickformat: '3s',
+ tickformat: '',
multiselect: true,
_index: 0,
_length: 1
@@ -319,7 +322,7 @@ describe('parcoords initialization tests', function() {
color: '#444'
});
});
-
+/* TODO: write a new test for typed arrays
it('\'dimensions.values\' and \'line.color\' should convert typed arrays to normal arrays', function() {
var fullTrace = _calc(Lib.extendDeep({}, base, {
dimensions: [{
@@ -343,6 +346,7 @@ describe('parcoords initialization tests', function() {
expect(Array.isArray(fullTrace.dimensions[1].values) === true).toEqual(true);
expect(Array.isArray(fullTrace.dimensions[2].values) === true).toEqual(true);
});
+ */
});
});
@@ -1156,6 +1160,128 @@ describe('parcoords basic use', function() {
});
});
+describe('parcoords react more attributes', function() {
+ var gd;
+
+ beforeEach(function(done) {
+ var hasGD = !!gd;
+ if(!hasGD) gd = createGraphDiv();
+
+ Plotly.react(gd, mock3)
+ .catch(failTest)
+ .then(done);
+ });
+
+ afterAll(purgeGraphDiv);
+
+ it('@gl should change various axis parameters', function(done) {
+ var mockCopy = Lib.extendDeep({}, mock3);
+ var m0 = mockCopy.data[0];
+ m0.labelangle = '-90';
+ m0.labelfont = { size: '24', family: 'Times', color: 'orange' };
+ m0.rangefont = { size: '20', family: 'Times', color: 'brown' };
+ m0.tickfont = { size: '16', family: 'Times', color: 'yellow' };
+ m0.dimensions[0].label = 'Changed!';
+ m0.dimensions[1].range = ['-2', '2'];
+ m0.dimensions[2].constraintrange = [];
+ m0.dimensions[1].multiselect = false;
+ m0.dimensions[1].constraintrange = [
+ [-1.5, -0.5],
+ [0, 1.5] // won't be selected because multiselect is tuned off.
+ ];
+ m0.dimensions[0].constraintrange = [[2, 4], [7, 10], [11, 12]];
+ m0.dimensions[0].tickvals = ['1', '2', '3', '5', '8', '13'];
+ m0.dimensions[0].ticktext = ['1/1', '2/1', '3/2', '5/3', '8/5', '13/8'];
+ m0.domain = { x: [0, 0.5], y: [0, 0.5] };
+
+ Plotly.react(gd, mockCopy.data).then(function() {
+ var allParcoords = d3.selectAll('.' + PC.cn.parcoords);
+
+ var allLabels = allParcoords.selectAll('.' + PC.cn.axisTitle);
+ expect(allLabels.size()).toBe(3);
+ allLabels.each(function(d) {
+ expect(d.model.labelAngle).toEqual(-90);
+ expect(d.model.labelFont.size).toEqual(24);
+ expect(d.model.labelFont.family).toEqual('Times');
+ expect(d.model.labelFont.color).toEqual('orange');
+ });
+ expect(allLabels[0][2].getAttribute('data-unformatted'), 'Changed!');
+
+ var allTopRanges = allParcoords.selectAll('.' + PC.cn.axisExtentTopText);
+ expect(allTopRanges.size()).toBe(3);
+ allTopRanges.each(function(d) {
+ expect(d.model.rangeFont.size).toEqual(20);
+ expect(d.model.rangeFont.family).toEqual('Times');
+ expect(d.model.rangeFont.color).toEqual('brown');
+ });
+
+ var allBottomRanges = allParcoords.selectAll('.' + PC.cn.axisExtentBottomText);
+ expect(allBottomRanges.size()).toBe(3);
+ allBottomRanges.each(function(d) {
+ expect(d.model.rangeFont.size).toEqual(20);
+ expect(d.model.rangeFont.family).toEqual('Times');
+ expect(d.model.rangeFont.color).toEqual('brown');
+ });
+
+ var allTicks = allParcoords.selectAll('.' + PC.cn.axis);
+ expect(allTicks.size()).toBe(3);
+ var allTickVals = [];
+ var allTickText = [];
+ allTicks.each(function(d) {
+ expect(d.model.tickFont.size).toEqual(16);
+ expect(d.model.tickFont.family).toEqual('Times');
+ expect(d.model.tickFont.color).toEqual('yellow');
+
+ allTickVals.push(d.tickvals);
+ allTickText.push(d.ticktext);
+ });
+ expect(allTickVals[2]).toBeCloseToArray([1, 2, 3, 5, 8, 13]);
+ expect(allTickText[2]).toBeCloseToArray(['1/1', '2/1', '3/2', '5/3', '8/5', '13/8']);
+
+ var allHighlights = allParcoords.selectAll('.' + PC.cn.axisBrush).selectAll('.highlight');
+ expect(allHighlights.size()).toBe(3);
+ var nHighlight = [];
+ allHighlights.each(function() {
+ var highlight = d3.select(this)[0][0];
+ nHighlight.push(
+ highlight.getAttribute('stroke-dasharray').split(',').length
+ );
+ });
+ expect(nHighlight[2]).toBe(6);
+ expect(nHighlight[1]).toBe(2);
+ expect(nHighlight[0]).toBe(4);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('@gl should change axis visibility', function(done) {
+ var mockCopy = Lib.extendDeep({}, mock3);
+ var m0 = mockCopy.data[0];
+
+ m0.dimensions[1].visible = false;
+
+ Plotly.react(gd, mockCopy.data).then(function() {
+ var allParcoords = d3.selectAll('.' + PC.cn.parcoords);
+
+ var allLabels = allParcoords.selectAll('.' + PC.cn.axisTitle);
+ expect(allLabels.size()).toBe(2);
+
+ m0.dimensions[1].visible = true;
+ })
+ .then(function() {
+ Plotly.react(gd, mockCopy.data).then(function() {
+ var allParcoords = d3.selectAll('.' + PC.cn.parcoords);
+
+ var allLabels = allParcoords.selectAll('.' + PC.cn.axisTitle);
+ expect(allLabels.size()).toBe(3);
+ });
+ })
+ .catch(failTest)
+ .then(done);
+ });
+});
+
describe('parcoords constraint interactions', function() {
var gd, initialDashArray0, initialDashArray1;
@@ -1181,19 +1307,18 @@ describe('parcoords constraint interactions', function() {
};
}
- var parcoordsConstants = require('@src/traces/parcoords/constants');
var initialSnapDuration;
var shortenedSnapDuration = 20;
var snapDelay = 100;
var noSnapDelay = 20;
beforeAll(function() {
- initialSnapDuration = parcoordsConstants.bar.snapDuration;
- parcoordsConstants.bar.snapDuration = shortenedSnapDuration;
+ initialSnapDuration = PC.bar.snapDuration;
+ PC.bar.snapDuration = shortenedSnapDuration;
});
afterAll(function() {
purgeGraphDiv();
- parcoordsConstants.bar.snapDuration = initialSnapDuration;
+ PC.bar.snapDuration = initialSnapDuration;
});
beforeEach(function(done) {
@@ -1359,7 +1484,7 @@ describe('parcoords constraint interactions', function() {
.then(done);
});
- it('@gl will only select one region when multiselect is disabled', function(done) {
+ it('@noCI @gl will only select one region when multiselect is disabled', function(done) {
var newDashArray;
Plotly.restyle(gd, {'dimensions[1].multiselect': false})
diff --git a/test/jasmine/tests/plot_api_react_test.js b/test/jasmine/tests/plot_api_react_test.js
index f6c5e750858..cfb73939d73 100644
--- a/test/jasmine/tests/plot_api_react_test.js
+++ b/test/jasmine/tests/plot_api_react_test.js
@@ -15,6 +15,7 @@ var supplyAllDefaults = require('../assets/supply_defaults');
var mockLists = require('../assets/mock_lists');
var mouseEvent = require('../assets/mouse_event');
var drag = require('../assets/drag');
+var delay = require('../assets/delay');
var MAPBOX_ACCESS_TOKEN = require('@build/credentials.json').MAPBOX_ACCESS_TOKEN;
@@ -944,7 +945,10 @@ describe('Plotly.react and uirevision attributes', function() {
gd = createGraphDiv();
});
- afterEach(destroyGraphDiv);
+ afterEach(function() {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ });
function checkCloseIfArray(val1, val2, msg) {
if(Array.isArray(val1) && Array.isArray(val2)) {
@@ -1805,16 +1809,18 @@ describe('Plotly.react and uirevision attributes', function() {
.then(function() {
return drag({node: axisDragNode(0), dpos: [0, 50], noCover: true});
})
+ .then(delay(100))
.then(function() {
return drag({node: axisDragNode(0), dpos: [0, -50], noCover: true});
})
+ .then(delay(100))
.then(function() {
return drag({node: axisDragNode(1), dpos: [0, -50], noCover: true});
});
}
_run(fig, editTrace, checkState([attrs(true)]), checkState([attrs()])).then(done);
- });
+ }, 5 * jasmine.DEFAULT_TIMEOUT_INTERVAL);
it('preserves editable: true axis titles using the axis uirevisions', function(done) {
function fig(mainRev, axRev) {