@jrkasprzyk/parcoord-es 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -0
  3. package/dist/parcoords.css +61 -0
  4. package/dist/parcoords.esm.js +4132 -0
  5. package/dist/parcoords.esm.js.map +1 -0
  6. package/dist/parcoords.js +4130 -0
  7. package/dist/parcoords.js.map +1 -0
  8. package/dist/parcoords.standalone.js +11233 -0
  9. package/dist/parcoords.standalone.js.map +1 -0
  10. package/package.json +89 -0
  11. package/src/api/adjacentPairs.js +10 -0
  12. package/src/api/applyAxisConfig.js +35 -0
  13. package/src/api/applyDimensionDefaults.js +25 -0
  14. package/src/api/autoscale.js +133 -0
  15. package/src/api/axisDots.js +27 -0
  16. package/src/api/brushMode.js +50 -0
  17. package/src/api/brushReset.js +34 -0
  18. package/src/api/brushable.js +81 -0
  19. package/src/api/clear.js +21 -0
  20. package/src/api/commonScale.js +45 -0
  21. package/src/api/computeRealCentroids.js +8 -0
  22. package/src/api/createAxes.js +101 -0
  23. package/src/api/detectDimensionTypes.js +12 -0
  24. package/src/api/detectDimensions.js +7 -0
  25. package/src/api/flip.js +11 -0
  26. package/src/api/getOrderedDimensionKeys.js +8 -0
  27. package/src/api/highlight.js +26 -0
  28. package/src/api/init.js +53 -0
  29. package/src/api/interactive.js +7 -0
  30. package/src/api/intersection.js +16 -0
  31. package/src/api/mark.js +20 -0
  32. package/src/api/mergeParcoords.js +86 -0
  33. package/src/api/removeAxes.js +9 -0
  34. package/src/api/render.js +26 -0
  35. package/src/api/renderBrushed.js +39 -0
  36. package/src/api/renderDefault.js +27 -0
  37. package/src/api/renderMarked.js +34 -0
  38. package/src/api/reorder.js +37 -0
  39. package/src/api/reorderable.js +41 -0
  40. package/src/api/resize.js +36 -0
  41. package/src/api/scale.js +10 -0
  42. package/src/api/selected.js +209 -0
  43. package/src/api/shadows.js +9 -0
  44. package/src/api/sortDimensions.js +13 -0
  45. package/src/api/sortDimensionsByRowData.js +20 -0
  46. package/src/api/toString.js +11 -0
  47. package/src/api/toType.js +9 -0
  48. package/src/api/toTypeCoerceNumbers.js +7 -0
  49. package/src/api/unhighlight.js +12 -0
  50. package/src/api/unmark.js +12 -0
  51. package/src/api/updateAxes.js +103 -0
  52. package/src/bindEvents.js +60 -0
  53. package/src/brush/1d/brushExtents.js +73 -0
  54. package/src/brush/1d/brushFor.js +92 -0
  55. package/src/brush/1d/brushReset.js +36 -0
  56. package/src/brush/1d/index.js +20 -0
  57. package/src/brush/1d/install.js +41 -0
  58. package/src/brush/1d/selected.js +79 -0
  59. package/src/brush/1d/uninstall.js +12 -0
  60. package/src/brush/1d-multi/brushExtents.js +101 -0
  61. package/src/brush/1d-multi/brushFor.js +13 -0
  62. package/src/brush/1d-multi/brushReset.js +53 -0
  63. package/src/brush/1d-multi/drawBrushes.js +36 -0
  64. package/src/brush/1d-multi/index.js +20 -0
  65. package/src/brush/1d-multi/install.js +29 -0
  66. package/src/brush/1d-multi/newBrush.js +102 -0
  67. package/src/brush/1d-multi/selected.js +160 -0
  68. package/src/brush/1d-multi/uninstall.js +12 -0
  69. package/src/brush/angular/brushReset.js +14 -0
  70. package/src/brush/angular/index.js +19 -0
  71. package/src/brush/angular/install.js +141 -0
  72. package/src/brush/angular/onDrag.js +109 -0
  73. package/src/brush/angular/onDragEnd.js +30 -0
  74. package/src/brush/angular/onDragStart.js +38 -0
  75. package/src/brush/angular/removeStrum.js +12 -0
  76. package/src/brush/angular/selected.js +63 -0
  77. package/src/brush/angular/uninstall.js +17 -0
  78. package/src/brush/angular/util/hypothenuse.js +3 -0
  79. package/src/brush/consecutive.js +11 -0
  80. package/src/brush/dimensionsForPoint.js +32 -0
  81. package/src/brush/invertByScale.js +24 -0
  82. package/src/brush/strums/brushReset.js +14 -0
  83. package/src/brush/strums/index.js +19 -0
  84. package/src/brush/strums/install.js +81 -0
  85. package/src/brush/strums/onDrag.js +83 -0
  86. package/src/brush/strums/onDragEnd.js +20 -0
  87. package/src/brush/strums/onDragStart.js +33 -0
  88. package/src/brush/strums/removeStrum.js +10 -0
  89. package/src/brush/strums/selected.js +52 -0
  90. package/src/brush/strums/uninstall.js +16 -0
  91. package/src/index.js +226 -0
  92. package/src/parallel-coordinates.css +61 -0
  93. package/src/state/defaultConfig.js +36 -0
  94. package/src/state/index.js +86 -0
  95. package/src/state/sideEffects.js +89 -0
  96. package/src/util/colorPath.js +71 -0
  97. package/src/util/computeCentroids.js +44 -0
  98. package/src/util/computeClusterCentroids.js +33 -0
  99. package/src/util/computeControlPoints.js +38 -0
  100. package/src/util/dimensionLabels.js +11 -0
  101. package/src/util/flipAxisAndUpdatePCP.js +19 -0
  102. package/src/util/functor.js +3 -0
  103. package/src/util/getRange.js +24 -0
  104. package/src/util/getset.js +23 -0
  105. package/src/util/height.js +3 -0
  106. package/src/util/isBrushed.js +15 -0
  107. package/src/util/renderQueue.js +67 -0
  108. package/src/util/rotateLabels.js +18 -0
  109. package/src/util/width.js +3 -0
