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": "e = 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) {