Skip to content

Commit 1c29f81

Browse files
committed
multiple parcoords rows, replot, restyle, and their test cases; hidden dimensions; fix incremental redraw regression; refactoring steps (squashed)
1 parent e34efd5 commit 1c29f81

File tree

7 files changed

+397
-227
lines changed

7 files changed

+397
-227
lines changed

src/traces/parcoords/attributes.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,20 @@ module.exports = {
7878
role: 'info',
7979
description: 'The shown name of the dimension.'
8080
},
81+
hidden: {
82+
valType: 'boolean',
83+
dflt: false,
84+
role: 'info',
85+
description: 'Hides the dimension when set to true.'
86+
},
87+
constraintrange: {
88+
valType: 'data_array',
89+
role: 'info',
90+
description: [
91+
'The initial domain extent to which the filter on the dimension is constrained. Must be an array',
92+
'of `[fromValue, toValue]` with finite numbers as elements.'
93+
].join(' ')
94+
},
8195
values: {
8296
valType: 'data_array',
8397
role: 'info',
@@ -98,6 +112,14 @@ module.exports = {
98112
description: 'The desired approximate tick distance (in pixels) between axis ticks on an axis.'
99113
},
100114

115+
padding: {
116+
valType: 'number',
117+
dflt: 80,
118+
min: 0,
119+
role: 'style',
120+
description: 'The desired space for displaying axis labels and domain ranges around the actual parcoords.'
121+
},
122+
101123
line: extendFlat({},
102124
colorAttributes('line'),
103125
{

src/traces/parcoords/calc.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ module.exports = function calc(gd, trace) {
3434
cd.push({
3535
v: v,
3636
i: i,
37+
constraintrange: vals[i].constraintrange,
3738
integer: vals[i].integer,
39+
hidden: vals[i].hidden,
3840
label: vals[i].label,
3941
values: vals[i].values
4042
});
@@ -73,6 +75,7 @@ module.exports = function calc(gd, trace) {
7375
tickdistance: trace.tickdistance,
7476
lines: trace.lines,
7577
line: trace.line,
78+
padding: trace.padding,
7679
unitToColor: function(d) {
7780
return polylinearUnitScales.map(function(s) {
7881
return s(cScale(d));

src/traces/parcoords/defaults.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ function dimensionsDefaults(traceIn, traceOut) {
5050
coerce('id');
5151
coerce('label');
5252
coerce('integer');
53+
coerce('hidden');
54+
coerce('constraintrange');
5355
coerce('values');
5456

5557
dimensionOut._index = i;
@@ -75,6 +77,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
7577
handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
7678

7779
coerce('tickdistance');
80+
coerce('padding');
7881

7982
coerce('domain.x');
8083
coerce('domain.y');

src/traces/parcoords/lines.js

Lines changed: 99 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
'use strict';
1010

11-
var d3 = require('d3');
1211
var createREGL = require('regl');
1312
var glslify = require('glslify');
1413
var vertexShaderSource = glslify('./shaders/vertex.glsl');
@@ -17,6 +16,10 @@ var fragmentShaderSource = glslify('./shaders/fragment.glsl');
1716
var depthLimitEpsilon = 1e-6; // don't change; otherwise near/far plane lines are lost
1817
var filterEpsilon = 1e-3; // don't change; otherwise filter may lose lines on domain boundaries
1918

19+
var gpuDimensionCount = 64;
20+
var sectionVertexCount = 2;
21+
var vec4NumberCount = 4;
22+
2023
var dummyPixel = new Uint8Array(4);
2124
function ensureDraw(regl) {
2225
regl.read({
@@ -51,8 +54,8 @@ function renderBlock(regl, glAes, renderState, blockLineCount, sampleCount, item
5154

5255
count = Math.min(blockLineCount, sampleCount - blockNumber * blockLineCount);
5356

54-
item.offset = 2 * blockNumber * blockLineCount;
55-
item.count = 2 * count;
57+
item.offset = sectionVertexCount * blockNumber * blockLineCount;
58+
item.count = sectionVertexCount * count;
5659
if(blockNumber === 0) {
5760
window.cancelAnimationFrame(renderState.currentRafs[rafKey]); // stop drawing possibly stale glyphs before clearing
5861
clear(regl, item.scissorX, 0, item.scissorWidth, item.viewBoxSize[1]);
@@ -77,77 +80,93 @@ function renderBlock(regl, glAes, renderState, blockLineCount, sampleCount, item
7780
render(blockNumber);
7881
}
7982

80-
module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, data, unitToColor, context) {
81-
82-
var renderState = {
83-
currentRafs: {},
84-
drawCompleted: true,
85-
clearOnly: false
86-
};
87-
88-
var dimensions = data;
89-
var dimensionCount = dimensions.length;
90-
var sampleCount = dimensions[0].values.length;
91-
92-
var focusAlphaBlending = context; // controlConfig.focusAlphaBlending;
93-
94-
var canvasPixelRatio = lines.pixelratio;
95-
var canvasPanelSizeY = canvasHeight;
83+
function adjustDepth(d) {
84+
// WebGL matrix operations use floats with limited precision, potentially causing a number near a border of [0, 1]
85+
// to end up slightly outside the border. With an epsilon, we reduce the chance that a line gets clipped by the
86+
// near or the far plane.
87+
return Math.max(depthLimitEpsilon, Math.min(1 - depthLimitEpsilon, d));
88+
}
9689

97-
var gpuDimensionCount = 64;
98-
var strideableVectorAttributeCount = gpuDimensionCount - 4; // stride can't be an exact 256
90+
function palette(unitToColor, context, lines_contextcolor, lines_contextopacity) {
91+
var result = [];
92+
for(var j = 0; j < 256; j++) {
93+
var c = unitToColor(j / 255);
94+
result.push((context ? lines_contextcolor : c).concat([context ? lines_contextopacity : 255]));
95+
}
9996

100-
var paddedUnit = function paddedUnit(d) {
101-
var unitPad = lines.verticalpadding / canvasPanelSizeY;
102-
return unitPad + d * (1 - 2 * unitPad);
103-
};
97+
return result;
98+
}
10499

105-
var color = lines.color.map(paddedUnit);
106-
var overdrag = lines.overdrag * canvasPixelRatio;
100+
function makePoints(sampleCount, dimensionCount, dimensions, color) {
107101

108102
var points = [];
109-
var i, j;
110-
for(j = 0; j < sampleCount; j++) {
111-
for(i = 0; i < strideableVectorAttributeCount; i++) {
112-
points.push(i < dimensionCount ? paddedUnit(dimensions[i].domainToUnitScale(data[i].values[j])) : 0.5);
103+
for(var j = 0; j < sampleCount; j++) {
104+
for(var i = 0; i < gpuDimensionCount; i++) {
105+
points.push(i < dimensionCount ?
106+
dimensions[i].paddedUnitValues[j] :
107+
i === (gpuDimensionCount - 1) ?
108+
adjustDepth(color[j]) :
109+
0.5);
113110
}
114111
}
115112

113+
return points;
114+
}
115+
116+
function makeVecAttr(sampleCount, points, vecIndex) {
117+
118+
var i, j, k;
116119
var pointPairs = [];
117120

118121
for(j = 0; j < sampleCount; j++) {
119-
for(i = 0; i < strideableVectorAttributeCount; i++) {
120-
pointPairs.push(points[j * strideableVectorAttributeCount + i]);
121-
}
122-
for(i = 0; i < strideableVectorAttributeCount; i++) {
123-
pointPairs.push(points[j * strideableVectorAttributeCount + i]);
122+
for(k = 0; k < sectionVertexCount; k++) {
123+
for(i = 0; i < vec4NumberCount; i++) {
124+
pointPairs.push(points[j * gpuDimensionCount + vecIndex * vec4NumberCount + i]);
125+
if(vecIndex * vec4NumberCount + i === gpuDimensionCount - 1 && k % 2 === 0) {
126+
pointPairs[pointPairs.length - 1] *= -1;
127+
}
128+
}
124129
}
125130
}
126131

127-
function adjustDepth(d) {
128-
return Math.max(depthLimitEpsilon, Math.min(1 - depthLimitEpsilon, d));
129-
}
132+
return pointPairs;
133+
}
130134

131-
var ccolor = [];
132-
for(j = 0; j < 256; j++) {
133-
var c = unitToColor(j / 255);
134-
ccolor.push((focusAlphaBlending ? lines.contextcolor : c).concat([focusAlphaBlending ? lines.contextopacity : 255]));
135-
}
135+
function makeAttributes(sampleCount, points) {
136136

137-
var styling = [];
138-
for(j = 0; j < sampleCount; j++) {
139-
for(var k = 0; k < 2; k++) {
140-
styling.push(points[(j + 1) * strideableVectorAttributeCount]);
141-
styling.push(points[(j + 1) * strideableVectorAttributeCount + 1]);
142-
styling.push(points[(j + 1) * strideableVectorAttributeCount + 2]);
143-
styling.push(Math.round(2 * ((k % 2) - 0.5)) * adjustDepth(color[j]));
144-
}
145-
}
137+
var vecIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
138+
var vectors = vecIndices.map(function(vecIndex) {return makeVecAttr(sampleCount, points, vecIndex);});
139+
140+
var attributes = {};
141+
vectors.forEach(function(v, vecIndex) {
142+
attributes['p' + vecIndex.toString(16)] = v;
143+
});
144+
145+
return attributes;
146+
}
147+
148+
module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, dimensions, unitToColor, context) {
149+
150+
var renderState = {
151+
currentRafs: {},
152+
drawCompleted: true,
153+
clearOnly: false
154+
};
146155

147-
var positionStride = strideableVectorAttributeCount * 4;
156+
var dimensionCount = dimensions.length;
157+
var sampleCount = dimensions[0].values.length;
148158

149-
var shownDimensionCount = dimensionCount;
150-
var shownPanelCount = shownDimensionCount - 1;
159+
var focusAlphaBlending = context; // controlConfig.focusAlphaBlending;
160+
161+
var canvasPanelSizeY = canvasHeight;
162+
163+
var color = lines.color;
164+
var overdrag = lines.canvasOverdrag;
165+
166+
var panelCount = dimensionCount - 1;
167+
168+
var points = makePoints(sampleCount, dimensionCount, dimensions, color);
169+
var attributes = makeAttributes(sampleCount, points);
151170

152171
var regl = createREGL({
153172
canvas: canvasGL,
@@ -162,23 +181,9 @@ module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, data, unit
162181
type: 'uint8',
163182
mag: 'nearest',
164183
min: 'nearest',
165-
data: ccolor
184+
data: palette(unitToColor, context, lines.contextcolor, lines.contextopacity)
166185
});
167186

168-
var positionBuffer = regl.buffer(new Float32Array(pointPairs));
169-
170-
var attributes = {
171-
pf: styling
172-
};
173-
174-
for(i = 0; i < strideableVectorAttributeCount / 4; i++) {
175-
attributes['p' + i.toString(16)] = {
176-
offset: i * 16,
177-
stride: positionStride,
178-
buffer: positionBuffer
179-
};
180-
}
181-
182187
var glAes = regl({
183188

184189
profile: false,
@@ -265,36 +270,29 @@ module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, data, unit
265270
colorClamp[1] = unitDomain[1];
266271
}
267272

268-
function approach(/* dimension */) {
269-
// console.log('Approached ', JSON.stringify(dimension.name));
270-
}
271-
272273
var previousAxisOrder = [];
273274

274-
var dims = d3.range(2).map(function() {return d3.range(4).map(function() {return new Float32Array(16);});});
275-
var lims = d3.range(2).map(function() {return d3.range(4).map(function() {return new Float32Array(16);});});
276-
277-
function renderGLParcoords(dimensionViews, setChanged, clearOnly) {
275+
function renderGLParcoords(dimensions, setChanged, clearOnly) {
278276

279277
var I;
280278

281279
function valid(i, offset) {
282-
return i < shownDimensionCount && i + offset < dimensionViews.length;
280+
return i + offset < dimensions.length;
283281
}
284282

285283
function orig(i) {
286-
var index = dimensionViews.map(function(v) {return v.originalXIndex;}).indexOf(i);
287-
return dimensionViews[index];
284+
var index = dimensions.map(function(v) {return v.originalXIndex;}).indexOf(i);
285+
return dimensions[index];
288286
}
289287

290288
var leftmostIndex, rightmostIndex, lowestX = Infinity, highestX = -Infinity;
291-
for(I = 0; I < shownPanelCount; I++) {
292-
if(dimensionViews[I].x > highestX) {
293-
highestX = dimensionViews[I].x;
289+
for(I = 0; I < panelCount; I++) {
290+
if(dimensions[I].canvasX > highestX) {
291+
highestX = dimensions[I].canvasX;
294292
rightmostIndex = I;
295293
}
296-
if(dimensionViews[I].x < lowestX) {
297-
lowestX = dimensionViews[I].x;
294+
if(dimensions[I].canvasX < lowestX) {
295+
lowestX = dimensions[I].canvasX;
298296
leftmostIndex = I;
299297
}
300298
}
@@ -303,12 +301,15 @@ module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, data, unit
303301
var loHi, abcd, d, index;
304302
var leftRight = [i, ii];
305303

304+
var dims = [0, 1].map(function() {return [0, 1, 2, 3].map(function() {return new Float32Array(16);});});
305+
var lims = [0, 1].map(function() {return [0, 1, 2, 3].map(function() {return new Float32Array(16);});});
306+
306307
for(loHi = 0; loHi < 2; loHi++) {
307308
index = leftRight[loHi];
308309
for(abcd = 0; abcd < 4; abcd++) {
309310
for(d = 0; d < 16; d++) {
310311
dims[loHi][abcd][d] = d + 16 * abcd === index ? 1 : 0;
311-
lims[loHi][abcd][d] = paddedUnit((!context && valid(d, 16 * abcd) ? orig(d + 16 * abcd).filter[loHi] : loHi)) + (2 * loHi - 1) * filterEpsilon;
312+
lims[loHi][abcd][d] = (!context && valid(d, 16 * abcd) ? orig(d + 16 * abcd).filter[loHi] : loHi) + (2 * loHi - 1) * filterEpsilon;
312313
}
313314
}
314315
}
@@ -344,30 +345,25 @@ module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, data, unit
344345
};
345346
}
346347

347-
for(I = 0; I < shownPanelCount; I++) {
348-
var dimensionView = dimensionViews[I];
349-
var i = dimensionView.originalXIndex;
350-
var x = dimensionView.x * canvasPixelRatio;
351-
var nextDim = dimensionViews[(I + 1) % shownDimensionCount];
348+
for(I = 0; I < panelCount; I++) {
349+
var dimension = dimensions[I];
350+
var i = dimension.originalXIndex;
351+
var x = dimension.canvasX;
352+
var nextDim = dimensions[(I + 1) % dimensionCount];
352353
var ii = nextDim.originalXIndex;
353-
var panelSizeX = nextDim.x * canvasPixelRatio - x;
354-
if(setChanged || !previousAxisOrder[i] || previousAxisOrder[i][0] !== x || previousAxisOrder[i][1] !== nextDim.x) {
355-
previousAxisOrder[i] = [x, nextDim.x];
356-
var item = makeItem(i, ii, x, panelSizeX, dimensionView.originalXIndex, dimensionView.scatter);
354+
var panelSizeX = nextDim.canvasX - x;
355+
if(setChanged || !previousAxisOrder[i] || previousAxisOrder[i][0] !== x || previousAxisOrder[i][1] !== nextDim.canvasX) {
356+
previousAxisOrder[i] = [x, nextDim.canvasX];
357+
var item = makeItem(i, ii, x, panelSizeX, dimension.originalXIndex, dimension.scatter);
357358
renderState.clearOnly = clearOnly;
358359
renderBlock(regl, glAes, renderState, setChanged ? lines.blocklinecount : sampleCount, sampleCount, item);
359360
}
360361
}
361362
}
362363

363-
function destroy() {
364-
regl.destroy();
365-
}
366-
367364
return {
368365
setColorDomain: setColorDomain,
369-
approach: approach,
370366
render: renderGLParcoords,
371-
destroy: destroy
367+
destroy: regl.destroy
372368
};
373369
};

0 commit comments

Comments
 (0)