@@ -0,0 +1,89 @@
1
+ // side effects for setters
2
+ import { dispatch } from 'd3-dispatch';
3
+ import computeClusterCentroids from '../util/computeClusterCentroids';
4
+ import flipAxisAndUpdatePCP from '../util/flipAxisAndUpdatePCP';
5
+
6
+ const without = (arr, items) => {
7
+ items.forEach(el => {
8
+ delete arr[el];
9
+ });
10
+ return arr;
11
+ };
12
+
13
+ const sideEffects = (
14
+ config,
15
+ ctx,
16
+ pc,
17
+ xscale,
18
+ axis,
19
+ flags,
20
+ brushedQueue,
21
+ markedQueue,
22
+ foregroundQueue
23
+ ) =>
24
+ dispatch
25
+ .apply(this, Object.keys(config))
26
+ .on('composite', d => {
27
+ ctx.foreground.globalCompositeOperation = d.value;
28
+ ctx.brushed.globalCompositeOperation = d.value;
29
+ })
30
+ .on('alpha', d => {
31
+ ctx.foreground.globalAlpha = d.value;
32
+ ctx.brushed.globalAlpha = d.value;
33
+ })
34
+ .on('brushedColor', d => {
35
+ ctx.brushed.strokeStyle = d.value;
36
+ })
37
+ .on('width', d => pc.resize())
38
+ .on('height', d => pc.resize())
39
+ .on('margin', d => pc.resize())
40
+ .on('rate', d => {
41
+ brushedQueue.rate(d.value);
42
+ markedQueue.rate(d.value);
43
+ foregroundQueue.rate(d.value);
44
+ })
45
+ .on('dimensions', d => {
46
+ config.dimensions = pc.applyDimensionDefaults(Object.keys(d.value));
47
+ xscale.domain(pc.getOrderedDimensionKeys());
48
+ pc.sortDimensions();
49
+ if (flags.interactive) {
50
+ pc.render().updateAxes();
51
+ }
52
+ })
53
+ .on('bundleDimension', d => {
54
+ if (!Object.keys(config.dimensions).length) pc.detectDimensions();
55
+ pc.autoscale();
56
+ if (typeof d.value === 'number') {
57
+ if (d.value < Object.keys(config.dimensions).length) {
58
+ config.bundleDimension = config.dimensions[d.value];
59
+ } else if (d.value < config.hideAxis.length) {
60
+ config.bundleDimension = config.hideAxis[d.value];
61
+ }
62
+ } else {
63
+ config.bundleDimension = d.value;
64
+ }
65
+
66
+ config.clusterCentroids = computeClusterCentroids(
67
+ config,
68
+ config.bundleDimension
69
+ );
70
+ if (flags.interactive) {
71
+ pc.render();
72
+ }
73
+ })
74
+ .on('hideAxis', d => {
75
+ pc.brushReset();
76
+ pc.dimensions(pc.applyDimensionDefaults());
77
+ pc.dimensions(without(config.dimensions, d.value));
78
+ pc.render();
79
+ })
80
+ .on('flipAxes', d => {
81
+ if (d.value && d.value.length) {
82
+ d.value.forEach(function(dimension) {
83
+ flipAxisAndUpdatePCP(config, pc, axis)(dimension);
84
+ });
85
+ pc.updateAxes(0);
86
+ }
87
+ });
88
+
89
+ export default sideEffects;
@@ -0,0 +1,71 @@
1
+ // draw single cubic bezier curve
2
+ import computeCentroids from './computeCentroids';
3
+ import computeControlPoints from './computeControlPoints';
4
+ import h from './height';
5
+
6
+ const singleCurve = (config, position, d, ctx) => {
7
+ const centroids = computeCentroids(config, position, d);
8
+ const cps = computeControlPoints(config.smoothness, centroids);
9
+
10
+ ctx.moveTo(cps[0].e(1), cps[0].e(2));
11
+
12
+ for (let i = 1; i < cps.length; i += 3) {
13
+ if (config.showControlPoints) {
14
+ for (let j = 0; j < 3; j++) {
15
+ ctx.fillRect(cps[i + j].e(1), cps[i + j].e(2), 2, 2);
16
+ }
17
+ }
18
+ ctx.bezierCurveTo(
19
+ cps[i].e(1),
20
+ cps[i].e(2),
21
+ cps[i + 1].e(1),
22
+ cps[i + 1].e(2),
23
+ cps[i + 2].e(1),
24
+ cps[i + 2].e(2)
25
+ );
26
+ }
27
+ };
28
+
29
+ // returns the y-position just beyond the separating null value line
30
+ const getNullPosition = config => {
31
+ if (config.nullValueSeparator === 'bottom') {
32
+ return h(config) + 1;
33
+ } else if (config.nullValueSeparator === 'top') {
34
+ return 1;
35
+ } else {
36
+ console.log(
37
+ "A value is NULL, but nullValueSeparator is not set; set it to 'bottom' or 'top'."
38
+ );
39
+ }
40
+ return h(config) + 1;
41
+ };
42
+
43
+ const singlePath = (config, position, d, ctx) => {
44
+ Object.keys(config.dimensions)
45
+ .map(p => [
46
+ position(p),
47
+ d[p] === undefined
48
+ ? getNullPosition(config)
49
+ : config.dimensions[p].yscale(d[p]),
50
+ ])
51
+ .sort((a, b) => a[0] - b[0])
52
+ .forEach((p, i) => {
53
+ i === 0 ? ctx.moveTo(p[0], p[1]) : ctx.lineTo(p[0], p[1]);
54
+ });
55
+ };
56
+
57
+ // draw single polyline
58
+ const colorPath = (config, position, d, ctx) => {
59
+ ctx.beginPath();
60
+ if (
61
+ (config.bundleDimension !== null && config.bundlingStrength > 0) ||
62
+ config.smoothness > 0
63
+ ) {
64
+ singleCurve(config, position, d, ctx);
65
+ } else {
66
+ singlePath(config, position, d, ctx);
67
+ }
68
+ ctx.stroke();
69
+ };
70
+
71
+ export default colorPath;
@@ -0,0 +1,44 @@
1
+ import { Vector } from 'sylvester-es6/src/Vector';
2
+
3
+ const computeCentroids = (config, position, row) => {
4
+ const centroids = [];
5
+
6
+ const p = Object.keys(config.dimensions);
7
+ const cols = p.length;
8
+ const a = 0.5; // center between axes
9
+ for (let i = 0; i < cols; ++i) {
10
+ // centroids on 'real' axes
11
+ const x = position(p[i]);
12
+ const y = config.dimensions[p[i]].yscale(row[p[i]]);
13
+ centroids.push(new Vector([x, y]));
14
+
15
+ // centroids on 'virtual' axes
16
+ if (i < cols - 1) {
17
+ const cx = x + a * (position(p[i + 1]) - x);
18
+ let cy = y + a * (config.dimensions[p[i + 1]].yscale(row[p[i + 1]]) - y);
19
+ if (config.bundleDimension !== null) {
20
+ const leftCentroid = config.clusterCentroids
21
+ .get(
22
+ config.dimensions[config.bundleDimension].yscale(
23
+ row[config.bundleDimension]
24
+ )
25
+ )
26
+ .get(p[i]);
27
+ const rightCentroid = config.clusterCentroids
28
+ .get(
29
+ config.dimensions[config.bundleDimension].yscale(
30
+ row[config.bundleDimension]
31
+ )
32
+ )
33
+ .get(p[i + 1]);
34
+ let centroid = 0.5 * (leftCentroid + rightCentroid);
35
+ cy = centroid + (1 - config.bundlingStrength) * (cy - centroid);
36
+ }
37
+ centroids.push(new Vector([cx, cy]));
38
+ }
39
+ }
40
+
41
+ return centroids;
42
+ };
43
+
44
+ export default computeCentroids;
@@ -0,0 +1,33 @@
1
+ const computeClusterCentroids = (config, d) => {
2
+ const clusterCentroids = new Map();
3
+ const clusterCounts = new Map();
4
+ // determine clusterCounts
5
+ config.data.forEach(function(row) {
6
+ let scaled = config.dimensions[d].yscale(row[d]);
7
+ if (!clusterCounts.has(scaled)) {
8
+ clusterCounts.set(scaled, 0);
9
+ }
10
+ let count = clusterCounts.get(scaled);
11
+ clusterCounts.set(scaled, count + 1);
12
+ });
13
+
14
+ config.data.forEach(function(row) {
15
+ Object.keys(config.dimensions).map(p => {
16
+ let scaled = config.dimensions[d].yscale(row[d]);
17
+ if (!clusterCentroids.has(scaled)) {
18
+ const _map = new Map();
19
+ clusterCentroids.set(scaled, _map);
20
+ }
21
+ if (!clusterCentroids.get(scaled).has(p)) {
22
+ clusterCentroids.get(scaled).set(p, 0);
23
+ }
24
+ let value = clusterCentroids.get(scaled).get(p);
25
+ value += config.dimensions[p].yscale(row[p]) / clusterCounts.get(scaled);
26
+ clusterCentroids.get(scaled).set(p, value);
27
+ });
28
+ });
29
+
30
+ return clusterCentroids;
31
+ };
32
+
33
+ export default computeClusterCentroids;
@@ -0,0 +1,38 @@
1
+ import { Vector } from 'sylvester-es6/src/Vector';
2
+
3
+ const computeControlPoints = (smoothness, centroids) => {
4
+ const cols = centroids.length;
5
+ const a = smoothness;
6
+ const cps = [];
7
+
8
+ cps.push(centroids[0]);
9
+ cps.push(
10
+ new Vector([
11
+ centroids[0].e(1) + a * 2 * (centroids[1].e(1) - centroids[0].e(1)),
12
+ centroids[0].e(2),
13
+ ])
14
+ );
15
+ for (let col = 1; col < cols - 1; ++col) {
16
+ let mid = centroids[col];
17
+ let left = centroids[col - 1];
18
+ let right = centroids[col + 1];
19
+
20
+ let diff = left.subtract(right);
21
+ cps.push(mid.add(diff.x(a)));
22
+ cps.push(mid);
23
+ cps.push(mid.subtract(diff.x(a)));
24
+ }
25
+
26
+ cps.push(
27
+ new Vector([
28
+ centroids[cols - 1].e(1) +
29
+ a * 2 * (centroids[cols - 2].e(1) - centroids[cols - 1].e(1)),
30
+ centroids[cols - 1].e(2),
31
+ ])
32
+ );
33
+ cps.push(centroids[cols - 1]);
34
+
35
+ return cps;
36
+ };
37
+
38
+ export default computeControlPoints;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * dimension display names
3
+ *
4
+ * @param config
5
+ * @param d
6
+ * @returns {*}
7
+ */
8
+ const dimensionLabels = config => d =>
9
+ config.dimensions[d].title ? config.dimensions[d].title : d;
10
+
11
+ export default dimensionLabels;
@@ -0,0 +1,19 @@
1
+ import { select, selectAll } from 'd3-selection';
2
+
3
+ const flipAxisAndUpdatePCP = (config, pc, axis) =>
4
+ function(dimension) {
5
+ pc.flip(dimension);
6
+ pc.brushReset(dimension);
7
+
8
+ // select(this.parentElement)
9
+ pc.selection
10
+ .select('svg')
11
+ .selectAll('g.axis')
12
+ .filter(d => d === dimension)
13
+ .transition()
14
+ .duration(config.animationTime)
15
+ .call(axis.scale(config.dimensions[dimension].yscale));
16
+ pc.render();
17
+ };
18
+
19
+ export default flipAxisAndUpdatePCP;
@@ -0,0 +1,3 @@
1
+ const _functor = v => (typeof v === 'function' ? v : () => v);
2
+
3
+ export default _functor;
@@ -0,0 +1,24 @@
1
+ /** adjusts an axis' default range [h()+1, 1] if a NullValueSeparator is set */
2
+ const getRange = config => {
3
+ const h = config.height - config.margin.top - config.margin.bottom;
4
+
5
+ if (config.nullValueSeparator == 'bottom') {
6
+ return [
7
+ h +
8
+ 1 -
9
+ config.nullValueSeparatorPadding.bottom -
10
+ config.nullValueSeparatorPadding.top,
11
+ 1,
12
+ ];
13
+ } else if (config.nullValueSeparator == 'top') {
14
+ return [
15
+ h + 1,
16
+ 1 +
17
+ config.nullValueSeparatorPadding.bottom +
18
+ config.nullValueSeparatorPadding.top,
19
+ ];
20
+ }
21
+ return [h + 1, 1];
22
+ };
23
+
24
+ export default getRange;
@@ -0,0 +1,23 @@
1
+ const getset = (obj, state, events, side_effects) => {
2
+ Object.keys(state).forEach(function(key) {
3
+ obj[key] = function(x) {
4
+ if (!arguments.length) {
5
+ return state[key];
6
+ }
7
+ if (
8
+ key === 'dimensions' &&
9
+ Object.prototype.toString.call(x) === '[object Array]'
10
+ ) {
11
+ console.warn('pc.dimensions([]) is deprecated, use pc.dimensions({})');
12
+ x = obj.applyDimensionDefaults(x);
13
+ }
14
+ let old = state[key];
15
+ state[key] = x;
16
+ side_effects.call(key, obj, { value: x, previous: old });
17
+ events.call(key, obj, { value: x, previous: old });
18
+ return obj;
19
+ };
20
+ });
21
+ };
22
+
23
+ export default getset;
@@ -0,0 +1,3 @@
1
+ const h = config => config.height - config.margin.top - config.margin.bottom;
2
+
3
+ export default h;
@@ -0,0 +1,15 @@
1
+ const isBrushed = (config, brushGroup) => {
2
+ if (config.brushed && config.brushed.length !== config.data.length)
3
+ return true;
4
+
5
+ const object = brushGroup.currentMode().brushState();
6
+
7
+ for (let key in object) {
8
+ if (object.hasOwnProperty(key)) {
9
+ return true;
10
+ }
11
+ }
12
+ return false;
13
+ };
14
+
15
+ export default isBrushed;
@@ -0,0 +1,67 @@
1
+ import 'requestanimationframe';
2
+
3
+ const renderQueue = function(func) {
4
+ let _queue = [], // data to be rendered
5
+ _rate = 1000, // number of calls per frame
6
+ _invalidate = function() {}, // invalidate last render queue
7
+ _clear = function() {}; // clearing function
8
+
9
+ let rq = function(data) {
10
+ if (data) rq.data(data);
11
+ _invalidate();
12
+ _clear();
13
+ rq.render();
14
+ };
15
+
16
+ rq.render = function() {
17
+ let valid = true;
18
+ _invalidate = rq.invalidate = function() {
19
+ valid = false;
20
+ };
21
+
22
+ function doFrame() {
23
+ if (!valid) return true;
24
+ let chunk = _queue.splice(0, _rate);
25
+ chunk.map(func);
26
+ requestAnimationFrame(doFrame);
27
+ }
28
+
29
+ doFrame();
30
+ };
31
+
32
+ rq.data = function(data) {
33
+ _invalidate();
34
+ _queue = data.slice(0); // creates a copy of the data
35
+ return rq;
36
+ };
37
+
38
+ rq.add = function(data) {
39
+ _queue = _queue.concat(data);
40
+ };
41
+
42
+ rq.rate = function(value) {
43
+ if (!arguments.length) return _rate;
44
+ _rate = value;
45
+ return rq;
46
+ };
47
+
48
+ rq.remaining = function() {
49
+ return _queue.length;
50
+ };
51
+
52
+ // clear the canvas
53
+ rq.clear = function(func) {
54
+ if (!arguments.length) {
55
+ _clear();
56
+ return rq;
57
+ }
58
+ _clear = func;
59
+ return rq;
60
+ };
61
+
62
+ rq.invalidate = _invalidate;
63
+
64
+ return rq;
65
+ };
66
+
67
+ export default renderQueue;
@@ -0,0 +1,18 @@
1
+ const rotateLabels = (config, pc) => (event) => {
2
+ if (!config.rotateLabels) return;
3
+
4
+ let delta = event.deltaY;
5
+ delta = delta < 0 ? -5 : delta;
6
+ delta = delta > 0 ? 5 : delta;
7
+
8
+ config.dimensionTitleRotation += delta;
9
+ pc.svg
10
+ .selectAll('text.label')
11
+ .attr(
12
+ 'transform',
13
+ 'translate(0,-5) rotate(' + config.dimensionTitleRotation + ')'
14
+ );
15
+ event.preventDefault();
16
+ };
17
+
18
+ export default rotateLabels;
@@ -0,0 +1,3 @@
1
+ const w = config => config.width - config.margin.right - config.margin.left;
2
+
3
+ export default w;