@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
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@jrkasprzyk/parcoord-es",
3
+ "version": "3.0.0",
4
+ "description": "ES6 module of Parallel Coordinates, based on d3 v7 modules",
5
+ "main": "dist/parcoords.js",
6
+ "module": "dist/parcoords.esm.js",
7
+ "jsnext:main": "dist/parcoords.esm.js",
8
+ "style": "dist/parcoords.css",
9
+ "scripts": {
10
+ "_prebuild": "eslint src test",
11
+ "build": "rollup -c",
12
+ "dev": "rollup -c rollup.config.dev.js -w",
13
+ "watch": "rollup -c -w",
14
+ "pretest": "npm run build",
15
+ "test:watch": "mocha --reporter min --watch",
16
+ "test": "mocha",
17
+ "test:cover": "babel-node ./node_modules/mocha/bin/_mocha",
18
+ "prepare": "npm test",
19
+ "format": "prettier --single-quote --trailing-comma es5 --write \"{src,__{tests,demo,dist}__}/**/*.{js,html}\""
20
+ },
21
+ "files": [
22
+ "src",
23
+ "dist"
24
+ ],
25
+ "dependencies": {
26
+ "d3-array": "^3.0.0",
27
+ "d3-axis": "^3.0.0",
28
+ "d3-brush": "^3.0.0",
29
+ "d3-dispatch": "^3.0.0",
30
+ "d3-drag": "^3.0.0",
31
+ "d3-interpolate": "^3.0.0",
32
+ "d3-scale": "^4.0.0",
33
+ "d3-selection": "^3.0.0",
34
+ "d3-shape": "^3.0.0",
35
+ "d3-transition": "^3.0.0",
36
+ "requestanimationframe": "0.0.23",
37
+ "sylvester-es6": "0.0.2"
38
+ },
39
+ "devDependencies": {
40
+ "@babel/cli": "^7.25.0",
41
+ "@babel/core": "^7.25.0",
42
+ "@babel/eslint-parser": "^7.25.0",
43
+ "@babel/plugin-proposal-object-rest-spread": "^7.20.0",
44
+ "@babel/preset-env": "^7.25.0",
45
+ "@babel/register": "^7.25.0",
46
+ "@rollup/plugin-babel": "^6.0.0",
47
+ "@rollup/plugin-commonjs": "^25.0.0",
48
+ "@rollup/plugin-json": "^6.0.0",
49
+ "@rollup/plugin-node-resolve": "^15.0.0",
50
+ "eslint": "^8.57.0",
51
+ "mocha": "^11.0.0",
52
+ "prettier": "1.13.5",
53
+ "rollup": "^4.20.0",
54
+ "rollup-plugin-livereload": "^2.0.5",
55
+ "rollup-plugin-postcss": "^4.0.2",
56
+ "rollup-plugin-serve": "^3.0.0"
57
+ },
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git+https://github.com/jrkasprzyk/parcoords-es.git"
61
+ },
62
+ "keywords": [
63
+ "es6",
64
+ "d3",
65
+ "v4",
66
+ "ParCoords",
67
+ "visualization",
68
+ "parallel coordinates"
69
+ ],
70
+ "author": {
71
+ "name": "Xing Yun"
72
+ },
73
+ "contributors": [
74
+ {
75
+ "name": "Joseph Kasprzyk"
76
+ }
77
+ ],
78
+ "license": "MIT",
79
+ "publishConfig": {
80
+ "access": "public"
81
+ },
82
+ "overrides": {
83
+ "diff": "^8.0.3",
84
+ "serialize-javascript": "^7.0.5"
85
+ },
86
+ "bugs": {
87
+ "url": "https://github.com/jrkasprzyk/parcoords-es/issues"
88
+ }
89
+ }
@@ -0,0 +1,10 @@
1
+ // pairs of adjacent dimensions
2
+ const adjacentPairs = arr => {
3
+ let ret = [];
4
+ for (let i = 0; i < arr.length - 1; i++) {
5
+ ret.push([arr[i], arr[i + 1]]);
6
+ }
7
+ return ret;
8
+ };
9
+
10
+ export default adjacentPairs;
@@ -0,0 +1,35 @@
1
+ import { axisBottom, axisLeft, axisRight, axisTop } from 'd3-axis';
2
+
3
+ const applyAxisConfig = (axis, dimension) => {
4
+ let axisCfg;
5
+
6
+ switch (dimension.orient) {
7
+ case 'left':
8
+ axisCfg = axisLeft(dimension.yscale);
9
+ break;
10
+ case 'right':
11
+ axisCfg = axisRight(dimension.yscale);
12
+ break;
13
+ case 'top':
14
+ axisCfg = axisTop(dimension.yscale);
15
+ break;
16
+ case 'bottom':
17
+ axisCfg = axisBottom(dimension.yscale);
18
+ break;
19
+ default:
20
+ axisCfg = axisLeft(dimension.yscale);
21
+ break;
22
+ }
23
+
24
+ axisCfg
25
+ .ticks(dimension.ticks)
26
+ .tickValues(dimension.tickValues)
27
+ .tickSizeInner(dimension.innerTickSize)
28
+ .tickSizeOuter(dimension.outerTickSize)
29
+ .tickPadding(dimension.tickPadding)
30
+ .tickFormat(dimension.tickFormat);
31
+
32
+ return axisCfg;
33
+ };
34
+
35
+ export default applyAxisConfig;
@@ -0,0 +1,25 @@
1
+ const isValid = d => d !== null && d !== undefined;
2
+
3
+ const applyDimensionDefaults = (config, pc) =>
4
+ function(dims) {
5
+ const types = pc.detectDimensionTypes(config.data);
6
+ dims = dims ? dims : Object.keys(types);
7
+
8
+ return dims.reduce((acc, cur, i) => {
9
+ const k = config.dimensions[cur] ? config.dimensions[cur] : {};
10
+ acc[cur] = {
11
+ ...k,
12
+ orient: isValid(k.orient) ? k.orient : 'left',
13
+ ticks: isValid(k.ticks) ? k.ticks : 5,
14
+ innerTickSize: isValid(k.innerTickSize) ? k.innerTickSize : 6,
15
+ outerTickSize: isValid(k.outerTickSize) ? k.outerTickSize : 0,
16
+ tickPadding: isValid(k.tickPadding) ? k.tickPadding : 3,
17
+ type: isValid(k.type) ? k.type : types[cur],
18
+ index: isValid(k.index) ? k.index : i,
19
+ };
20
+
21
+ return acc;
22
+ }, {});
23
+ };
24
+
25
+ export default applyDimensionDefaults;
@@ -0,0 +1,133 @@
1
+ import { scaleLinear, scaleOrdinal, scalePoint, scaleTime } from 'd3-scale';
2
+ import { extent } from 'd3-array';
3
+
4
+ import getRange from '../util/getRange';
5
+ import w from '../util/width';
6
+ import h from '../util/height';
7
+
8
+ const autoscale = (config, pc, xscale, ctx) =>
9
+ function() {
10
+ // yscale
11
+ const defaultScales = {
12
+ date: function(k) {
13
+ let _extent = extent(config.data, d => (d[k] ? d[k].getTime() : null));
14
+ // special case if single value
15
+ if (_extent[0] === _extent[1]) {
16
+ return scalePoint()
17
+ .domain(_extent)
18
+ .range(getRange(config));
19
+ }
20
+ if (config.flipAxes.includes(k)) {
21
+ _extent = _extent.map(val => tempDate.unshift(val));
22
+ }
23
+ return scaleTime()
24
+ .domain(_extent)
25
+ .range(getRange(config));
26
+ },
27
+ number: function(k) {
28
+ let _extent = extent(config.data, d => +d[k]);
29
+ // special case if single value
30
+ if (_extent[0] === _extent[1]) {
31
+ return scalePoint()
32
+ .domain(_extent)
33
+ .range(getRange(config));
34
+ }
35
+ if (config.flipAxes.includes(k)) {
36
+ _extent = _extent.map(val => tempDate.unshift(val));
37
+ }
38
+ return scaleLinear()
39
+ .domain(_extent)
40
+ .range(getRange(config));
41
+ },
42
+ string: function(k) {
43
+ let counts = {},
44
+ domain = [];
45
+ // Let's get the count for each value so that we can sort the domain based
46
+ // on the number of items for each value.
47
+ config.data.map(p => {
48
+ if (p[k] === undefined && config.nullValueSeparator !== 'undefined') {
49
+ return null; // null values will be drawn beyond the horizontal null value separator!
50
+ }
51
+ if (counts[p[k]] === undefined) {
52
+ counts[p[k]] = 1;
53
+ } else {
54
+ counts[p[k]] = counts[p[k]] + 1;
55
+ }
56
+ });
57
+ if (config.flipAxes.includes(k)) {
58
+ domain = Object.getOwnPropertyNames(counts).sort();
59
+ } else {
60
+ let tempArr = Object.getOwnPropertyNames(counts).sort();
61
+ for (let i = 0; i < Object.getOwnPropertyNames(counts).length; i++) {
62
+ domain.push(tempArr.pop());
63
+ }
64
+ }
65
+
66
+ //need to create an ordinal scale for categorical data
67
+ let categoricalRange = [];
68
+ if (domain.length === 1) {
69
+ //edge case
70
+ domain = [' ', domain[0], ' '];
71
+ }
72
+ let addBy = getRange(config)[0] / (domain.length - 1);
73
+ for (let j = 0; j < domain.length; j++) {
74
+ if (categoricalRange.length === 0) {
75
+ categoricalRange.push(0);
76
+ continue;
77
+ }
78
+ categoricalRange.push(categoricalRange[j - 1] + addBy);
79
+ }
80
+ return scaleOrdinal()
81
+ .domain(domain)
82
+ .range(categoricalRange);
83
+ },
84
+ };
85
+ Object.keys(config.dimensions).forEach(function(k) {
86
+ if (
87
+ config.dimensions[k].yscale === undefined ||
88
+ config.dimensions[k].yscale === null
89
+ ) {
90
+ config.dimensions[k].yscale = defaultScales[config.dimensions[k].type](
91
+ k
92
+ );
93
+ }
94
+ });
95
+
96
+ // xscale
97
+ // add padding for d3 >= v4 default 0.2
98
+ xscale.range([0, w(config)]).padding(0.2);
99
+
100
+ // Retina display, etc.
101
+ const devicePixelRatio = window.devicePixelRatio || 1;
102
+
103
+ // canvas sizes
104
+ pc.selection
105
+ .selectAll('canvas')
106
+ .style('margin-top', config.margin.top + 'px')
107
+ .style('margin-left', config.margin.left + 'px')
108
+ .style('width', w(config) + 2 + 'px')
109
+ .style('height', h(config) + 2 + 'px')
110
+ .attr('width', (w(config) + 2) * devicePixelRatio)
111
+ .attr('height', (h(config) + 2) * devicePixelRatio);
112
+ // default styles, needs to be set when canvas width changes
113
+ ctx.foreground.strokeStyle = config.color;
114
+ ctx.foreground.lineWidth = config.lineWidth;
115
+ ctx.foreground.globalCompositeOperation = config.composite;
116
+ ctx.foreground.globalAlpha = config.alpha;
117
+ ctx.foreground.scale(devicePixelRatio, devicePixelRatio);
118
+ ctx.brushed.strokeStyle = config.brushedColor;
119
+ ctx.brushed.lineWidth = config.lineWidth;
120
+ ctx.brushed.globalCompositeOperation = config.composite;
121
+ ctx.brushed.globalAlpha = config.alpha;
122
+ ctx.brushed.scale(devicePixelRatio, devicePixelRatio);
123
+ ctx.highlight.lineWidth = config.highlightedLineWidth;
124
+ ctx.highlight.scale(devicePixelRatio, devicePixelRatio);
125
+ ctx.marked.lineWidth = config.markedLineWidth;
126
+ ctx.marked.shadowColor = config.markedShadowColor;
127
+ ctx.marked.shadowBlur = config.markedShadowBlur;
128
+ ctx.marked.scale(devicePixelRatio, devicePixelRatio);
129
+
130
+ return this;
131
+ };
132
+
133
+ export default autoscale;
@@ -0,0 +1,27 @@
1
+ import { min } from 'd3-array';
2
+
3
+ //draw dots with radius r on the axis line where data intersects
4
+ const axisDots = (config, pc, position) => _r => {
5
+ const r = _r || 0.1;
6
+ const ctx = pc.ctx.dots;
7
+ const startAngle = 0;
8
+ const endAngle = 2 * Math.PI;
9
+ ctx.globalAlpha = min([1 / Math.pow(config.data.length, 1 / 2), 1]);
10
+ config.data.forEach(d => {
11
+ Object.entries(config.dimensions).forEach(([key], i) => {
12
+ ctx.beginPath();
13
+ ctx.arc(
14
+ position(key),
15
+ config.dimensions[key].yscale(d[key]),
16
+ r,
17
+ startAngle,
18
+ endAngle
19
+ );
20
+ ctx.stroke();
21
+ ctx.fill();
22
+ });
23
+ });
24
+ return this;
25
+ };
26
+
27
+ export default axisDots;
@@ -0,0 +1,50 @@
1
+ const brushPredicate = (brushGroup, config, pc) => (predicate = null) => {
2
+ if (predicate === null) {
3
+ return brushGroup.predicate;
4
+ }
5
+
6
+ predicate = String(predicate).toUpperCase();
7
+ if (predicate !== 'AND' && predicate !== 'OR') {
8
+ throw new Error('Invalid predicate ' + predicate);
9
+ }
10
+
11
+ brushGroup.predicate = predicate;
12
+ config.brushed = brushGroup.currentMode().selected();
13
+ pc.renderBrushed();
14
+ return pc;
15
+ };
16
+
17
+ const brushMode = (brushGroup, config, pc) => (mode = null) => {
18
+ if (mode === null) {
19
+ return brushGroup.mode;
20
+ }
21
+
22
+ if (pc.brushModes().indexOf(mode) === -1) {
23
+ throw new Error('pc.brushmode: Unsupported brush mode: ' + mode);
24
+ }
25
+
26
+ // Make sure that we don't trigger unnecessary events by checking if the mode
27
+ // actually changes.
28
+ if (mode !== brushGroup.mode) {
29
+ // When changing brush modes, the first thing we need to do is clearing any
30
+ // brushes from the current mode, if any.
31
+ if (brushGroup.mode !== 'None') {
32
+ pc.brushReset();
33
+ }
34
+
35
+ // Next, we need to 'uninstall' the current brushMode.
36
+ brushGroup.modes[brushGroup.mode].uninstall(pc);
37
+ // Finally, we can install the requested one.
38
+ brushGroup.mode = mode;
39
+ brushGroup.modes[brushGroup.mode].install();
40
+ if (mode === 'None') {
41
+ delete pc.brushPredicate;
42
+ } else {
43
+ pc.brushPredicate = brushPredicate(brushGroup, config, pc);
44
+ }
45
+ }
46
+
47
+ return pc;
48
+ };
49
+
50
+ export default brushMode;
@@ -0,0 +1,34 @@
1
+ import { select } from 'd3-selection';
2
+
3
+ const brushReset = (config, pc) =>
4
+ function(dimension) {
5
+ const brushesToKeep = [];
6
+ for (let j = 0; j < config.brushes.length; j++) {
7
+ if (config.brushes[j].data !== dimension) {
8
+ brushesToKeep.push(config.brushes[j]);
9
+ }
10
+ }
11
+
12
+ config.brushes = brushesToKeep;
13
+ config.brushed = false;
14
+
15
+ if (pc.g() !== undefined) {
16
+ const nodes = pc
17
+ .g()
18
+ .selectAll('.brush')
19
+ .nodes();
20
+ for (let i = 0; i < nodes.length; i++) {
21
+ if (nodes[i].__data__ === dimension) {
22
+ // remove all dummy brushes for this axis or the real brush
23
+ select(select(nodes[i]).nodes()[0].parentNode)
24
+ .selectAll('.dummy')
25
+ .remove();
26
+ config.dimensions[dimension].brush.move(select(nodes[i], null));
27
+ }
28
+ }
29
+ }
30
+
31
+ return this;
32
+ };
33
+
34
+ export default brushReset;
@@ -0,0 +1,81 @@
1
+ import { brushSelection, brushY } from 'd3-brush';
2
+ import { select } from 'd3-selection';
3
+
4
+ const brushable = (config, pc, flags) =>
5
+ function() {
6
+ if (!pc.g()) {
7
+ pc.createAxes();
8
+ }
9
+
10
+ const g = pc.g();
11
+
12
+ // Add and store a brush for each axis.
13
+ g.append('svg:g')
14
+ .attr('class', 'brush')
15
+ .each(function(d) {
16
+ if (config.dimensions[d] !== undefined) {
17
+ config.dimensions[d]['brush'] = brushY(select(this)).extent([
18
+ [-15, 0],
19
+ [15, config.dimensions[d].yscale.range()[0]],
20
+ ]);
21
+ select(this).call(
22
+ config.dimensions[d]['brush']
23
+ .on('start', function(event) {
24
+ if (event.sourceEvent !== null && !event.sourceEvent.ctrlKey) {
25
+ pc.brushReset();
26
+ }
27
+ })
28
+ .on('brush', function(event) {
29
+ if (!event.sourceEvent.ctrlKey) {
30
+ pc.brush();
31
+ }
32
+ })
33
+ .on('end', function(event) {
34
+ // save brush selection is ctrl key is held
35
+ // store important brush information and
36
+ // the html element of the selection,
37
+ // to make a dummy selection element
38
+ if (event.sourceEvent.ctrlKey) {
39
+ let html = select(this)
40
+ .select('.selection')
41
+ .nodes()[0].outerHTML;
42
+ html = html.replace(
43
+ 'class="selection"',
44
+ 'class="selection dummy' +
45
+ ' selection-' +
46
+ config.brushes.length +
47
+ '"'
48
+ );
49
+ let dat = select(this).nodes()[0].__data__;
50
+ let brush = {
51
+ id: config.brushes.length,
52
+ extent: brushSelection(this),
53
+ html: html,
54
+ data: dat,
55
+ };
56
+ config.brushes.push(brush);
57
+ select(select(this).nodes()[0].parentNode)
58
+ .select('.axis')
59
+ .nodes()[0].outerHTML += html;
60
+ pc.brush();
61
+ config.dimensions[d].brush.move(select(this, null));
62
+ select(this)
63
+ .select('.selection')
64
+ .attr('style', 'display:none');
65
+ pc.brushable();
66
+ } else {
67
+ pc.brush();
68
+ }
69
+ })
70
+ );
71
+ select(this).on('dblclick', function() {
72
+ pc.brushReset(d);
73
+ });
74
+ }
75
+ });
76
+
77
+ flags.brushable = true;
78
+ return this;
79
+ };
80
+
81
+ export default brushable;
@@ -0,0 +1,21 @@
1
+ import isBrushed from '../util/isBrushed';
2
+ import w from '../util/width';
3
+ import h from '../util/height';
4
+
5
+ const clear = (config, pc, ctx, brushGroup) =>
6
+ function(layer) {
7
+ ctx[layer].clearRect(0, 0, w(config) + 2, h(config) + 2);
8
+
9
+ // This will make sure that the foreground items are transparent
10
+ // without the need for changing the opacity style of the foreground canvas
11
+ // as this would stop the css styling from working
12
+ if (layer === 'brushed' && isBrushed(config, brushGroup)) {
13
+ ctx.brushed.fillStyle = pc.selection.style('background-color');
14
+ ctx.brushed.globalAlpha = 1 - config.alphaOnBrushed;
15
+ ctx.brushed.fillRect(0, 0, w(config) + 2, h(config) + 2);
16
+ ctx.brushed.globalAlpha = config.alpha;
17
+ }
18
+ return this;
19
+ };
20
+
21
+ export default clear;
@@ -0,0 +1,45 @@
1
+ import { extent } from 'd3-array';
2
+
3
+ const commonScale = (config, pc) =>
4
+ function(global, type) {
5
+ const t = type || 'number';
6
+ if (typeof global === 'undefined') {
7
+ global = true;
8
+ }
9
+
10
+ // try to autodetect dimensions and create scales
11
+ if (!Object.keys(config.dimensions).length) {
12
+ pc.detectDimensions();
13
+ }
14
+ pc.autoscale();
15
+
16
+ // scales of the same type
17
+ const scales = Object.keys(config.dimensions).filter(
18
+ p => config.dimensions[p].type == t
19
+ );
20
+
21
+ if (global) {
22
+ let _extent = extent(
23
+ scales
24
+ .map(d => config.dimensions[d].yscale.domain())
25
+ .reduce((cur, acc) => cur.concat(acc))
26
+ );
27
+
28
+ scales.forEach(d => {
29
+ config.dimensions[d].yscale.domain(_extent);
30
+ });
31
+ } else {
32
+ scales.forEach(d => {
33
+ config.dimensions[d].yscale.domain(extent(config.data, d => +d[k]));
34
+ });
35
+ }
36
+
37
+ // update centroids
38
+ if (config.bundleDimension !== null) {
39
+ pc.bundleDimension(config.bundleDimension);
40
+ }
41
+
42
+ return this;
43
+ };
44
+
45
+ export default commonScale;
@@ -0,0 +1,8 @@
1
+ const computeRealCentroids = (config, position) => row =>
2
+ Object.keys(config.dimensions).map(d => {
3
+ const x = position(d);
4
+ const y = config.dimensions[d].yscale(row[d]);
5
+ return [x, y];
6
+ });
7
+
8
+ export default computeRealCentroids;
@@ -0,0 +1,101 @@
1
+ import { select } from 'd3-selection';
2
+
3
+ import dimensionLabels from '../util/dimensionLabels';
4
+ import flipAxisAndUpdatePCP from '../util/flipAxisAndUpdatePCP';
5
+ import rotateLabels from '../util/rotateLabels';
6
+
7
+ import w from '../util/width';
8
+ import h from '../util/height';
9
+
10
+ /**
11
+ * Create static SVG axes with dimension names, ticks, and labels.
12
+ *
13
+ * @param config
14
+ * @param pc
15
+ * @param xscale
16
+ * @param flags
17
+ * @param axis
18
+ * @returns {Function}
19
+ */
20
+ const createAxes = (config, pc, xscale, flags, axis) =>
21
+ function() {
22
+ if (pc.g() !== undefined) {
23
+ pc.removeAxes();
24
+ }
25
+ // Add a group element for each dimension.
26
+ pc._g = pc.svg
27
+ .selectAll('.dimension')
28
+ .data(pc.getOrderedDimensionKeys(), function(d) {
29
+ return d;
30
+ })
31
+ .enter()
32
+ .append('svg:g')
33
+ .attr('class', 'dimension')
34
+ .attr('transform', function(d) {
35
+ return 'translate(' + xscale(d) + ')';
36
+ });
37
+ // Add an axis and title.
38
+ pc._g
39
+ .append('svg:g')
40
+ .attr('class', 'axis')
41
+ .attr('transform', 'translate(0,0)')
42
+ .each(function(d) {
43
+ let axisElement = select(this).call(
44
+ pc.applyAxisConfig(axis, config.dimensions[d])
45
+ );
46
+
47
+ axisElement
48
+ .selectAll('path')
49
+ .style('fill', 'none')
50
+ .style('stroke', '#222')
51
+ .style('shape-rendering', 'crispEdges');
52
+
53
+ axisElement
54
+ .selectAll('line')
55
+ .style('fill', 'none')
56
+ .style('stroke', '#222')
57
+ .style('shape-rendering', 'crispEdges');
58
+ })
59
+
60
+ .append('svg:text')
61
+ .attr('text-anchor', 'middle')
62
+ .attr('y', 0)
63
+ .attr(
64
+ 'transform',
65
+ 'translate(0,-5) rotate(' + config.dimensionTitleRotation + ')'
66
+ )
67
+ .attr('x', 0)
68
+ .attr('class', 'label')
69
+ .text(dimensionLabels(config))
70
+ .on('dblclick', flipAxisAndUpdatePCP(config, pc, axis))
71
+ .on('wheel', rotateLabels(config, pc));
72
+
73
+ if (config.nullValueSeparator === 'top') {
74
+ pc.svg
75
+ .append('line')
76
+ .attr('x1', 0)
77
+ .attr('y1', 1 + config.nullValueSeparatorPadding.top)
78
+ .attr('x2', w(config))
79
+ .attr('y2', 1 + config.nullValueSeparatorPadding.top)
80
+ .attr('stroke-width', 1)
81
+ .attr('stroke', '#777')
82
+ .attr('fill', 'none')
83
+ .attr('shape-rendering', 'crispEdges');
84
+ } else if (config.nullValueSeparator === 'bottom') {
85
+ pc.svg
86
+ .append('line')
87
+ .attr('x1', 0)
88
+ .attr('y1', h(config) + 1 - config.nullValueSeparatorPadding.bottom)
89
+ .attr('x2', w(config))
90
+ .attr('y2', h(config) + 1 - config.nullValueSeparatorPadding.bottom)
91
+ .attr('stroke-width', 1)
92
+ .attr('stroke', '#777')
93
+ .attr('fill', 'none')
94
+ .attr('shape-rendering', 'crispEdges');
95
+ }
96
+
97
+ flags.axes = true;
98
+ return this;
99
+ };
100
+
101
+ export default createAxes;
@@ -0,0 +1,12 @@
1
+ import toTypeCoerceNumbers from './toTypeCoerceNumbers';
2
+
3
+ // attempt to determine types of each dimension based on first row of data
4
+ const detectDimensionTypes = data =>
5
+ Object.keys(data[0]).reduce((acc, cur) => {
6
+ const key = isNaN(Number(cur)) ? cur : parseInt(cur);
7
+ acc[key] = toTypeCoerceNumbers(data[0][cur]);
8
+
9
+ return acc;
10
+ }, {});
11
+
12
+ export default detectDimensionTypes;