@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.
- package/LICENSE +21 -0
- package/README.md +160 -0
- package/dist/parcoords.css +61 -0
- package/dist/parcoords.esm.js +4132 -0
- package/dist/parcoords.esm.js.map +1 -0
- package/dist/parcoords.js +4130 -0
- package/dist/parcoords.js.map +1 -0
- package/dist/parcoords.standalone.js +11233 -0
- package/dist/parcoords.standalone.js.map +1 -0
- package/package.json +89 -0
- package/src/api/adjacentPairs.js +10 -0
- package/src/api/applyAxisConfig.js +35 -0
- package/src/api/applyDimensionDefaults.js +25 -0
- package/src/api/autoscale.js +133 -0
- package/src/api/axisDots.js +27 -0
- package/src/api/brushMode.js +50 -0
- package/src/api/brushReset.js +34 -0
- package/src/api/brushable.js +81 -0
- package/src/api/clear.js +21 -0
- package/src/api/commonScale.js +45 -0
- package/src/api/computeRealCentroids.js +8 -0
- package/src/api/createAxes.js +101 -0
- package/src/api/detectDimensionTypes.js +12 -0
- package/src/api/detectDimensions.js +7 -0
- package/src/api/flip.js +11 -0
- package/src/api/getOrderedDimensionKeys.js +8 -0
- package/src/api/highlight.js +26 -0
- package/src/api/init.js +53 -0
- package/src/api/interactive.js +7 -0
- package/src/api/intersection.js +16 -0
- package/src/api/mark.js +20 -0
- package/src/api/mergeParcoords.js +86 -0
- package/src/api/removeAxes.js +9 -0
- package/src/api/render.js +26 -0
- package/src/api/renderBrushed.js +39 -0
- package/src/api/renderDefault.js +27 -0
- package/src/api/renderMarked.js +34 -0
- package/src/api/reorder.js +37 -0
- package/src/api/reorderable.js +41 -0
- package/src/api/resize.js +36 -0
- package/src/api/scale.js +10 -0
- package/src/api/selected.js +209 -0
- package/src/api/shadows.js +9 -0
- package/src/api/sortDimensions.js +13 -0
- package/src/api/sortDimensionsByRowData.js +20 -0
- package/src/api/toString.js +11 -0
- package/src/api/toType.js +9 -0
- package/src/api/toTypeCoerceNumbers.js +7 -0
- package/src/api/unhighlight.js +12 -0
- package/src/api/unmark.js +12 -0
- package/src/api/updateAxes.js +103 -0
- package/src/bindEvents.js +60 -0
- package/src/brush/1d/brushExtents.js +73 -0
- package/src/brush/1d/brushFor.js +92 -0
- package/src/brush/1d/brushReset.js +36 -0
- package/src/brush/1d/index.js +20 -0
- package/src/brush/1d/install.js +41 -0
- package/src/brush/1d/selected.js +79 -0
- package/src/brush/1d/uninstall.js +12 -0
- package/src/brush/1d-multi/brushExtents.js +101 -0
- package/src/brush/1d-multi/brushFor.js +13 -0
- package/src/brush/1d-multi/brushReset.js +53 -0
- package/src/brush/1d-multi/drawBrushes.js +36 -0
- package/src/brush/1d-multi/index.js +20 -0
- package/src/brush/1d-multi/install.js +29 -0
- package/src/brush/1d-multi/newBrush.js +102 -0
- package/src/brush/1d-multi/selected.js +160 -0
- package/src/brush/1d-multi/uninstall.js +12 -0
- package/src/brush/angular/brushReset.js +14 -0
- package/src/brush/angular/index.js +19 -0
- package/src/brush/angular/install.js +141 -0
- package/src/brush/angular/onDrag.js +109 -0
- package/src/brush/angular/onDragEnd.js +30 -0
- package/src/brush/angular/onDragStart.js +38 -0
- package/src/brush/angular/removeStrum.js +12 -0
- package/src/brush/angular/selected.js +63 -0
- package/src/brush/angular/uninstall.js +17 -0
- package/src/brush/angular/util/hypothenuse.js +3 -0
- package/src/brush/consecutive.js +11 -0
- package/src/brush/dimensionsForPoint.js +32 -0
- package/src/brush/invertByScale.js +24 -0
- package/src/brush/strums/brushReset.js +14 -0
- package/src/brush/strums/index.js +19 -0
- package/src/brush/strums/install.js +81 -0
- package/src/brush/strums/onDrag.js +83 -0
- package/src/brush/strums/onDragEnd.js +20 -0
- package/src/brush/strums/onDragStart.js +33 -0
- package/src/brush/strums/removeStrum.js +10 -0
- package/src/brush/strums/selected.js +52 -0
- package/src/brush/strums/uninstall.js +16 -0
- package/src/index.js +226 -0
- package/src/parallel-coordinates.css +61 -0
- package/src/state/defaultConfig.js +36 -0
- package/src/state/index.js +86 -0
- package/src/state/sideEffects.js +89 -0
- package/src/util/colorPath.js +71 -0
- package/src/util/computeCentroids.js +44 -0
- package/src/util/computeClusterCentroids.js +33 -0
- package/src/util/computeControlPoints.js +38 -0
- package/src/util/dimensionLabels.js +11 -0
- package/src/util/flipAxisAndUpdatePCP.js +19 -0
- package/src/util/functor.js +3 -0
- package/src/util/getRange.js +24 -0
- package/src/util/getset.js +23 -0
- package/src/util/height.js +3 -0
- package/src/util/isBrushed.js +15 -0
- package/src/util/renderQueue.js +67 -0
- package/src/util/rotateLabels.js +18 -0
- package/src/util/width.js +3 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import brushExtents from './brushExtents';
|
|
2
|
+
import install from './install';
|
|
3
|
+
import selected from './selected';
|
|
4
|
+
import uninstall from './uninstall';
|
|
5
|
+
|
|
6
|
+
const install1DAxes = (brushGroup, config, pc, events) => {
|
|
7
|
+
const state = {
|
|
8
|
+
brushes: {},
|
|
9
|
+
brushNodes: {},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
brushGroup.modes['1D-axes'] = {
|
|
13
|
+
install: install(state, config, pc, events, brushGroup),
|
|
14
|
+
uninstall: uninstall(state, pc),
|
|
15
|
+
selected: selected(state, config, brushGroup),
|
|
16
|
+
brushState: brushExtents(state, config, pc),
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default install1DAxes;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { select } from 'd3-selection';
|
|
2
|
+
import brushExtents from './brushExtents';
|
|
3
|
+
import brushReset from './brushReset';
|
|
4
|
+
import brushFor from './brushFor';
|
|
5
|
+
|
|
6
|
+
const install = (state, config, pc, events, brushGroup) => () => {
|
|
7
|
+
if (!pc.g()) {
|
|
8
|
+
pc.createAxes();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Add and store a brush for each axis.
|
|
12
|
+
const brush = pc
|
|
13
|
+
.g()
|
|
14
|
+
.append('svg:g')
|
|
15
|
+
.attr('class', 'brush')
|
|
16
|
+
.each(function(d) {
|
|
17
|
+
select(this).call(
|
|
18
|
+
brushFor(state, config, pc, events, brushGroup)(d, select(this))
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
brush
|
|
22
|
+
.selectAll('rect')
|
|
23
|
+
.style('visibility', null)
|
|
24
|
+
.attr('x', -15)
|
|
25
|
+
.attr('width', 30);
|
|
26
|
+
|
|
27
|
+
brush.selectAll('rect.background').style('fill', 'transparent');
|
|
28
|
+
|
|
29
|
+
brush
|
|
30
|
+
.selectAll('rect.extent')
|
|
31
|
+
.style('fill', 'rgba(255,255,255,0.25)')
|
|
32
|
+
.style('stroke', 'rgba(0,0,0,0.6)');
|
|
33
|
+
|
|
34
|
+
brush.selectAll('.resize rect').style('fill', 'rgba(0,0,0,0.1)');
|
|
35
|
+
|
|
36
|
+
pc.brushExtents = brushExtents(state, config, pc);
|
|
37
|
+
pc.brushReset = brushReset(state, config, pc);
|
|
38
|
+
return pc;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default install;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { brushSelection } from 'd3-brush';
|
|
2
|
+
//https://github.com/d3/d3-brush/issues/10
|
|
3
|
+
|
|
4
|
+
// data within extents
|
|
5
|
+
const selected = (state, config, brushGroup) => () => {
|
|
6
|
+
const { brushNodes } = state;
|
|
7
|
+
const is_brushed = p =>
|
|
8
|
+
brushNodes[p] && brushSelection(brushNodes[p]) !== null;
|
|
9
|
+
|
|
10
|
+
const actives = Object.keys(config.dimensions).filter(is_brushed);
|
|
11
|
+
const extents = actives.map(p => {
|
|
12
|
+
const _brushRange = brushSelection(brushNodes[p]);
|
|
13
|
+
|
|
14
|
+
if (typeof config.dimensions[p].yscale.invert === 'function') {
|
|
15
|
+
return [
|
|
16
|
+
config.dimensions[p].yscale.invert(_brushRange[1]),
|
|
17
|
+
config.dimensions[p].yscale.invert(_brushRange[0]),
|
|
18
|
+
];
|
|
19
|
+
} else {
|
|
20
|
+
return _brushRange;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
// We don't want to return the full data set when there are no axes brushed.
|
|
24
|
+
// Actually, when there are no axes brushed, by definition, no items are
|
|
25
|
+
// selected. So, let's avoid the filtering and just return false.
|
|
26
|
+
//if (actives.length === 0) return false;
|
|
27
|
+
|
|
28
|
+
// Resolves broken examples for now. They expect to get the full dataset back from empty brushes
|
|
29
|
+
if (actives.length === 0) return config.data;
|
|
30
|
+
|
|
31
|
+
// test if within range
|
|
32
|
+
const within = {
|
|
33
|
+
date: (d, p, dimension) => {
|
|
34
|
+
if (typeof config.dimensions[p].yscale.bandwidth === 'function') {
|
|
35
|
+
// if it is ordinal
|
|
36
|
+
return (
|
|
37
|
+
extents[dimension][0] <= config.dimensions[p].yscale(d[p]) &&
|
|
38
|
+
config.dimensions[p].yscale(d[p]) <= extents[dimension][1]
|
|
39
|
+
);
|
|
40
|
+
} else {
|
|
41
|
+
return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1];
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
number: (d, p, dimension) => {
|
|
45
|
+
if (typeof config.dimensions[p].yscale.bandwidth === 'function') {
|
|
46
|
+
// if it is ordinal
|
|
47
|
+
return (
|
|
48
|
+
extents[dimension][0] <= config.dimensions[p].yscale(d[p]) &&
|
|
49
|
+
config.dimensions[p].yscale(d[p]) <= extents[dimension][1]
|
|
50
|
+
);
|
|
51
|
+
} else {
|
|
52
|
+
return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1];
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
string: (d, p, dimension) => {
|
|
56
|
+
return (
|
|
57
|
+
extents[dimension][0] <= config.dimensions[p].yscale(d[p]) &&
|
|
58
|
+
config.dimensions[p].yscale(d[p]) <= extents[dimension][1]
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return config.data.filter(d => {
|
|
64
|
+
switch (brushGroup.predicate) {
|
|
65
|
+
case 'AND':
|
|
66
|
+
return actives.every(function(p, dimension) {
|
|
67
|
+
return within[config.dimensions[p].type](d, p, dimension);
|
|
68
|
+
});
|
|
69
|
+
case 'OR':
|
|
70
|
+
return actives.some(function(p, dimension) {
|
|
71
|
+
return within[config.dimensions[p].type](d, p, dimension);
|
|
72
|
+
});
|
|
73
|
+
default:
|
|
74
|
+
throw new Error('Unknown brush predicate ' + config.brushPredicate);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default selected;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { select } from 'd3-selection';
|
|
2
|
+
import { brushSelection } from 'd3-brush';
|
|
3
|
+
import newBrush from './newBrush';
|
|
4
|
+
import drawBrushes from './drawBrushes';
|
|
5
|
+
import invertByScale from '../invertByScale';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* extents are in format of [[2,6], [3,5]]
|
|
10
|
+
*
|
|
11
|
+
* * @param state
|
|
12
|
+
* @param config
|
|
13
|
+
* @param pc
|
|
14
|
+
* @returns {Function}
|
|
15
|
+
*/
|
|
16
|
+
const brushExtents = (state, config, pc, events, brushGroup) => extents => {
|
|
17
|
+
const { brushes } = state;
|
|
18
|
+
const hiddenAxes = pc.hideAxis();
|
|
19
|
+
|
|
20
|
+
if (typeof extents === 'undefined') {
|
|
21
|
+
return Object.keys(config.dimensions)
|
|
22
|
+
.filter(d => !hiddenAxes.includes(d))
|
|
23
|
+
.reduce((acc, cur, pos) => {
|
|
24
|
+
const axisBrushes = brushes[cur];
|
|
25
|
+
|
|
26
|
+
if (axisBrushes === undefined || axisBrushes === null) {
|
|
27
|
+
acc[cur] = [];
|
|
28
|
+
} else {
|
|
29
|
+
acc[cur] = axisBrushes.reduce((d, p, i) => {
|
|
30
|
+
const raw = brushSelection(
|
|
31
|
+
document.getElementById('brush-' + pos + '-' + i)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (raw) {
|
|
35
|
+
const yScale = config.dimensions[cur].yscale;
|
|
36
|
+
const scaled = invertByScale(raw, yScale);
|
|
37
|
+
|
|
38
|
+
d.push({
|
|
39
|
+
extent: p.brush.extent(),
|
|
40
|
+
selection: {
|
|
41
|
+
raw,
|
|
42
|
+
scaled,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return d;
|
|
47
|
+
}, []);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return acc;
|
|
51
|
+
}, {});
|
|
52
|
+
} else {
|
|
53
|
+
// //first get all the brush selections
|
|
54
|
+
// loop over each dimension and update appropriately (if it was passed in through extents)
|
|
55
|
+
Object.keys(config.dimensions).forEach((d, pos) => {
|
|
56
|
+
if (extents[d] === undefined || extents[d] === null) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const dim = config.dimensions[d];
|
|
61
|
+
|
|
62
|
+
const yExtents = extents[d].map(e => e.map(dim.yscale));
|
|
63
|
+
|
|
64
|
+
const _bs = yExtents.map((e, j) => {
|
|
65
|
+
const _brush = newBrush(state, config, pc, events, brushGroup)(
|
|
66
|
+
d,
|
|
67
|
+
select('#brush-group-' + pos)
|
|
68
|
+
);
|
|
69
|
+
//update the extent
|
|
70
|
+
//sets the brushable extent to the specified array of points [[x0, y0], [x1, y1]]
|
|
71
|
+
_brush.extent([[-15, e[1]], [15, e[0]]]);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
id: j,
|
|
75
|
+
brush: _brush,
|
|
76
|
+
ext: e,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
brushes[d] = _bs;
|
|
81
|
+
|
|
82
|
+
drawBrushes(_bs, config, pc, d, select('#brush-group-' + pos));
|
|
83
|
+
|
|
84
|
+
//redraw the brush
|
|
85
|
+
//https://github.com/d3/d3-brush#brush_move
|
|
86
|
+
// For an x-brush, it must be defined as [x0, x1]; for a y-brush, it must be defined as [y0, y1].
|
|
87
|
+
_bs.forEach((f, k) => {
|
|
88
|
+
select('#brush-' + pos + '-' + k)
|
|
89
|
+
.call(f.brush)
|
|
90
|
+
.call(f.brush.move, f.ext.reverse());
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
//redraw the chart
|
|
95
|
+
pc.renderBrushed();
|
|
96
|
+
|
|
97
|
+
return pc;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default brushExtents;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import newBrush from './newBrush';
|
|
2
|
+
import drawBrushes from './drawBrushes';
|
|
3
|
+
|
|
4
|
+
const brushFor = (state, config, pc, events, brushGroup) => (
|
|
5
|
+
axis,
|
|
6
|
+
_selector
|
|
7
|
+
) => {
|
|
8
|
+
const { brushes } = state;
|
|
9
|
+
newBrush(state, config, pc, events, brushGroup)(axis, _selector);
|
|
10
|
+
drawBrushes(brushes[axis], config, pc, axis, _selector);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default brushFor;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { select } from 'd3-selection';
|
|
2
|
+
import { brushSelection } from 'd3-brush';
|
|
3
|
+
|
|
4
|
+
const brushReset = (state, config, pc) => dimension => {
|
|
5
|
+
const { brushes } = state;
|
|
6
|
+
|
|
7
|
+
if (dimension === undefined) {
|
|
8
|
+
if (pc.g() !== undefined && pc.g() !== null) {
|
|
9
|
+
Object.keys(config.dimensions).forEach((d, pos) => {
|
|
10
|
+
const axisBrush = brushes[d];
|
|
11
|
+
|
|
12
|
+
// hidden axes will be undefined
|
|
13
|
+
if (axisBrush) {
|
|
14
|
+
axisBrush.forEach((e, i) => {
|
|
15
|
+
const brush = document.getElementById('brush-' + pos + '-' + i);
|
|
16
|
+
if (brush && brushSelection(brush) !== null) {
|
|
17
|
+
pc.g()
|
|
18
|
+
.select('#brush-' + pos + '-' + i)
|
|
19
|
+
.call(e.brush.move, null);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
pc.renderBrushed();
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
if (pc.g() !== undefined && pc.g() !== null) {
|
|
29
|
+
const axisBrush = brushes[dimension];
|
|
30
|
+
const pos = Object.keys(config.dimensions).indexOf(dimension);
|
|
31
|
+
|
|
32
|
+
if (axisBrush) {
|
|
33
|
+
axisBrush.forEach((e, i) => {
|
|
34
|
+
const brush = document.getElementById('brush-' + pos + '-' + i);
|
|
35
|
+
if (brushSelection(brush) !== null) {
|
|
36
|
+
pc.g()
|
|
37
|
+
.select('#brush-' + pos + '-' + i)
|
|
38
|
+
.call(e.brush.move, null);
|
|
39
|
+
|
|
40
|
+
if (typeof e.event === 'function') {
|
|
41
|
+
e.event(select('#brush-' + pos + '-' + i));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pc.renderBrushed();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return this;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default brushReset;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { select } from 'd3-selection';
|
|
2
|
+
|
|
3
|
+
const drawBrushes = (brushes, config, pc, axis, selector) => {
|
|
4
|
+
const brushSelection = selector.selectAll('.brush').data(brushes, d => d.id);
|
|
5
|
+
|
|
6
|
+
brushSelection
|
|
7
|
+
.enter()
|
|
8
|
+
.insert('g', '.brush')
|
|
9
|
+
.attr('class', 'brush')
|
|
10
|
+
.attr('dimension', axis)
|
|
11
|
+
.attr(
|
|
12
|
+
'id',
|
|
13
|
+
b => 'brush-' + Object.keys(config.dimensions).indexOf(axis) + '-' + b.id
|
|
14
|
+
)
|
|
15
|
+
.each(function(brushObject) {
|
|
16
|
+
brushObject.brush(select(this));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
brushSelection.each(function(brushObject) {
|
|
20
|
+
select(this)
|
|
21
|
+
.attr('class', 'brush')
|
|
22
|
+
.selectAll('.overlay')
|
|
23
|
+
.style('pointer-events', function() {
|
|
24
|
+
const brush = brushObject.brush;
|
|
25
|
+
if (brushObject.id === brushes.length - 1 && brush !== undefined) {
|
|
26
|
+
return 'all';
|
|
27
|
+
} else {
|
|
28
|
+
return 'none';
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
brushSelection.exit().remove();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default drawBrushes;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import brushExtents from './brushExtents';
|
|
2
|
+
import install from './install';
|
|
3
|
+
import selected from './selected';
|
|
4
|
+
import uninstall from './uninstall';
|
|
5
|
+
|
|
6
|
+
const install1DMultiAxes = (brushGroup, config, pc, events) => {
|
|
7
|
+
const state = {
|
|
8
|
+
brushes: {},
|
|
9
|
+
brushNodes: {},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
brushGroup.modes['1D-axes-multi'] = {
|
|
13
|
+
install: install(state, config, pc, events, brushGroup),
|
|
14
|
+
uninstall: uninstall(state, pc),
|
|
15
|
+
selected: selected(state, config, brushGroup),
|
|
16
|
+
brushState: brushExtents(state, config, pc),
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default install1DMultiAxes;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { select } from 'd3-selection';
|
|
2
|
+
import brushExtents from './brushExtents';
|
|
3
|
+
import brushReset from './brushReset';
|
|
4
|
+
import brushFor from './brushFor';
|
|
5
|
+
|
|
6
|
+
const install = (state, config, pc, events, brushGroup) => () => {
|
|
7
|
+
if (!pc.g()) {
|
|
8
|
+
pc.createAxes();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const hiddenAxes = pc.hideAxis();
|
|
12
|
+
|
|
13
|
+
pc.g()
|
|
14
|
+
.append('svg:g')
|
|
15
|
+
.attr('id', (d, i) => 'brush-group-' + i)
|
|
16
|
+
.attr('class', 'brush-group')
|
|
17
|
+
.attr('dimension', d => d)
|
|
18
|
+
.each(function(d) {
|
|
19
|
+
if (!hiddenAxes.includes(d)) {
|
|
20
|
+
brushFor(state, config, pc, events, brushGroup)(d, select(this));
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
pc.brushExtents = brushExtents(state, config, pc, events, brushGroup);
|
|
25
|
+
pc.brushReset = brushReset(state, config, pc);
|
|
26
|
+
return pc;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default install;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { brushY, brushSelection } from 'd3-brush';
|
|
2
|
+
import { select } from 'd3-selection';
|
|
3
|
+
import drawBrushes from './drawBrushes';
|
|
4
|
+
import selected from './selected';
|
|
5
|
+
|
|
6
|
+
const brushUpdated = (config, pc, events) => newSelection => {
|
|
7
|
+
config.brushed = newSelection;
|
|
8
|
+
events.call('brush', pc, config.brushed);
|
|
9
|
+
pc.renderBrushed();
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const newBrush = (state, config, pc, events, brushGroup) => (
|
|
13
|
+
axis,
|
|
14
|
+
_selector
|
|
15
|
+
) => {
|
|
16
|
+
const { brushes, brushNodes } = state;
|
|
17
|
+
|
|
18
|
+
const brushRangeMax =
|
|
19
|
+
config.dimensions[axis].type === 'string'
|
|
20
|
+
? config.dimensions[axis].yscale.range()[
|
|
21
|
+
config.dimensions[axis].yscale.range().length - 1
|
|
22
|
+
]
|
|
23
|
+
: config.dimensions[axis].yscale.range()[0];
|
|
24
|
+
|
|
25
|
+
const brush = brushY().extent([[-15, 0], [15, brushRangeMax]]);
|
|
26
|
+
const id = brushes[axis] ? brushes[axis].length : 0;
|
|
27
|
+
const node =
|
|
28
|
+
'brush-' + Object.keys(config.dimensions).indexOf(axis) + '-' + id;
|
|
29
|
+
|
|
30
|
+
if (brushes[axis]) {
|
|
31
|
+
brushes[axis].push({
|
|
32
|
+
id,
|
|
33
|
+
brush,
|
|
34
|
+
node,
|
|
35
|
+
});
|
|
36
|
+
} else {
|
|
37
|
+
brushes[axis] = [{ id, brush, node }];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (brushNodes[axis]) {
|
|
41
|
+
brushNodes[axis].push({ id, node });
|
|
42
|
+
} else {
|
|
43
|
+
brushNodes[axis] = [{ id, node }];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
brush
|
|
47
|
+
.on('start', function(event) {
|
|
48
|
+
if (event.sourceEvent !== null) {
|
|
49
|
+
events.call('brushstart', pc, config.brushed);
|
|
50
|
+
if (typeof event.sourceEvent.stopPropagation === 'function') {
|
|
51
|
+
event.sourceEvent.stopPropagation();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
.on('brush', function() {
|
|
56
|
+
// record selections
|
|
57
|
+
brushUpdated(
|
|
58
|
+
config,
|
|
59
|
+
pc,
|
|
60
|
+
events
|
|
61
|
+
)(selected(state, config, pc, events, brushGroup));
|
|
62
|
+
})
|
|
63
|
+
.on('end', function(event) {
|
|
64
|
+
// Figure out if our latest brush has a selection
|
|
65
|
+
const lastBrushID = brushes[axis][brushes[axis].length - 1].id;
|
|
66
|
+
const lastBrush = document.getElementById(
|
|
67
|
+
'brush-' +
|
|
68
|
+
Object.keys(config.dimensions).indexOf(axis) +
|
|
69
|
+
'-' +
|
|
70
|
+
lastBrushID
|
|
71
|
+
);
|
|
72
|
+
const selection = brushSelection(lastBrush);
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
selection !== undefined &&
|
|
76
|
+
selection !== null &&
|
|
77
|
+
selection[0] !== selection[1]
|
|
78
|
+
) {
|
|
79
|
+
newBrush(state, config, pc, events, brushGroup)(axis, _selector);
|
|
80
|
+
|
|
81
|
+
drawBrushes(brushes[axis], config, pc, axis, _selector);
|
|
82
|
+
|
|
83
|
+
brushUpdated(config, pc, events)(
|
|
84
|
+
selected(state, config, pc, events, brushGroup)
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
if (
|
|
88
|
+
event.sourceEvent &&
|
|
89
|
+
event.sourceEvent.toString() === '[object MouseEvent]' &&
|
|
90
|
+
event.selection === null
|
|
91
|
+
) {
|
|
92
|
+
pc.brushReset(axis);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
events.call('brushend', pc, config.brushed);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return brush;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export default newBrush;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { brushSelection } from 'd3-brush';
|
|
2
|
+
|
|
3
|
+
// data within extents
|
|
4
|
+
const selected = (state, config, pc, events, brushGroup) => {
|
|
5
|
+
const { brushes } = state;
|
|
6
|
+
|
|
7
|
+
const is_brushed = (p, pos) => {
|
|
8
|
+
const axisBrushes = brushes[p];
|
|
9
|
+
|
|
10
|
+
for (let i = 0; i < axisBrushes.length; i++) {
|
|
11
|
+
const brush = document.getElementById('brush-' + pos + '-' + i);
|
|
12
|
+
|
|
13
|
+
if (brush && brushSelection(brush) !== null) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return false;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const actives = Object.keys(config.dimensions).filter(is_brushed);
|
|
22
|
+
const extents = actives.map(p => {
|
|
23
|
+
const axisBrushes = brushes[p];
|
|
24
|
+
|
|
25
|
+
return axisBrushes
|
|
26
|
+
.filter(d => !pc.hideAxis().includes(d))
|
|
27
|
+
.map((d, i) =>
|
|
28
|
+
brushSelection(
|
|
29
|
+
document.getElementById(
|
|
30
|
+
'brush-' + Object.keys(config.dimensions).indexOf(p) + '-' + i
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
.map((d, i) => {
|
|
35
|
+
if (d === null || d === undefined) {
|
|
36
|
+
return null;
|
|
37
|
+
} else if (typeof config.dimensions[p].yscale.invert === 'function') {
|
|
38
|
+
return [
|
|
39
|
+
config.dimensions[p].yscale.invert(d[1]),
|
|
40
|
+
config.dimensions[p].yscale.invert(d[0]),
|
|
41
|
+
];
|
|
42
|
+
} else {
|
|
43
|
+
return d;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// We don't want to return the full data set when there are no axes brushed.
|
|
49
|
+
// Actually, when there are no axes brushed, by definition, no items are
|
|
50
|
+
// selected. So, let's avoid the filtering and just return false.
|
|
51
|
+
//if (actives.length === 0) return false;
|
|
52
|
+
|
|
53
|
+
// Resolves broken examples for now. They expect to get the full dataset back from empty brushes
|
|
54
|
+
if (actives.length === 0) return config.data;
|
|
55
|
+
|
|
56
|
+
// test if within range
|
|
57
|
+
const within = {
|
|
58
|
+
date: (d, p, i) => {
|
|
59
|
+
const dimExt = extents[i];
|
|
60
|
+
|
|
61
|
+
if (typeof config.dimensions[p].yscale.bandwidth === 'function') {
|
|
62
|
+
// if it is ordinal
|
|
63
|
+
for (const e of dimExt) {
|
|
64
|
+
if (e === null || e === undefined) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
e[0] <= config.dimensions[p].yscale(d[p]) &&
|
|
70
|
+
config.dimensions[p].yscale(d[p]) <= e[1]
|
|
71
|
+
) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return false;
|
|
77
|
+
} else {
|
|
78
|
+
for (const e of dimExt) {
|
|
79
|
+
if (e === null || e === undefined) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (e[0] <= d[p] && d[p] <= e[1]) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
number: (d, p, i) => {
|
|
92
|
+
const dimExt = extents[i];
|
|
93
|
+
|
|
94
|
+
if (typeof config.dimensions[p].yscale.bandwidth === 'function') {
|
|
95
|
+
// if it is ordinal
|
|
96
|
+
for (const e of dimExt) {
|
|
97
|
+
if (e === null || e === undefined) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
e[0] <= config.dimensions[p].yscale(d[p]) &&
|
|
103
|
+
config.dimensions[p].yscale(d[p]) <= e[1]
|
|
104
|
+
) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return false;
|
|
110
|
+
} else {
|
|
111
|
+
for (const e of dimExt) {
|
|
112
|
+
if (e === null || e === undefined) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (e[0] <= d[p] && d[p] <= e[1]) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
string: (d, p, i) => {
|
|
125
|
+
const dimExt = extents[i];
|
|
126
|
+
|
|
127
|
+
for (const e of dimExt) {
|
|
128
|
+
if (e === null || e === undefined) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (
|
|
133
|
+
e[0] <= config.dimensions[p].yscale(d[p]) &&
|
|
134
|
+
config.dimensions[p].yscale(d[p]) <= e[1]
|
|
135
|
+
) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return false;
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return config.data.filter(d => {
|
|
145
|
+
switch (brushGroup.predicate) {
|
|
146
|
+
case 'AND':
|
|
147
|
+
return actives.every((p, i) =>
|
|
148
|
+
within[config.dimensions[p].type](d, p, i)
|
|
149
|
+
);
|
|
150
|
+
case 'OR':
|
|
151
|
+
return actives.some((p, i) =>
|
|
152
|
+
within[config.dimensions[p].type](d, p, i)
|
|
153
|
+
);
|
|
154
|
+
default:
|
|
155
|
+
throw new Error('Unknown brush predicate ' + config.brushPredicate);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export default selected;
|