@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,14 @@
1
+ import onDragEnd from './onDragEnd';
2
+ import removeStrum from './removeStrum';
3
+
4
+ const brushReset = (brushGroup, state, config, pc, events) => () => {
5
+ const ids = Object.getOwnPropertyNames(state.arcs).filter(d => !isNaN(d));
6
+
7
+ ids.forEach(d => {
8
+ state.arcs.active = d;
9
+ removeStrum(state, pc);
10
+ });
11
+ onDragEnd(brushGroup, state, config, pc, events)();
12
+ };
13
+
14
+ export default brushReset;
@@ -0,0 +1,19 @@
1
+ import uninstall from './uninstall';
2
+ import install from './install';
3
+ import selected from './selected';
4
+
5
+ const installAngularBrush = (brushGroup, config, pc, events, xscale) => {
6
+ const state = {
7
+ arcs: {},
8
+ strumRect: {},
9
+ };
10
+
11
+ brushGroup.modes['angular'] = {
12
+ install: install(brushGroup, state, config, pc, events, xscale),
13
+ uninstall: uninstall(state, pc),
14
+ selected: selected(brushGroup, state, config),
15
+ brushState: () => state.arcs,
16
+ };
17
+ };
18
+
19
+ export default installAngularBrush;
@@ -0,0 +1,141 @@
1
+ import { drag } from 'd3-drag';
2
+ import onDragEnd from './onDragEnd';
3
+ import onDrag from './onDrag';
4
+ import onDragStart from './onDragStart';
5
+ import removeStrum from './removeStrum';
6
+ import brushReset from './brushReset';
7
+ import w from '../../util/width';
8
+ import h from '../../util/height';
9
+
10
+ import hypothenuse from './util/hypothenuse';
11
+ import consecutive from '../consecutive';
12
+
13
+ // returns angles in [-PI/2, PI/2]
14
+ const angle = (p1, p2) => {
15
+ const a = p1[0] - p2[0],
16
+ b = p1[1] - p2[1],
17
+ c = hypothenuse(a, b);
18
+
19
+ return Math.asin(b / c);
20
+ };
21
+
22
+ const endAngle = state => id => {
23
+ const arc = state.arcs[id];
24
+ if (arc === undefined) {
25
+ return undefined;
26
+ }
27
+ let sAngle = angle(arc.p1, arc.p2),
28
+ uAngle = -sAngle + Math.PI / 2;
29
+
30
+ if (arc.p1[0] > arc.p2[0]) {
31
+ uAngle = 2 * Math.PI - uAngle;
32
+ }
33
+
34
+ return uAngle;
35
+ };
36
+
37
+ const startAngle = state => id => {
38
+ const arc = state.arcs[id];
39
+ if (arc === undefined) {
40
+ return undefined;
41
+ }
42
+
43
+ let sAngle = angle(arc.p1, arc.p3),
44
+ uAngle = -sAngle + Math.PI / 2;
45
+
46
+ if (arc.p1[0] > arc.p3[0]) {
47
+ uAngle = 2 * Math.PI - uAngle;
48
+ }
49
+
50
+ return uAngle;
51
+ };
52
+
53
+ const length = state => id => {
54
+ const arc = state.arcs[id];
55
+
56
+ if (arc === undefined) {
57
+ return undefined;
58
+ }
59
+
60
+ const a = arc.p1[0] - arc.p2[0],
61
+ b = arc.p1[1] - arc.p2[1];
62
+
63
+ return hypothenuse(a, b);
64
+ };
65
+
66
+ const install = (brushGroup, state, config, pc, events, xscale) => () => {
67
+ if (!pc.g()) {
68
+ pc.createAxes();
69
+ }
70
+
71
+ const _drag = drag();
72
+
73
+ // Map of current arcs. arcs are stored per segment of the PC. A segment,
74
+ // being the area between two axes. The left most area is indexed at 0.
75
+ state.arcs.active = undefined;
76
+ // Returns the width of the PC segment where currently a arc is being
77
+ // placed. NOTE: even though they are evenly spaced in our current
78
+ // implementation, we keep for when non-even spaced segments are supported as
79
+ // well.
80
+ state.arcs.width = id => {
81
+ const arc = state.arcs[id];
82
+ return arc === undefined ? undefined : arc.maxX - arc.minX;
83
+ };
84
+
85
+ // returns angles in [0, 2 * PI]
86
+ state.arcs.endAngle = endAngle(state);
87
+ state.arcs.startAngle = startAngle(state);
88
+ state.arcs.length = length(state);
89
+
90
+ pc.on('axesreorder.arcs', () => {
91
+ const ids = Object.getOwnPropertyNames(arcs).filter(d => !isNaN(d));
92
+
93
+ if (ids.length > 0) {
94
+ // We have some arcs, which might need to be removed.
95
+ ids.forEach(d => {
96
+ const dims = arcs[d].dims;
97
+ state.arcs.active = d;
98
+ // If the two dimensions of the current arc are not next to each other
99
+ // any more, than we'll need to remove the arc. Otherwise we keep it.
100
+ if (!consecutive(dims)(dims.left, dims.right)) {
101
+ removeStrum(state, pc);
102
+ }
103
+ });
104
+ onDragEnd(brushGroup, state, config, pc, events)();
105
+ }
106
+ });
107
+
108
+ // Add a new svg group in which we draw the arcs.
109
+ pc.selection
110
+ .select('svg')
111
+ .append('g')
112
+ .attr('id', 'arcs')
113
+ .attr(
114
+ 'transform',
115
+ 'translate(' + config.margin.left + ',' + config.margin.top + ')'
116
+ );
117
+
118
+ // Install the required brushReset function
119
+ pc.brushReset = brushReset(brushGroup, state, config, pc, events);
120
+
121
+ _drag
122
+ .on('start', onDragStart(state, config, pc, xscale))
123
+ .on('drag', onDrag(brushGroup, state, config, pc, events))
124
+ .on('end', onDragEnd(brushGroup, state, config, pc, events));
125
+
126
+ // NOTE: The styling needs to be done here and not in the css. This is because
127
+ // for 1D brushing, the canvas layers should not listen to
128
+ // pointer-events._.
129
+ state.strumRect = pc.selection
130
+ .select('svg')
131
+ .insert('rect', 'g#arcs')
132
+ .attr('id', 'arc-events')
133
+ .attr('x', config.margin.left)
134
+ .attr('y', config.margin.top)
135
+ .attr('width', w(config))
136
+ .attr('height', h(config) + 2)
137
+ .style('opacity', 0)
138
+ .call(_drag);
139
+ };
140
+
141
+ export default install;
@@ -0,0 +1,109 @@
1
+ import { select } from 'd3-selection';
2
+ import { drag } from 'd3-drag';
3
+ import onDragEnd from './onDragEnd';
4
+
5
+ const drawStrum = (brushGroup, state, config, pc, events, arc, activePoint) => {
6
+ const svg = pc.selection.select('svg').select('g#arcs'),
7
+ id = arc.dims.i,
8
+ points = [arc.p2, arc.p3],
9
+ _line = svg
10
+ .selectAll('line#arc-' + id)
11
+ .data([{ p1: arc.p1, p2: arc.p2 }, { p1: arc.p1, p2: arc.p3 }]),
12
+ circles = svg.selectAll('circle#arc-' + id).data(points),
13
+ _drag = drag(),
14
+ _path = svg.selectAll('path#arc-' + id).data([arc]);
15
+
16
+ _path
17
+ .enter()
18
+ .append('path')
19
+ .attr('id', 'arc-' + id)
20
+ .attr('class', 'arc')
21
+ .style('fill', 'orange')
22
+ .style('opacity', 0.5);
23
+
24
+ _path
25
+ .attr('d', arc.arc)
26
+ .attr('transform', 'translate(' + arc.p1[0] + ',' + arc.p1[1] + ')');
27
+
28
+ _line
29
+ .enter()
30
+ .append('line')
31
+ .attr('id', 'arc-' + id)
32
+ .attr('class', 'arc');
33
+
34
+ _line
35
+ .attr('x1', d => d.p1[0])
36
+ .attr('y1', d => d.p1[1])
37
+ .attr('x2', d => d.p2[0])
38
+ .attr('y2', d => d.p2[1])
39
+ .attr('stroke', 'black')
40
+ .attr('stroke-width', 2);
41
+
42
+ _drag
43
+ .on('drag', (event, d) => {
44
+ const i = points.indexOf(d) + 2;
45
+
46
+ arc['p' + i][0] = Math.min(Math.max(arc.minX + 1, event.x), arc.maxX);
47
+ arc['p' + i][1] = Math.min(Math.max(arc.minY, event.y), arc.maxY);
48
+
49
+ const angle =
50
+ i === 3 ? state.arcs.startAngle(id) : state.arcs.endAngle(id);
51
+
52
+ if (
53
+ (arc.startAngle < Math.PI &&
54
+ arc.endAngle < Math.PI &&
55
+ angle < Math.PI) ||
56
+ (arc.startAngle >= Math.PI &&
57
+ arc.endAngle >= Math.PI &&
58
+ angle >= Math.PI)
59
+ ) {
60
+ if (i === 2) {
61
+ arc.endAngle = angle;
62
+ arc.arc.endAngle(angle);
63
+ } else if (i === 3) {
64
+ arc.startAngle = angle;
65
+ arc.arc.startAngle(angle);
66
+ }
67
+ }
68
+
69
+ drawStrum(brushGroup, state, config, pc, events, arc, i - 2);
70
+ })
71
+ .on('end', onDragEnd(brushGroup, state, config, pc, events));
72
+
73
+ circles
74
+ .enter()
75
+ .append('circle')
76
+ .attr('id', 'arc-' + id)
77
+ .attr('class', 'arc');
78
+
79
+ circles
80
+ .attr('cx', d => d[0])
81
+ .attr('cy', d => d[1])
82
+ .attr('r', 5)
83
+ .style(
84
+ 'opacity',
85
+ (d, i) => (activePoint !== undefined && i === activePoint ? 0.8 : 0)
86
+ )
87
+ .on('mouseover', function() {
88
+ select(this).style('opacity', 0.8);
89
+ })
90
+ .on('mouseout', function() {
91
+ select(this).style('opacity', 0);
92
+ })
93
+ .call(_drag);
94
+ };
95
+
96
+ const onDrag = (brushGroup, state, config, pc, events) => (event) => {
97
+ const arc = state.arcs[state.arcs.active];
98
+
99
+ // Make sure that the point is within the bounds
100
+ arc.p2[0] = Math.min(
101
+ Math.max(arc.minX + 1, event.x - config.margin.left),
102
+ arc.maxX
103
+ );
104
+ arc.p2[1] = Math.min(Math.max(arc.minY, event.y - config.margin.top), arc.maxY);
105
+ arc.p3 = arc.p2.slice();
106
+ drawStrum(brushGroup, state, config, pc, events, arc, 1);
107
+ };
108
+
109
+ export default onDrag;
@@ -0,0 +1,30 @@
1
+ import selected from './selected';
2
+ import removeStrum from './removeStrum';
3
+
4
+ const onDragEnd = (brushGroup, state, config, pc, events) => () => {
5
+ const arc = state.arcs[state.arcs.active];
6
+
7
+ // Okay, somewhat unexpected, but not totally unsurprising, a mousclick is
8
+ // considered a drag without move. So we have to deal with that case
9
+ if (arc && arc.p1[0] === arc.p2[0] && arc.p1[1] === arc.p2[1]) {
10
+ removeStrum(state, pc);
11
+ }
12
+
13
+ if (arc) {
14
+ const angle = state.arcs.startAngle(state.arcs.active);
15
+
16
+ arc.startAngle = angle;
17
+ arc.endAngle = angle;
18
+ arc.arc
19
+ .outerRadius(state.arcs.length(state.arcs.active))
20
+ .startAngle(angle)
21
+ .endAngle(angle);
22
+ }
23
+
24
+ state.arcs.active = undefined;
25
+ config.brushed = selected(brushGroup, state, config);
26
+ pc.renderBrushed();
27
+ events.call('brushend', pc, config.brushed);
28
+ };
29
+
30
+ export default onDragEnd;
@@ -0,0 +1,38 @@
1
+ import { pointer } from 'd3-selection';
2
+ import { arc as d3Arc } from 'd3-shape';
3
+ import dimensionsForPoint from '../dimensionsForPoint';
4
+ import h from '../../util/height';
5
+
6
+ // First we need to determine between which two axes the arc was started.
7
+ // This will determine the freedom of movement, because a arc can
8
+ // logically only happen between two axes, so no movement outside these axes
9
+ // should be allowed.
10
+ const onDragStart = (state, config, pc, xscale) => (event) => {
11
+ const p = pointer(event, state.strumRect.node());
12
+
13
+ p[0] = p[0] - config.margin.left;
14
+ p[1] = p[1] - config.margin.top;
15
+
16
+ const dims = dimensionsForPoint(config, pc, xscale, p);
17
+ const arc = {
18
+ p1: p,
19
+ dims: dims,
20
+ minX: xscale(dims.left),
21
+ maxX: xscale(dims.right),
22
+ minY: 0,
23
+ maxY: h(config),
24
+ startAngle: undefined,
25
+ endAngle: undefined,
26
+ arc: d3Arc().innerRadius(0),
27
+ };
28
+
29
+ // Make sure that the point is within the bounds
30
+ arc.p1[0] = Math.min(Math.max(arc.minX, p[0]), arc.maxX);
31
+ arc.p2 = arc.p1.slice();
32
+ arc.p3 = arc.p1.slice();
33
+
34
+ state.arcs[dims.i] = arc;
35
+ state.arcs.active = dims.i;
36
+ };
37
+
38
+ export default onDragStart;
@@ -0,0 +1,12 @@
1
+ const removeStrum = (state, pc) => {
2
+ const arc = state.arcs[state.arcs.active],
3
+ svg = pc.selection.select('svg').select('g#arcs');
4
+
5
+ delete state.arcs[state.arcs.active];
6
+ state.arcs.active = undefined;
7
+ svg.selectAll('line#arc-' + arc.dims.i).remove();
8
+ svg.selectAll('circle#arc-' + arc.dims.i).remove();
9
+ svg.selectAll('path#arc-' + arc.dims.i).remove();
10
+ };
11
+
12
+ export default removeStrum;
@@ -0,0 +1,63 @@
1
+ import hypothenuse from './util/hypothenuse';
2
+
3
+ // [0, 2*PI] -> [-PI/2, PI/2]
4
+ const signedAngle = angle =>
5
+ angle > Math.PI ? 1.5 * Math.PI - angle : 0.5 * Math.PI - angle;
6
+
7
+ /**
8
+ * angles are stored in radians from in [0, 2*PI], where 0 in 12 o'clock.
9
+ * However, one can only select lines from 0 to PI, so we compute the
10
+ * 'signed' angle, where 0 is the horizontal line (3 o'clock), and +/- PI/2
11
+ * are 12 and 6 o'clock respectively.
12
+ */
13
+ const containmentTest = arc => a => {
14
+ let startAngle = signedAngle(arc.startAngle);
15
+ let endAngle = signedAngle(arc.endAngle);
16
+
17
+ if (startAngle > endAngle) {
18
+ const tmp = startAngle;
19
+ startAngle = endAngle;
20
+ endAngle = tmp;
21
+ }
22
+
23
+ // test if segment angle is contained in angle interval
24
+ return a >= startAngle && a <= endAngle;
25
+ };
26
+
27
+ const crossesStrum = (state, config) => (d, id) => {
28
+ const arc = state.arcs[id],
29
+ test = containmentTest(arc),
30
+ d1 = arc.dims.left,
31
+ d2 = arc.dims.right,
32
+ y1 = config.dimensions[d1].yscale,
33
+ y2 = config.dimensions[d2].yscale,
34
+ a = state.arcs.width(id),
35
+ b = y1(d[d1]) - y2(d[d2]),
36
+ c = hypothenuse(a, b),
37
+ angle = Math.asin(b / c); // rad in [-PI/2, PI/2]
38
+ return test(angle);
39
+ };
40
+
41
+ const selected = (brushGroup, state, config) => {
42
+ const ids = Object.getOwnPropertyNames(state.arcs).filter(d => !isNaN(d));
43
+ const brushed = config.data;
44
+
45
+ if (ids.length === 0) {
46
+ return brushed;
47
+ }
48
+
49
+ const crossTest = crossesStrum(state, config);
50
+
51
+ return brushed.filter(d => {
52
+ switch (brushGroup.predicate) {
53
+ case 'AND':
54
+ return ids.every(id => crossTest(d, id));
55
+ case 'OR':
56
+ return ids.some(id => crossTest(d, id));
57
+ default:
58
+ throw new Error('Unknown brush predicate ' + config.brushPredicate);
59
+ }
60
+ });
61
+ };
62
+
63
+ export default selected;
@@ -0,0 +1,17 @@
1
+ const uninstall = (state, pc) => () => {
2
+ pc.selection
3
+ .select('svg')
4
+ .select('g#arcs')
5
+ .remove();
6
+ pc.selection
7
+ .select('svg')
8
+ .select('rect#arc-events')
9
+ .remove();
10
+ pc.on('axesreorder.arcs', undefined);
11
+
12
+ delete pc.brushReset;
13
+
14
+ state.strumRect = undefined;
15
+ };
16
+
17
+ export default uninstall;
@@ -0,0 +1,3 @@
1
+ const hypothenuse = (a, b) => Math.sqrt(a * a + b * b);
2
+
3
+ export default hypothenuse;
@@ -0,0 +1,11 @@
1
+ // Checks if the first dimension is directly left of the second dimension.
2
+ const consecutive = dimensions => (first, second) => {
3
+ const keys = Object.keys(dimensions);
4
+
5
+ return keys.some(
6
+ (d, i) =>
7
+ d === first ? i + i < keys.length && dimensions[i + 1] === second : false
8
+ );
9
+ };
10
+
11
+ export default consecutive;
@@ -0,0 +1,32 @@
1
+ const dimensionsForPoint = (config, pc, xscale, p) => {
2
+ const dims = { i: -1, left: undefined, right: undefined };
3
+ Object.keys(config.dimensions).some((dim, i) => {
4
+ if (xscale(dim) < p[0]) {
5
+ dims.i = i;
6
+ dims.left = dim;
7
+ dims.right = Object.keys(config.dimensions)[
8
+ pc.getOrderedDimensionKeys().indexOf(dim) + 1
9
+ ];
10
+ return false;
11
+ }
12
+ return true;
13
+ });
14
+
15
+ if (dims.left === undefined) {
16
+ // Event on the left side of the first axis.
17
+ dims.i = 0;
18
+ dims.left = pc.getOrderedDimensionKeys()[0];
19
+ dims.right = pc.getOrderedDimensionKeys()[1];
20
+ } else if (dims.right === undefined) {
21
+ // Event on the right side of the last axis
22
+ dims.i = Object.keys(config.dimensions).length - 1;
23
+ dims.right = dims.left;
24
+ dims.left = pc.getOrderedDimensionKeys()[
25
+ Object.keys(config.dimensions).length - 2
26
+ ];
27
+ }
28
+
29
+ return dims;
30
+ };
31
+
32
+ export default dimensionsForPoint;
@@ -0,0 +1,24 @@
1
+ const invertCategorical = (selection, scale) => {
2
+ if (selection.length === 0) {
3
+ return [];
4
+ }
5
+ const domain = scale.domain();
6
+ const range = scale.range();
7
+ const found = [];
8
+ range.forEach((d, i) => {
9
+ if (d >= selection[0] && d <= selection[1]) {
10
+ found.push(domain[i]);
11
+ }
12
+ });
13
+ return found;
14
+ };
15
+
16
+ const invertByScale = (selection, scale) => {
17
+ if (scale === null) return [];
18
+ return typeof scale.invert === 'undefined'
19
+ ? invertCategorical(selection, scale)
20
+ : selection.map(d => scale.invert(d));
21
+ };
22
+
23
+ export default invertByScale;
24
+ export { invertByScale };
@@ -0,0 +1,14 @@
1
+ import onDragEnd from './onDragEnd';
2
+ import removeStrum from './removeStrum';
3
+
4
+ const brushReset = (brushGroup, state, config, pc, events) => () => {
5
+ const ids = Object.getOwnPropertyNames(state.strums).filter(d => !isNaN(d));
6
+
7
+ ids.forEach(d => {
8
+ state.strums.active = d;
9
+ removeStrum(state, pc);
10
+ });
11
+ onDragEnd(brushGroup, state, config, pc, events)();
12
+ };
13
+
14
+ export default brushReset;
@@ -0,0 +1,19 @@
1
+ import uninstall from './uninstall';
2
+ import install from './install';
3
+ import selected from './selected';
4
+
5
+ const install2DStrums = (brushGroup, config, pc, events, xscale) => {
6
+ const state = {
7
+ strums: {},
8
+ strumRect: {},
9
+ };
10
+
11
+ brushGroup.modes['2D-strums'] = {
12
+ install: install(brushGroup, state, config, pc, events, xscale),
13
+ uninstall: uninstall(state, pc),
14
+ selected: selected(brushGroup, state, config),
15
+ brushState: () => state.strums,
16
+ };
17
+ };
18
+
19
+ export default install2DStrums;
@@ -0,0 +1,81 @@
1
+ import { drag } from 'd3-drag';
2
+ import onDragEnd from './onDragEnd';
3
+ import onDrag from './onDrag';
4
+ import onDragStart from './onDragStart';
5
+ import removeStrum from './removeStrum';
6
+ import brushReset from './brushReset';
7
+ import w from '../../util/width';
8
+ import h from '../../util/height';
9
+ import consecutive from '../consecutive';
10
+
11
+ const install = (brushGroup, state, config, pc, events, xscale) => () => {
12
+ if (pc.g() === undefined || pc.g() === null) {
13
+ pc.createAxes();
14
+ }
15
+
16
+ const _drag = drag();
17
+
18
+ // Map of current strums. Strums are stored per segment of the PC. A segment,
19
+ // being the area between two axes. The left most area is indexed at 0.
20
+ state.strums.active = undefined;
21
+ // Returns the width of the PC segment where currently a strum is being
22
+ // placed. NOTE: even though they are evenly spaced in our current
23
+ // implementation, we keep for when non-even spaced segments are supported as
24
+ // well.
25
+ state.strums.width = id =>
26
+ state.strums[id] === undefined
27
+ ? undefined
28
+ : state.strums[id].maxX - state.strums[id].minX;
29
+
30
+ pc.on('axesreorder.strums', () => {
31
+ const ids = Object.getOwnPropertyNames(state.strums).filter(d => !isNaN(d));
32
+
33
+ if (ids.length > 0) {
34
+ // We have some strums, which might need to be removed.
35
+ ids.forEach(d => {
36
+ const dims = state.strums[d].dims;
37
+ state.strums.active = d;
38
+ // If the two dimensions of the current strum are not next to each other
39
+ // any more, than we'll need to remove the strum. Otherwise we keep it.
40
+ if (!consecutive(config.dimensions)(dims.left, dims.right)) {
41
+ removeStrum(state, pc);
42
+ }
43
+ });
44
+ onDragEnd(brushGroup, state, config, pc, events)();
45
+ }
46
+ });
47
+
48
+ // Add a new svg group in which we draw the strums.
49
+ pc.selection
50
+ .select('svg')
51
+ .append('g')
52
+ .attr('id', 'strums')
53
+ .attr(
54
+ 'transform',
55
+ 'translate(' + config.margin.left + ',' + config.margin.top + ')'
56
+ );
57
+
58
+ // Install the required brushReset function
59
+ pc.brushReset = brushReset(brushGroup, state, config, pc, events);
60
+
61
+ _drag
62
+ .on('start', onDragStart(state, config, pc, xscale))
63
+ .on('drag', onDrag(brushGroup, state, config, pc, events))
64
+ .on('end', onDragEnd(brushGroup, state, config, pc, events));
65
+
66
+ // NOTE: The styling needs to be done here and not in the css. This is because
67
+ // for 1D brushing, the canvas layers should not listen to
68
+ // pointer-events._.
69
+ state.strumRect = pc.selection
70
+ .select('svg')
71
+ .insert('rect', 'g#strums')
72
+ .attr('id', 'strum-events')
73
+ .attr('x', config.margin.left)
74
+ .attr('y', config.margin.top)
75
+ .attr('width', w(config))
76
+ .attr('height', h(config) + 2)
77
+ .style('opacity', 0)
78
+ .call(_drag);
79
+ };
80
+
81
+ export default install;