@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,4130 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('requestanimationframe'), require('d3-selection'), require('d3-brush'), require('d3-drag'), require('d3-shape'), require('d3-scale'), require('d3-array'), require('d3-axis'), require('d3-dispatch')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['requestanimationframe', 'd3-selection', 'd3-brush', 'd3-drag', 'd3-shape', 'd3-scale', 'd3-array', 'd3-axis', 'd3-dispatch'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ParCoords = factory(null, global.d3Selection, global.d3Brush, global.d3Drag, global.d3Shape, global.d3Scale, global.d3Array, global.d3Axis, global.d3Dispatch));
|
|
5
|
+
})(this, (function (requestanimationframe, d3Selection, d3Brush, d3Drag, d3Shape, d3Scale, d3Array, d3Axis, d3Dispatch) { 'use strict';
|
|
6
|
+
|
|
7
|
+
var renderQueue = function renderQueue(func) {
|
|
8
|
+
var _queue = [],
|
|
9
|
+
// data to be rendered
|
|
10
|
+
_rate = 1000,
|
|
11
|
+
// number of calls per frame
|
|
12
|
+
_invalidate = function _invalidate() {},
|
|
13
|
+
// invalidate last render queue
|
|
14
|
+
_clear = function _clear() {}; // clearing function
|
|
15
|
+
|
|
16
|
+
var _rq = function rq(data) {
|
|
17
|
+
if (data) _rq.data(data);
|
|
18
|
+
_invalidate();
|
|
19
|
+
_clear();
|
|
20
|
+
_rq.render();
|
|
21
|
+
};
|
|
22
|
+
_rq.render = function () {
|
|
23
|
+
var valid = true;
|
|
24
|
+
_invalidate = _rq.invalidate = function () {
|
|
25
|
+
valid = false;
|
|
26
|
+
};
|
|
27
|
+
function doFrame() {
|
|
28
|
+
if (!valid) return true;
|
|
29
|
+
var chunk = _queue.splice(0, _rate);
|
|
30
|
+
chunk.map(func);
|
|
31
|
+
requestAnimationFrame(doFrame);
|
|
32
|
+
}
|
|
33
|
+
doFrame();
|
|
34
|
+
};
|
|
35
|
+
_rq.data = function (data) {
|
|
36
|
+
_invalidate();
|
|
37
|
+
_queue = data.slice(0); // creates a copy of the data
|
|
38
|
+
return _rq;
|
|
39
|
+
};
|
|
40
|
+
_rq.add = function (data) {
|
|
41
|
+
_queue = _queue.concat(data);
|
|
42
|
+
};
|
|
43
|
+
_rq.rate = function (value) {
|
|
44
|
+
if (!arguments.length) return _rate;
|
|
45
|
+
_rate = value;
|
|
46
|
+
return _rq;
|
|
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
|
+
_rq.invalidate = _invalidate;
|
|
62
|
+
return _rq;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
var w = function w(config) {
|
|
66
|
+
return config.width - config.margin.right - config.margin.left;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
var invertCategorical = function invertCategorical(selection, scale) {
|
|
70
|
+
if (selection.length === 0) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
var domain = scale.domain();
|
|
74
|
+
var range = scale.range();
|
|
75
|
+
var found = [];
|
|
76
|
+
range.forEach(function (d, i) {
|
|
77
|
+
if (d >= selection[0] && d <= selection[1]) {
|
|
78
|
+
found.push(domain[i]);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return found;
|
|
82
|
+
};
|
|
83
|
+
var invertByScale = function invertByScale(selection, scale) {
|
|
84
|
+
if (scale === null) return [];
|
|
85
|
+
return typeof scale.invert === 'undefined' ? invertCategorical(selection, scale) : selection.map(function (d) {
|
|
86
|
+
return scale.invert(d);
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
var brushExtents$1 = function brushExtents(state, config, pc) {
|
|
91
|
+
return function (extents) {
|
|
92
|
+
var brushes = state.brushes,
|
|
93
|
+
brushNodes = state.brushNodes;
|
|
94
|
+
if (typeof extents === 'undefined') {
|
|
95
|
+
return Object.keys(config.dimensions).reduce(function (acc, cur) {
|
|
96
|
+
var brush = brushes[cur];
|
|
97
|
+
//todo: brush check
|
|
98
|
+
if (brush !== undefined && d3Brush.brushSelection(brushNodes[cur]) !== null) {
|
|
99
|
+
var raw = d3Brush.brushSelection(brushNodes[cur]);
|
|
100
|
+
var yScale = config.dimensions[cur].yscale;
|
|
101
|
+
var scaled = invertByScale(raw, yScale);
|
|
102
|
+
acc[cur] = {
|
|
103
|
+
extent: brush.extent(),
|
|
104
|
+
selection: {
|
|
105
|
+
raw: raw,
|
|
106
|
+
scaled: scaled
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return acc;
|
|
111
|
+
}, {});
|
|
112
|
+
} else {
|
|
113
|
+
//first get all the brush selections
|
|
114
|
+
var brushSelections = {};
|
|
115
|
+
pc.g().selectAll('.brush').each(function (d) {
|
|
116
|
+
brushSelections[d] = d3Selection.select(this);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// loop over each dimension and update appropriately (if it was passed in through extents)
|
|
120
|
+
Object.keys(config.dimensions).forEach(function (d) {
|
|
121
|
+
if (extents[d] === undefined) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
var brush = brushes[d];
|
|
125
|
+
if (brush !== undefined) {
|
|
126
|
+
var dim = config.dimensions[d];
|
|
127
|
+
var yExtent = extents[d].map(dim.yscale);
|
|
128
|
+
|
|
129
|
+
//update the extent
|
|
130
|
+
//sets the brushable extent to the specified array of points [[x0, y0], [x1, y1]]
|
|
131
|
+
//we actually don't need this since we are using brush.move below
|
|
132
|
+
//extents set the limits of the brush which means a user will not be able
|
|
133
|
+
//to move or drag the brush beyond the limits set by brush.extent
|
|
134
|
+
//brush.extent([[-15, yExtent[1]], [15, yExtent[0]]]);
|
|
135
|
+
|
|
136
|
+
//redraw the brush
|
|
137
|
+
//https://github.com/d3/d3-brush#brush_move
|
|
138
|
+
// For an x-brush, it must be defined as [x0, x1]; for a y-brush, it must be defined as [y0, y1].
|
|
139
|
+
brushSelections[d].call(brush).call(brush.move, yExtent.reverse());
|
|
140
|
+
|
|
141
|
+
//fire some events
|
|
142
|
+
// brush.event(brushSelections[d]);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
//redraw the chart
|
|
147
|
+
pc.renderBrushed();
|
|
148
|
+
return pc;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
var _this$5 = undefined;
|
|
154
|
+
var brushReset$4 = function brushReset(state, config, pc) {
|
|
155
|
+
return function (dimension) {
|
|
156
|
+
var brushes = state.brushes;
|
|
157
|
+
if (dimension === undefined) {
|
|
158
|
+
config.brushed = false;
|
|
159
|
+
if (pc.g() !== undefined && pc.g() !== null) {
|
|
160
|
+
pc.g().selectAll('.brush').each(function (d) {
|
|
161
|
+
if (brushes[d] !== undefined) {
|
|
162
|
+
d3Selection.select(this).call(brushes[d].move, null);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
pc.renderBrushed();
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
config.brushed = false;
|
|
169
|
+
if (pc.g() !== undefined && pc.g() !== null) {
|
|
170
|
+
pc.g().selectAll('.brush').each(function (d) {
|
|
171
|
+
if (d !== dimension) return;
|
|
172
|
+
d3Selection.select(this).call(brushes[d].move, null);
|
|
173
|
+
if (typeof brushes[d].type === 'function') {
|
|
174
|
+
brushes[d].event(d3Selection.select(this));
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
pc.renderBrushed();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return _this$5;
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
//https://github.com/d3/d3-brush/issues/10
|
|
185
|
+
|
|
186
|
+
// data within extents
|
|
187
|
+
var selected$4 = function selected(state, config, brushGroup) {
|
|
188
|
+
return function () {
|
|
189
|
+
var brushNodes = state.brushNodes;
|
|
190
|
+
var is_brushed = function is_brushed(p) {
|
|
191
|
+
return brushNodes[p] && d3Brush.brushSelection(brushNodes[p]) !== null;
|
|
192
|
+
};
|
|
193
|
+
var actives = Object.keys(config.dimensions).filter(is_brushed);
|
|
194
|
+
var extents = actives.map(function (p) {
|
|
195
|
+
var _brushRange = d3Brush.brushSelection(brushNodes[p]);
|
|
196
|
+
if (typeof config.dimensions[p].yscale.invert === 'function') {
|
|
197
|
+
return [config.dimensions[p].yscale.invert(_brushRange[1]), config.dimensions[p].yscale.invert(_brushRange[0])];
|
|
198
|
+
} else {
|
|
199
|
+
return _brushRange;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
// We don't want to return the full data set when there are no axes brushed.
|
|
203
|
+
// Actually, when there are no axes brushed, by definition, no items are
|
|
204
|
+
// selected. So, let's avoid the filtering and just return false.
|
|
205
|
+
//if (actives.length === 0) return false;
|
|
206
|
+
|
|
207
|
+
// Resolves broken examples for now. They expect to get the full dataset back from empty brushes
|
|
208
|
+
if (actives.length === 0) return config.data;
|
|
209
|
+
|
|
210
|
+
// test if within range
|
|
211
|
+
var within = {
|
|
212
|
+
date: function date(d, p, dimension) {
|
|
213
|
+
if (typeof config.dimensions[p].yscale.bandwidth === 'function') {
|
|
214
|
+
// if it is ordinal
|
|
215
|
+
return extents[dimension][0] <= config.dimensions[p].yscale(d[p]) && config.dimensions[p].yscale(d[p]) <= extents[dimension][1];
|
|
216
|
+
} else {
|
|
217
|
+
return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1];
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
number: function number(d, p, dimension) {
|
|
221
|
+
if (typeof config.dimensions[p].yscale.bandwidth === 'function') {
|
|
222
|
+
// if it is ordinal
|
|
223
|
+
return extents[dimension][0] <= config.dimensions[p].yscale(d[p]) && config.dimensions[p].yscale(d[p]) <= extents[dimension][1];
|
|
224
|
+
} else {
|
|
225
|
+
return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1];
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
string: function string(d, p, dimension) {
|
|
229
|
+
return extents[dimension][0] <= config.dimensions[p].yscale(d[p]) && config.dimensions[p].yscale(d[p]) <= extents[dimension][1];
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
return config.data.filter(function (d) {
|
|
233
|
+
switch (brushGroup.predicate) {
|
|
234
|
+
case 'AND':
|
|
235
|
+
return actives.every(function (p, dimension) {
|
|
236
|
+
return within[config.dimensions[p].type](d, p, dimension);
|
|
237
|
+
});
|
|
238
|
+
case 'OR':
|
|
239
|
+
return actives.some(function (p, dimension) {
|
|
240
|
+
return within[config.dimensions[p].type](d, p, dimension);
|
|
241
|
+
});
|
|
242
|
+
default:
|
|
243
|
+
throw new Error('Unknown brush predicate ' + config.brushPredicate);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
var brushUpdated$1 = function brushUpdated(config, pc, events, args) {
|
|
250
|
+
return function (newSelection) {
|
|
251
|
+
config.brushed = newSelection;
|
|
252
|
+
events.call('brush', pc, config.brushed, args);
|
|
253
|
+
pc.renderBrushed();
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
var brushFor$1 = function brushFor(state, config, pc, events, brushGroup) {
|
|
257
|
+
return function (axis, _selector) {
|
|
258
|
+
// handle hidden axes which will not be a property of dimensions
|
|
259
|
+
if (!config.dimensions.hasOwnProperty(axis)) {
|
|
260
|
+
return function () {};
|
|
261
|
+
}
|
|
262
|
+
var brushRangeMax = config.dimensions[axis].type === 'string' ? config.dimensions[axis].yscale.range()[config.dimensions[axis].yscale.range().length - 1] : config.dimensions[axis].yscale.range()[0];
|
|
263
|
+
var _brush = d3Brush.brushY(_selector).extent([[-15, 0], [15, brushRangeMax]]);
|
|
264
|
+
var convertBrushArguments = function convertBrushArguments(args) {
|
|
265
|
+
var args_array = Array.prototype.slice.call(args);
|
|
266
|
+
var axis = args_array[0];
|
|
267
|
+
var raw = d3Brush.brushSelection(args_array[2][0]) || [];
|
|
268
|
+
|
|
269
|
+
// handle hidden axes which will not have a yscale
|
|
270
|
+
var yscale = null;
|
|
271
|
+
if (config.dimensions.hasOwnProperty(axis)) {
|
|
272
|
+
yscale = config.dimensions[axis].yscale;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ordinal scales do not have invert
|
|
276
|
+
var scaled = invertByScale(raw, yscale);
|
|
277
|
+
return {
|
|
278
|
+
axis: args_array[0],
|
|
279
|
+
node: args_array[2][0],
|
|
280
|
+
selection: {
|
|
281
|
+
raw: raw,
|
|
282
|
+
scaled: scaled
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
};
|
|
286
|
+
_brush.on('start', function (event) {
|
|
287
|
+
if (event.sourceEvent !== null) {
|
|
288
|
+
events.call('brushstart', pc, config.brushed, convertBrushArguments(arguments));
|
|
289
|
+
if (typeof event.sourceEvent.stopPropagation === 'function') {
|
|
290
|
+
event.sourceEvent.stopPropagation();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}).on('brush', function () {
|
|
294
|
+
brushUpdated$1(config, pc, events, convertBrushArguments(arguments))(selected$4(state, config, brushGroup)());
|
|
295
|
+
}).on('end', function () {
|
|
296
|
+
brushUpdated$1(config, pc, events)(selected$4(state, config, brushGroup)());
|
|
297
|
+
events.call('brushend', pc, config.brushed, convertBrushArguments(arguments));
|
|
298
|
+
});
|
|
299
|
+
state.brushes[axis] = _brush;
|
|
300
|
+
state.brushNodes[axis] = _selector.node();
|
|
301
|
+
return _brush;
|
|
302
|
+
};
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
var install$3 = function install(state, config, pc, events, brushGroup) {
|
|
306
|
+
return function () {
|
|
307
|
+
if (!pc.g()) {
|
|
308
|
+
pc.createAxes();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Add and store a brush for each axis.
|
|
312
|
+
var brush = pc.g().append('svg:g').attr('class', 'brush').each(function (d) {
|
|
313
|
+
d3Selection.select(this).call(brushFor$1(state, config, pc, events, brushGroup)(d, d3Selection.select(this)));
|
|
314
|
+
});
|
|
315
|
+
brush.selectAll('rect').style('visibility', null).attr('x', -15).attr('width', 30);
|
|
316
|
+
brush.selectAll('rect.background').style('fill', 'transparent');
|
|
317
|
+
brush.selectAll('rect.extent').style('fill', 'rgba(255,255,255,0.25)').style('stroke', 'rgba(0,0,0,0.6)');
|
|
318
|
+
brush.selectAll('.resize rect').style('fill', 'rgba(0,0,0,0.1)');
|
|
319
|
+
pc.brushExtents = brushExtents$1(state, config, pc);
|
|
320
|
+
pc.brushReset = brushReset$4(state, config, pc);
|
|
321
|
+
return pc;
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
var uninstall$3 = function uninstall(state, pc) {
|
|
326
|
+
return function () {
|
|
327
|
+
if (pc.g() !== undefined && pc.g() !== null) pc.g().selectAll('.brush').remove();
|
|
328
|
+
state.brushes = {};
|
|
329
|
+
delete pc.brushExtents;
|
|
330
|
+
delete pc.brushReset;
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
var install1DAxes = function install1DAxes(brushGroup, config, pc, events) {
|
|
335
|
+
var state = {
|
|
336
|
+
brushes: {},
|
|
337
|
+
brushNodes: {}
|
|
338
|
+
};
|
|
339
|
+
brushGroup.modes['1D-axes'] = {
|
|
340
|
+
install: install$3(state, config, pc, events, brushGroup),
|
|
341
|
+
uninstall: uninstall$3(state, pc),
|
|
342
|
+
selected: selected$4(state, config, brushGroup),
|
|
343
|
+
brushState: brushExtents$1(state, config, pc)
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
var drawBrushes = function drawBrushes(brushes, config, pc, axis, selector) {
|
|
348
|
+
var brushSelection = selector.selectAll('.brush').data(brushes, function (d) {
|
|
349
|
+
return d.id;
|
|
350
|
+
});
|
|
351
|
+
brushSelection.enter().insert('g', '.brush').attr('class', 'brush').attr('dimension', axis).attr('id', function (b) {
|
|
352
|
+
return 'brush-' + Object.keys(config.dimensions).indexOf(axis) + '-' + b.id;
|
|
353
|
+
}).each(function (brushObject) {
|
|
354
|
+
brushObject.brush(d3Selection.select(this));
|
|
355
|
+
});
|
|
356
|
+
brushSelection.each(function (brushObject) {
|
|
357
|
+
d3Selection.select(this).attr('class', 'brush').selectAll('.overlay').style('pointer-events', function () {
|
|
358
|
+
var brush = brushObject.brush;
|
|
359
|
+
if (brushObject.id === brushes.length - 1 && brush !== undefined) {
|
|
360
|
+
return 'all';
|
|
361
|
+
} else {
|
|
362
|
+
return 'none';
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
brushSelection.exit().remove();
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
function _arrayLikeToArray(r, a) {
|
|
370
|
+
(null == a || a > r.length) && (a = r.length);
|
|
371
|
+
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
372
|
+
return n;
|
|
373
|
+
}
|
|
374
|
+
function _arrayWithHoles(r) {
|
|
375
|
+
if (Array.isArray(r)) return r;
|
|
376
|
+
}
|
|
377
|
+
function _createForOfIteratorHelper(r, e) {
|
|
378
|
+
var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
379
|
+
if (!t) {
|
|
380
|
+
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) {
|
|
381
|
+
t && (r = t);
|
|
382
|
+
var n = 0,
|
|
383
|
+
F = function () {};
|
|
384
|
+
return {
|
|
385
|
+
s: F,
|
|
386
|
+
n: function () {
|
|
387
|
+
return n >= r.length ? {
|
|
388
|
+
done: true
|
|
389
|
+
} : {
|
|
390
|
+
done: false,
|
|
391
|
+
value: r[n++]
|
|
392
|
+
};
|
|
393
|
+
},
|
|
394
|
+
e: function (r) {
|
|
395
|
+
throw r;
|
|
396
|
+
},
|
|
397
|
+
f: F
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
401
|
+
}
|
|
402
|
+
var o,
|
|
403
|
+
a = true,
|
|
404
|
+
u = false;
|
|
405
|
+
return {
|
|
406
|
+
s: function () {
|
|
407
|
+
t = t.call(r);
|
|
408
|
+
},
|
|
409
|
+
n: function () {
|
|
410
|
+
var r = t.next();
|
|
411
|
+
return a = r.done, r;
|
|
412
|
+
},
|
|
413
|
+
e: function (r) {
|
|
414
|
+
u = true, o = r;
|
|
415
|
+
},
|
|
416
|
+
f: function () {
|
|
417
|
+
try {
|
|
418
|
+
a || null == t.return || t.return();
|
|
419
|
+
} finally {
|
|
420
|
+
if (u) throw o;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function _defineProperty(e, r, t) {
|
|
426
|
+
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
427
|
+
value: t,
|
|
428
|
+
enumerable: true,
|
|
429
|
+
configurable: true,
|
|
430
|
+
writable: true
|
|
431
|
+
}) : e[r] = t, e;
|
|
432
|
+
}
|
|
433
|
+
function _iterableToArrayLimit(r, l) {
|
|
434
|
+
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
435
|
+
if (null != t) {
|
|
436
|
+
var e,
|
|
437
|
+
n,
|
|
438
|
+
i,
|
|
439
|
+
u,
|
|
440
|
+
a = [],
|
|
441
|
+
f = true,
|
|
442
|
+
o = false;
|
|
443
|
+
try {
|
|
444
|
+
if (i = (t = t.call(r)).next, 0 === l) {
|
|
445
|
+
if (Object(t) !== t) return;
|
|
446
|
+
f = !1;
|
|
447
|
+
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
|
448
|
+
} catch (r) {
|
|
449
|
+
o = true, n = r;
|
|
450
|
+
} finally {
|
|
451
|
+
try {
|
|
452
|
+
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
453
|
+
} finally {
|
|
454
|
+
if (o) throw n;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return a;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function _nonIterableRest() {
|
|
461
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
462
|
+
}
|
|
463
|
+
function ownKeys(e, r) {
|
|
464
|
+
var t = Object.keys(e);
|
|
465
|
+
if (Object.getOwnPropertySymbols) {
|
|
466
|
+
var o = Object.getOwnPropertySymbols(e);
|
|
467
|
+
r && (o = o.filter(function (r) {
|
|
468
|
+
return Object.getOwnPropertyDescriptor(e, r).enumerable;
|
|
469
|
+
})), t.push.apply(t, o);
|
|
470
|
+
}
|
|
471
|
+
return t;
|
|
472
|
+
}
|
|
473
|
+
function _objectSpread2(e) {
|
|
474
|
+
for (var r = 1; r < arguments.length; r++) {
|
|
475
|
+
var t = null != arguments[r] ? arguments[r] : {};
|
|
476
|
+
r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
|
|
477
|
+
_defineProperty(e, r, t[r]);
|
|
478
|
+
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
|
|
479
|
+
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return e;
|
|
483
|
+
}
|
|
484
|
+
function _slicedToArray(r, e) {
|
|
485
|
+
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
486
|
+
}
|
|
487
|
+
function _toPrimitive(t, r) {
|
|
488
|
+
if ("object" != typeof t || !t) return t;
|
|
489
|
+
var e = t[Symbol.toPrimitive];
|
|
490
|
+
if (void 0 !== e) {
|
|
491
|
+
var i = e.call(t, r);
|
|
492
|
+
if ("object" != typeof i) return i;
|
|
493
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
494
|
+
}
|
|
495
|
+
return ("string" === r ? String : Number)(t);
|
|
496
|
+
}
|
|
497
|
+
function _toPropertyKey(t) {
|
|
498
|
+
var i = _toPrimitive(t, "string");
|
|
499
|
+
return "symbol" == typeof i ? i : i + "";
|
|
500
|
+
}
|
|
501
|
+
function _unsupportedIterableToArray(r, a) {
|
|
502
|
+
if (r) {
|
|
503
|
+
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
504
|
+
var t = {}.toString.call(r).slice(8, -1);
|
|
505
|
+
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// data within extents
|
|
510
|
+
var selected$3 = function selected(state, config, pc, events, brushGroup) {
|
|
511
|
+
var brushes = state.brushes;
|
|
512
|
+
var is_brushed = function is_brushed(p, pos) {
|
|
513
|
+
var axisBrushes = brushes[p];
|
|
514
|
+
for (var i = 0; i < axisBrushes.length; i++) {
|
|
515
|
+
var brush = document.getElementById('brush-' + pos + '-' + i);
|
|
516
|
+
if (brush && d3Brush.brushSelection(brush) !== null) {
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return false;
|
|
521
|
+
};
|
|
522
|
+
var actives = Object.keys(config.dimensions).filter(is_brushed);
|
|
523
|
+
var extents = actives.map(function (p) {
|
|
524
|
+
var axisBrushes = brushes[p];
|
|
525
|
+
return axisBrushes.filter(function (d) {
|
|
526
|
+
return !pc.hideAxis().includes(d);
|
|
527
|
+
}).map(function (d, i) {
|
|
528
|
+
return d3Brush.brushSelection(document.getElementById('brush-' + Object.keys(config.dimensions).indexOf(p) + '-' + i));
|
|
529
|
+
}).map(function (d, i) {
|
|
530
|
+
if (d === null || d === undefined) {
|
|
531
|
+
return null;
|
|
532
|
+
} else if (typeof config.dimensions[p].yscale.invert === 'function') {
|
|
533
|
+
return [config.dimensions[p].yscale.invert(d[1]), config.dimensions[p].yscale.invert(d[0])];
|
|
534
|
+
} else {
|
|
535
|
+
return d;
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// We don't want to return the full data set when there are no axes brushed.
|
|
541
|
+
// Actually, when there are no axes brushed, by definition, no items are
|
|
542
|
+
// selected. So, let's avoid the filtering and just return false.
|
|
543
|
+
//if (actives.length === 0) return false;
|
|
544
|
+
|
|
545
|
+
// Resolves broken examples for now. They expect to get the full dataset back from empty brushes
|
|
546
|
+
if (actives.length === 0) return config.data;
|
|
547
|
+
|
|
548
|
+
// test if within range
|
|
549
|
+
var within = {
|
|
550
|
+
date: function date(d, p, i) {
|
|
551
|
+
var dimExt = extents[i];
|
|
552
|
+
if (typeof config.dimensions[p].yscale.bandwidth === 'function') {
|
|
553
|
+
// if it is ordinal
|
|
554
|
+
var _iterator = _createForOfIteratorHelper(dimExt),
|
|
555
|
+
_step;
|
|
556
|
+
try {
|
|
557
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
558
|
+
var e = _step.value;
|
|
559
|
+
if (e === null || e === undefined) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
if (e[0] <= config.dimensions[p].yscale(d[p]) && config.dimensions[p].yscale(d[p]) <= e[1]) {
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
} catch (err) {
|
|
567
|
+
_iterator.e(err);
|
|
568
|
+
} finally {
|
|
569
|
+
_iterator.f();
|
|
570
|
+
}
|
|
571
|
+
return false;
|
|
572
|
+
} else {
|
|
573
|
+
var _iterator2 = _createForOfIteratorHelper(dimExt),
|
|
574
|
+
_step2;
|
|
575
|
+
try {
|
|
576
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
577
|
+
var _e = _step2.value;
|
|
578
|
+
if (_e === null || _e === undefined) {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (_e[0] <= d[p] && d[p] <= _e[1]) {
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
} catch (err) {
|
|
586
|
+
_iterator2.e(err);
|
|
587
|
+
} finally {
|
|
588
|
+
_iterator2.f();
|
|
589
|
+
}
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
number: function number(d, p, i) {
|
|
594
|
+
var dimExt = extents[i];
|
|
595
|
+
if (typeof config.dimensions[p].yscale.bandwidth === 'function') {
|
|
596
|
+
// if it is ordinal
|
|
597
|
+
var _iterator3 = _createForOfIteratorHelper(dimExt),
|
|
598
|
+
_step3;
|
|
599
|
+
try {
|
|
600
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
601
|
+
var e = _step3.value;
|
|
602
|
+
if (e === null || e === undefined) {
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
if (e[0] <= config.dimensions[p].yscale(d[p]) && config.dimensions[p].yscale(d[p]) <= e[1]) {
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
} catch (err) {
|
|
610
|
+
_iterator3.e(err);
|
|
611
|
+
} finally {
|
|
612
|
+
_iterator3.f();
|
|
613
|
+
}
|
|
614
|
+
return false;
|
|
615
|
+
} else {
|
|
616
|
+
var _iterator4 = _createForOfIteratorHelper(dimExt),
|
|
617
|
+
_step4;
|
|
618
|
+
try {
|
|
619
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
620
|
+
var _e2 = _step4.value;
|
|
621
|
+
if (_e2 === null || _e2 === undefined) {
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
if (_e2[0] <= d[p] && d[p] <= _e2[1]) {
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
} catch (err) {
|
|
629
|
+
_iterator4.e(err);
|
|
630
|
+
} finally {
|
|
631
|
+
_iterator4.f();
|
|
632
|
+
}
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
string: function string(d, p, i) {
|
|
637
|
+
var dimExt = extents[i];
|
|
638
|
+
var _iterator5 = _createForOfIteratorHelper(dimExt),
|
|
639
|
+
_step5;
|
|
640
|
+
try {
|
|
641
|
+
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
|
|
642
|
+
var e = _step5.value;
|
|
643
|
+
if (e === null || e === undefined) {
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (e[0] <= config.dimensions[p].yscale(d[p]) && config.dimensions[p].yscale(d[p]) <= e[1]) {
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
} catch (err) {
|
|
651
|
+
_iterator5.e(err);
|
|
652
|
+
} finally {
|
|
653
|
+
_iterator5.f();
|
|
654
|
+
}
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
return config.data.filter(function (d) {
|
|
659
|
+
switch (brushGroup.predicate) {
|
|
660
|
+
case 'AND':
|
|
661
|
+
return actives.every(function (p, i) {
|
|
662
|
+
return within[config.dimensions[p].type](d, p, i);
|
|
663
|
+
});
|
|
664
|
+
case 'OR':
|
|
665
|
+
return actives.some(function (p, i) {
|
|
666
|
+
return within[config.dimensions[p].type](d, p, i);
|
|
667
|
+
});
|
|
668
|
+
default:
|
|
669
|
+
throw new Error('Unknown brush predicate ' + config.brushPredicate);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
var brushUpdated = function brushUpdated(config, pc, events) {
|
|
675
|
+
return function (newSelection) {
|
|
676
|
+
config.brushed = newSelection;
|
|
677
|
+
events.call('brush', pc, config.brushed);
|
|
678
|
+
pc.renderBrushed();
|
|
679
|
+
};
|
|
680
|
+
};
|
|
681
|
+
var _newBrush = function newBrush(state, config, pc, events, brushGroup) {
|
|
682
|
+
return function (axis, _selector) {
|
|
683
|
+
var brushes = state.brushes,
|
|
684
|
+
brushNodes = state.brushNodes;
|
|
685
|
+
var brushRangeMax = config.dimensions[axis].type === 'string' ? config.dimensions[axis].yscale.range()[config.dimensions[axis].yscale.range().length - 1] : config.dimensions[axis].yscale.range()[0];
|
|
686
|
+
var brush = d3Brush.brushY().extent([[-15, 0], [15, brushRangeMax]]);
|
|
687
|
+
var id = brushes[axis] ? brushes[axis].length : 0;
|
|
688
|
+
var node = 'brush-' + Object.keys(config.dimensions).indexOf(axis) + '-' + id;
|
|
689
|
+
if (brushes[axis]) {
|
|
690
|
+
brushes[axis].push({
|
|
691
|
+
id: id,
|
|
692
|
+
brush: brush,
|
|
693
|
+
node: node
|
|
694
|
+
});
|
|
695
|
+
} else {
|
|
696
|
+
brushes[axis] = [{
|
|
697
|
+
id: id,
|
|
698
|
+
brush: brush,
|
|
699
|
+
node: node
|
|
700
|
+
}];
|
|
701
|
+
}
|
|
702
|
+
if (brushNodes[axis]) {
|
|
703
|
+
brushNodes[axis].push({
|
|
704
|
+
id: id,
|
|
705
|
+
node: node
|
|
706
|
+
});
|
|
707
|
+
} else {
|
|
708
|
+
brushNodes[axis] = [{
|
|
709
|
+
id: id,
|
|
710
|
+
node: node
|
|
711
|
+
}];
|
|
712
|
+
}
|
|
713
|
+
brush.on('start', function (event) {
|
|
714
|
+
if (event.sourceEvent !== null) {
|
|
715
|
+
events.call('brushstart', pc, config.brushed);
|
|
716
|
+
if (typeof event.sourceEvent.stopPropagation === 'function') {
|
|
717
|
+
event.sourceEvent.stopPropagation();
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}).on('brush', function () {
|
|
721
|
+
// record selections
|
|
722
|
+
brushUpdated(config, pc, events)(selected$3(state, config, pc, events, brushGroup));
|
|
723
|
+
}).on('end', function (event) {
|
|
724
|
+
// Figure out if our latest brush has a selection
|
|
725
|
+
var lastBrushID = brushes[axis][brushes[axis].length - 1].id;
|
|
726
|
+
var lastBrush = document.getElementById('brush-' + Object.keys(config.dimensions).indexOf(axis) + '-' + lastBrushID);
|
|
727
|
+
var selection = d3Brush.brushSelection(lastBrush);
|
|
728
|
+
if (selection !== undefined && selection !== null && selection[0] !== selection[1]) {
|
|
729
|
+
_newBrush(state, config, pc, events, brushGroup)(axis, _selector);
|
|
730
|
+
drawBrushes(brushes[axis], config, pc, axis, _selector);
|
|
731
|
+
brushUpdated(config, pc, events)(selected$3(state, config, pc, events, brushGroup));
|
|
732
|
+
} else {
|
|
733
|
+
if (event.sourceEvent && event.sourceEvent.toString() === '[object MouseEvent]' && event.selection === null) {
|
|
734
|
+
pc.brushReset(axis);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
events.call('brushend', pc, config.brushed);
|
|
738
|
+
});
|
|
739
|
+
return brush;
|
|
740
|
+
};
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
*
|
|
745
|
+
* extents are in format of [[2,6], [3,5]]
|
|
746
|
+
*
|
|
747
|
+
* * @param state
|
|
748
|
+
* @param config
|
|
749
|
+
* @param pc
|
|
750
|
+
* @returns {Function}
|
|
751
|
+
*/
|
|
752
|
+
var brushExtents = function brushExtents(state, config, pc, events, brushGroup) {
|
|
753
|
+
return function (extents) {
|
|
754
|
+
var brushes = state.brushes;
|
|
755
|
+
var hiddenAxes = pc.hideAxis();
|
|
756
|
+
if (typeof extents === 'undefined') {
|
|
757
|
+
return Object.keys(config.dimensions).filter(function (d) {
|
|
758
|
+
return !hiddenAxes.includes(d);
|
|
759
|
+
}).reduce(function (acc, cur, pos) {
|
|
760
|
+
var axisBrushes = brushes[cur];
|
|
761
|
+
if (axisBrushes === undefined || axisBrushes === null) {
|
|
762
|
+
acc[cur] = [];
|
|
763
|
+
} else {
|
|
764
|
+
acc[cur] = axisBrushes.reduce(function (d, p, i) {
|
|
765
|
+
var raw = d3Brush.brushSelection(document.getElementById('brush-' + pos + '-' + i));
|
|
766
|
+
if (raw) {
|
|
767
|
+
var yScale = config.dimensions[cur].yscale;
|
|
768
|
+
var scaled = invertByScale(raw, yScale);
|
|
769
|
+
d.push({
|
|
770
|
+
extent: p.brush.extent(),
|
|
771
|
+
selection: {
|
|
772
|
+
raw: raw,
|
|
773
|
+
scaled: scaled
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
return d;
|
|
778
|
+
}, []);
|
|
779
|
+
}
|
|
780
|
+
return acc;
|
|
781
|
+
}, {});
|
|
782
|
+
} else {
|
|
783
|
+
// //first get all the brush selections
|
|
784
|
+
// loop over each dimension and update appropriately (if it was passed in through extents)
|
|
785
|
+
Object.keys(config.dimensions).forEach(function (d, pos) {
|
|
786
|
+
if (extents[d] === undefined || extents[d] === null) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
var dim = config.dimensions[d];
|
|
790
|
+
var yExtents = extents[d].map(function (e) {
|
|
791
|
+
return e.map(dim.yscale);
|
|
792
|
+
});
|
|
793
|
+
var _bs = yExtents.map(function (e, j) {
|
|
794
|
+
var _brush = _newBrush(state, config, pc, events, brushGroup)(d, d3Selection.select('#brush-group-' + pos));
|
|
795
|
+
//update the extent
|
|
796
|
+
//sets the brushable extent to the specified array of points [[x0, y0], [x1, y1]]
|
|
797
|
+
_brush.extent([[-15, e[1]], [15, e[0]]]);
|
|
798
|
+
return {
|
|
799
|
+
id: j,
|
|
800
|
+
brush: _brush,
|
|
801
|
+
ext: e
|
|
802
|
+
};
|
|
803
|
+
});
|
|
804
|
+
brushes[d] = _bs;
|
|
805
|
+
drawBrushes(_bs, config, pc, d, d3Selection.select('#brush-group-' + pos));
|
|
806
|
+
|
|
807
|
+
//redraw the brush
|
|
808
|
+
//https://github.com/d3/d3-brush#brush_move
|
|
809
|
+
// For an x-brush, it must be defined as [x0, x1]; for a y-brush, it must be defined as [y0, y1].
|
|
810
|
+
_bs.forEach(function (f, k) {
|
|
811
|
+
d3Selection.select('#brush-' + pos + '-' + k).call(f.brush).call(f.brush.move, f.ext.reverse());
|
|
812
|
+
});
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
//redraw the chart
|
|
816
|
+
pc.renderBrushed();
|
|
817
|
+
return pc;
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
var _this$4 = undefined;
|
|
823
|
+
var brushReset$3 = function brushReset(state, config, pc) {
|
|
824
|
+
return function (dimension) {
|
|
825
|
+
var brushes = state.brushes;
|
|
826
|
+
if (dimension === undefined) {
|
|
827
|
+
if (pc.g() !== undefined && pc.g() !== null) {
|
|
828
|
+
Object.keys(config.dimensions).forEach(function (d, pos) {
|
|
829
|
+
var axisBrush = brushes[d];
|
|
830
|
+
|
|
831
|
+
// hidden axes will be undefined
|
|
832
|
+
if (axisBrush) {
|
|
833
|
+
axisBrush.forEach(function (e, i) {
|
|
834
|
+
var brush = document.getElementById('brush-' + pos + '-' + i);
|
|
835
|
+
if (brush && d3Brush.brushSelection(brush) !== null) {
|
|
836
|
+
pc.g().select('#brush-' + pos + '-' + i).call(e.brush.move, null);
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
pc.renderBrushed();
|
|
842
|
+
}
|
|
843
|
+
} else {
|
|
844
|
+
if (pc.g() !== undefined && pc.g() !== null) {
|
|
845
|
+
var axisBrush = brushes[dimension];
|
|
846
|
+
var pos = Object.keys(config.dimensions).indexOf(dimension);
|
|
847
|
+
if (axisBrush) {
|
|
848
|
+
axisBrush.forEach(function (e, i) {
|
|
849
|
+
var brush = document.getElementById('brush-' + pos + '-' + i);
|
|
850
|
+
if (d3Brush.brushSelection(brush) !== null) {
|
|
851
|
+
pc.g().select('#brush-' + pos + '-' + i).call(e.brush.move, null);
|
|
852
|
+
if (typeof e.event === 'function') {
|
|
853
|
+
e.event(d3Selection.select('#brush-' + pos + '-' + i));
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
pc.renderBrushed();
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return _this$4;
|
|
862
|
+
};
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
var brushFor = function brushFor(state, config, pc, events, brushGroup) {
|
|
866
|
+
return function (axis, _selector) {
|
|
867
|
+
var brushes = state.brushes;
|
|
868
|
+
_newBrush(state, config, pc, events, brushGroup)(axis, _selector);
|
|
869
|
+
drawBrushes(brushes[axis], config, pc, axis, _selector);
|
|
870
|
+
};
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
var install$2 = function install(state, config, pc, events, brushGroup) {
|
|
874
|
+
return function () {
|
|
875
|
+
if (!pc.g()) {
|
|
876
|
+
pc.createAxes();
|
|
877
|
+
}
|
|
878
|
+
var hiddenAxes = pc.hideAxis();
|
|
879
|
+
pc.g().append('svg:g').attr('id', function (d, i) {
|
|
880
|
+
return 'brush-group-' + i;
|
|
881
|
+
}).attr('class', 'brush-group').attr('dimension', function (d) {
|
|
882
|
+
return d;
|
|
883
|
+
}).each(function (d) {
|
|
884
|
+
if (!hiddenAxes.includes(d)) {
|
|
885
|
+
brushFor(state, config, pc, events, brushGroup)(d, d3Selection.select(this));
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
pc.brushExtents = brushExtents(state, config, pc, events, brushGroup);
|
|
889
|
+
pc.brushReset = brushReset$3(state, config, pc);
|
|
890
|
+
return pc;
|
|
891
|
+
};
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
var uninstall$2 = function uninstall(state, pc) {
|
|
895
|
+
return function () {
|
|
896
|
+
if (pc.g() !== undefined && pc.g() !== null) pc.g().selectAll('.brush-group').remove();
|
|
897
|
+
state.brushes = {};
|
|
898
|
+
delete pc.brushExtents;
|
|
899
|
+
delete pc.brushReset;
|
|
900
|
+
};
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
var install1DMultiAxes = function install1DMultiAxes(brushGroup, config, pc, events) {
|
|
904
|
+
var state = {
|
|
905
|
+
brushes: {},
|
|
906
|
+
brushNodes: {}
|
|
907
|
+
};
|
|
908
|
+
brushGroup.modes['1D-axes-multi'] = {
|
|
909
|
+
install: install$2(state, config, pc, events, brushGroup),
|
|
910
|
+
uninstall: uninstall$2(state, pc),
|
|
911
|
+
selected: selected$3(state, config, brushGroup),
|
|
912
|
+
brushState: brushExtents(state, config, pc)
|
|
913
|
+
};
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
var uninstall$1 = function uninstall(state, pc) {
|
|
917
|
+
return function () {
|
|
918
|
+
pc.selection.select('svg').select('g#strums').remove();
|
|
919
|
+
pc.selection.select('svg').select('rect#strum-events').remove();
|
|
920
|
+
pc.on('axesreorder.strums', undefined);
|
|
921
|
+
delete pc.brushReset;
|
|
922
|
+
state.strumRect = undefined;
|
|
923
|
+
};
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
// test if point falls between lines
|
|
927
|
+
var containmentTest$1 = function containmentTest(strum, width) {
|
|
928
|
+
return function (p) {
|
|
929
|
+
var p1 = [strum.p1[0] - strum.minX, strum.p1[1] - strum.minX],
|
|
930
|
+
p2 = [strum.p2[0] - strum.minX, strum.p2[1] - strum.minX],
|
|
931
|
+
m1 = 1 - width / p1[0],
|
|
932
|
+
b1 = p1[1] * (1 - m1),
|
|
933
|
+
m2 = 1 - width / p2[0],
|
|
934
|
+
b2 = p2[1] * (1 - m2);
|
|
935
|
+
var x = p[0],
|
|
936
|
+
y = p[1],
|
|
937
|
+
y1 = m1 * x + b1,
|
|
938
|
+
y2 = m2 * x + b2;
|
|
939
|
+
return y > Math.min(y1, y2) && y < Math.max(y1, y2);
|
|
940
|
+
};
|
|
941
|
+
};
|
|
942
|
+
var crossesStrum$1 = function crossesStrum(state, config) {
|
|
943
|
+
return function (d, id) {
|
|
944
|
+
var strum = state.strums[id],
|
|
945
|
+
test = containmentTest$1(strum, state.strums.width(id)),
|
|
946
|
+
d1 = strum.dims.left,
|
|
947
|
+
d2 = strum.dims.right,
|
|
948
|
+
y1 = config.dimensions[d1].yscale,
|
|
949
|
+
y2 = config.dimensions[d2].yscale,
|
|
950
|
+
point = [y1(d[d1]) - strum.minX, y2(d[d2]) - strum.minX];
|
|
951
|
+
return test(point);
|
|
952
|
+
};
|
|
953
|
+
};
|
|
954
|
+
var selected$2 = function selected(brushGroup, state, config) {
|
|
955
|
+
// Get the ids of the currently active strums.
|
|
956
|
+
var ids = Object.getOwnPropertyNames(state.strums).filter(function (d) {
|
|
957
|
+
return !isNaN(d);
|
|
958
|
+
}),
|
|
959
|
+
brushed = config.data;
|
|
960
|
+
if (ids.length === 0) {
|
|
961
|
+
return brushed;
|
|
962
|
+
}
|
|
963
|
+
var crossTest = crossesStrum$1(state, config);
|
|
964
|
+
return brushed.filter(function (d) {
|
|
965
|
+
switch (brushGroup.predicate) {
|
|
966
|
+
case 'AND':
|
|
967
|
+
return ids.every(function (id) {
|
|
968
|
+
return crossTest(d, id);
|
|
969
|
+
});
|
|
970
|
+
case 'OR':
|
|
971
|
+
return ids.some(function (id) {
|
|
972
|
+
return crossTest(d, id);
|
|
973
|
+
});
|
|
974
|
+
default:
|
|
975
|
+
throw new Error('Unknown brush predicate ' + config.brushPredicate);
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
var removeStrum$1 = function removeStrum(state, pc) {
|
|
981
|
+
var strum = state.strums[state.strums.active],
|
|
982
|
+
svg = pc.selection.select('svg').select('g#strums');
|
|
983
|
+
delete state.strums[state.strums.active];
|
|
984
|
+
svg.selectAll('line#strum-' + strum.dims.i).remove();
|
|
985
|
+
svg.selectAll('circle#strum-' + strum.dims.i).remove();
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
var onDragEnd$1 = function onDragEnd(brushGroup, state, config, pc, events) {
|
|
989
|
+
return function () {
|
|
990
|
+
var strum = state.strums[state.strums.active];
|
|
991
|
+
|
|
992
|
+
// Okay, somewhat unexpected, but not totally unsurprising, a mousclick is
|
|
993
|
+
// considered a drag without move. So we have to deal with that case
|
|
994
|
+
if (strum && strum.p1[0] === strum.p2[0] && strum.p1[1] === strum.p2[1]) {
|
|
995
|
+
removeStrum$1(state, pc);
|
|
996
|
+
}
|
|
997
|
+
var brushed = selected$2(brushGroup, state, config);
|
|
998
|
+
state.strums.active = undefined;
|
|
999
|
+
config.brushed = brushed;
|
|
1000
|
+
pc.renderBrushed();
|
|
1001
|
+
events.call('brushend', pc, config.brushed);
|
|
1002
|
+
};
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
var _drawStrum$1 = function drawStrum(brushGroup, state, config, pc, events, strum, activePoint) {
|
|
1006
|
+
var _svg = pc.selection.select('svg').select('g#strums'),
|
|
1007
|
+
id = strum.dims.i,
|
|
1008
|
+
points = [strum.p1, strum.p2],
|
|
1009
|
+
_line = _svg.selectAll('line#strum-' + id).data([strum]),
|
|
1010
|
+
circles = _svg.selectAll('circle#strum-' + id).data(points),
|
|
1011
|
+
_drag = d3Drag.drag();
|
|
1012
|
+
_line.enter().append('line').attr('id', 'strum-' + id).attr('class', 'strum');
|
|
1013
|
+
_line.attr('x1', function (d) {
|
|
1014
|
+
return d.p1[0];
|
|
1015
|
+
}).attr('y1', function (d) {
|
|
1016
|
+
return d.p1[1];
|
|
1017
|
+
}).attr('x2', function (d) {
|
|
1018
|
+
return d.p2[0];
|
|
1019
|
+
}).attr('y2', function (d) {
|
|
1020
|
+
return d.p2[1];
|
|
1021
|
+
}).attr('stroke', 'black').attr('stroke-width', 2);
|
|
1022
|
+
_drag.on('drag', function (event, d) {
|
|
1023
|
+
var i = points.indexOf(d) + 1;
|
|
1024
|
+
strum['p' + i][0] = Math.min(Math.max(strum.minX + 1, event.x), strum.maxX);
|
|
1025
|
+
strum['p' + i][1] = Math.min(Math.max(strum.minY, event.y), strum.maxY);
|
|
1026
|
+
_drawStrum$1(brushGroup, state, config, pc, events, strum, i - 1);
|
|
1027
|
+
}).on('end', onDragEnd$1(brushGroup, state, config, pc, events));
|
|
1028
|
+
circles.enter().append('circle').attr('id', 'strum-' + id).attr('class', 'strum');
|
|
1029
|
+
circles.attr('cx', function (d) {
|
|
1030
|
+
return d[0];
|
|
1031
|
+
}).attr('cy', function (d) {
|
|
1032
|
+
return d[1];
|
|
1033
|
+
}).attr('r', 5).style('opacity', function (d, i) {
|
|
1034
|
+
return activePoint !== undefined && i === activePoint ? 0.8 : 0;
|
|
1035
|
+
}).on('mouseover', function () {
|
|
1036
|
+
d3Selection.select(this).style('opacity', 0.8);
|
|
1037
|
+
}).on('mouseout', function () {
|
|
1038
|
+
d3Selection.select(this).style('opacity', 0);
|
|
1039
|
+
}).call(_drag);
|
|
1040
|
+
};
|
|
1041
|
+
var onDrag$1 = function onDrag(brushGroup, state, config, pc, events) {
|
|
1042
|
+
return function (event) {
|
|
1043
|
+
var strum = state.strums[state.strums.active];
|
|
1044
|
+
|
|
1045
|
+
// Make sure that the point is within the bounds
|
|
1046
|
+
strum.p2[0] = Math.min(Math.max(strum.minX + 1, event.x - config.margin.left), strum.maxX);
|
|
1047
|
+
strum.p2[1] = Math.min(Math.max(strum.minY, event.y - config.margin.top), strum.maxY);
|
|
1048
|
+
_drawStrum$1(brushGroup, state, config, pc, events, strum, 1);
|
|
1049
|
+
};
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
var h = function h(config) {
|
|
1053
|
+
return config.height - config.margin.top - config.margin.bottom;
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
var dimensionsForPoint = function dimensionsForPoint(config, pc, xscale, p) {
|
|
1057
|
+
var dims = {
|
|
1058
|
+
i: -1,
|
|
1059
|
+
left: undefined,
|
|
1060
|
+
right: undefined
|
|
1061
|
+
};
|
|
1062
|
+
Object.keys(config.dimensions).some(function (dim, i) {
|
|
1063
|
+
if (xscale(dim) < p[0]) {
|
|
1064
|
+
dims.i = i;
|
|
1065
|
+
dims.left = dim;
|
|
1066
|
+
dims.right = Object.keys(config.dimensions)[pc.getOrderedDimensionKeys().indexOf(dim) + 1];
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
return true;
|
|
1070
|
+
});
|
|
1071
|
+
if (dims.left === undefined) {
|
|
1072
|
+
// Event on the left side of the first axis.
|
|
1073
|
+
dims.i = 0;
|
|
1074
|
+
dims.left = pc.getOrderedDimensionKeys()[0];
|
|
1075
|
+
dims.right = pc.getOrderedDimensionKeys()[1];
|
|
1076
|
+
} else if (dims.right === undefined) {
|
|
1077
|
+
// Event on the right side of the last axis
|
|
1078
|
+
dims.i = Object.keys(config.dimensions).length - 1;
|
|
1079
|
+
dims.right = dims.left;
|
|
1080
|
+
dims.left = pc.getOrderedDimensionKeys()[Object.keys(config.dimensions).length - 2];
|
|
1081
|
+
}
|
|
1082
|
+
return dims;
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
// First we need to determine between which two axes the sturm was started.
|
|
1086
|
+
// This will determine the freedom of movement, because a strum can
|
|
1087
|
+
// logically only happen between two axes, so no movement outside these axes
|
|
1088
|
+
// should be allowed.
|
|
1089
|
+
var onDragStart$1 = function onDragStart(state, config, pc, xscale) {
|
|
1090
|
+
return function (event) {
|
|
1091
|
+
var p = d3Selection.pointer(event, state.strumRect.node());
|
|
1092
|
+
p[0] = p[0] - config.margin.left;
|
|
1093
|
+
p[1] = p[1] - config.margin.top;
|
|
1094
|
+
var dims = dimensionsForPoint(config, pc, xscale, p);
|
|
1095
|
+
var strum = {
|
|
1096
|
+
p1: p,
|
|
1097
|
+
dims: dims,
|
|
1098
|
+
minX: xscale(dims.left),
|
|
1099
|
+
maxX: xscale(dims.right),
|
|
1100
|
+
minY: 0,
|
|
1101
|
+
maxY: h(config)
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
// Make sure that the point is within the bounds
|
|
1105
|
+
strum.p1[0] = Math.min(Math.max(strum.minX, p[0]), strum.maxX);
|
|
1106
|
+
strum.p2 = strum.p1.slice();
|
|
1107
|
+
state.strums[dims.i] = strum;
|
|
1108
|
+
state.strums.active = dims.i;
|
|
1109
|
+
};
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
var brushReset$2 = function brushReset(brushGroup, state, config, pc, events) {
|
|
1113
|
+
return function () {
|
|
1114
|
+
var ids = Object.getOwnPropertyNames(state.strums).filter(function (d) {
|
|
1115
|
+
return !isNaN(d);
|
|
1116
|
+
});
|
|
1117
|
+
ids.forEach(function (d) {
|
|
1118
|
+
state.strums.active = d;
|
|
1119
|
+
removeStrum$1(state, pc);
|
|
1120
|
+
});
|
|
1121
|
+
onDragEnd$1(brushGroup, state, config, pc, events)();
|
|
1122
|
+
};
|
|
1123
|
+
};
|
|
1124
|
+
|
|
1125
|
+
// Checks if the first dimension is directly left of the second dimension.
|
|
1126
|
+
var consecutive = function consecutive(dimensions) {
|
|
1127
|
+
return function (first, second) {
|
|
1128
|
+
var keys = Object.keys(dimensions);
|
|
1129
|
+
return keys.some(function (d, i) {
|
|
1130
|
+
return d === first ? i + i < keys.length && dimensions[i + 1] === second : false;
|
|
1131
|
+
});
|
|
1132
|
+
};
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
var install$1 = function install(brushGroup, state, config, pc, events, xscale) {
|
|
1136
|
+
return function () {
|
|
1137
|
+
if (pc.g() === undefined || pc.g() === null) {
|
|
1138
|
+
pc.createAxes();
|
|
1139
|
+
}
|
|
1140
|
+
var _drag = d3Drag.drag();
|
|
1141
|
+
|
|
1142
|
+
// Map of current strums. Strums are stored per segment of the PC. A segment,
|
|
1143
|
+
// being the area between two axes. The left most area is indexed at 0.
|
|
1144
|
+
state.strums.active = undefined;
|
|
1145
|
+
// Returns the width of the PC segment where currently a strum is being
|
|
1146
|
+
// placed. NOTE: even though they are evenly spaced in our current
|
|
1147
|
+
// implementation, we keep for when non-even spaced segments are supported as
|
|
1148
|
+
// well.
|
|
1149
|
+
state.strums.width = function (id) {
|
|
1150
|
+
return state.strums[id] === undefined ? undefined : state.strums[id].maxX - state.strums[id].minX;
|
|
1151
|
+
};
|
|
1152
|
+
pc.on('axesreorder.strums', function () {
|
|
1153
|
+
var ids = Object.getOwnPropertyNames(state.strums).filter(function (d) {
|
|
1154
|
+
return !isNaN(d);
|
|
1155
|
+
});
|
|
1156
|
+
if (ids.length > 0) {
|
|
1157
|
+
// We have some strums, which might need to be removed.
|
|
1158
|
+
ids.forEach(function (d) {
|
|
1159
|
+
var dims = state.strums[d].dims;
|
|
1160
|
+
state.strums.active = d;
|
|
1161
|
+
// If the two dimensions of the current strum are not next to each other
|
|
1162
|
+
// any more, than we'll need to remove the strum. Otherwise we keep it.
|
|
1163
|
+
if (!consecutive(config.dimensions)(dims.left, dims.right)) {
|
|
1164
|
+
removeStrum$1(state, pc);
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
onDragEnd$1(brushGroup, state, config, pc, events)();
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
// Add a new svg group in which we draw the strums.
|
|
1172
|
+
pc.selection.select('svg').append('g').attr('id', 'strums').attr('transform', 'translate(' + config.margin.left + ',' + config.margin.top + ')');
|
|
1173
|
+
|
|
1174
|
+
// Install the required brushReset function
|
|
1175
|
+
pc.brushReset = brushReset$2(brushGroup, state, config, pc, events);
|
|
1176
|
+
_drag.on('start', onDragStart$1(state, config, pc, xscale)).on('drag', onDrag$1(brushGroup, state, config, pc, events)).on('end', onDragEnd$1(brushGroup, state, config, pc, events));
|
|
1177
|
+
|
|
1178
|
+
// NOTE: The styling needs to be done here and not in the css. This is because
|
|
1179
|
+
// for 1D brushing, the canvas layers should not listen to
|
|
1180
|
+
// pointer-events._.
|
|
1181
|
+
state.strumRect = pc.selection.select('svg').insert('rect', 'g#strums').attr('id', 'strum-events').attr('x', config.margin.left).attr('y', config.margin.top).attr('width', w(config)).attr('height', h(config) + 2).style('opacity', 0).call(_drag);
|
|
1182
|
+
};
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
var install2DStrums = function install2DStrums(brushGroup, config, pc, events, xscale) {
|
|
1186
|
+
var state = {
|
|
1187
|
+
strums: {},
|
|
1188
|
+
strumRect: {}
|
|
1189
|
+
};
|
|
1190
|
+
brushGroup.modes['2D-strums'] = {
|
|
1191
|
+
install: install$1(brushGroup, state, config, pc, events, xscale),
|
|
1192
|
+
uninstall: uninstall$1(state, pc),
|
|
1193
|
+
selected: selected$2(brushGroup, state, config),
|
|
1194
|
+
brushState: function brushState() {
|
|
1195
|
+
return state.strums;
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
var uninstall = function uninstall(state, pc) {
|
|
1201
|
+
return function () {
|
|
1202
|
+
pc.selection.select('svg').select('g#arcs').remove();
|
|
1203
|
+
pc.selection.select('svg').select('rect#arc-events').remove();
|
|
1204
|
+
pc.on('axesreorder.arcs', undefined);
|
|
1205
|
+
delete pc.brushReset;
|
|
1206
|
+
state.strumRect = undefined;
|
|
1207
|
+
};
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
var hypothenuse = function hypothenuse(a, b) {
|
|
1211
|
+
return Math.sqrt(a * a + b * b);
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1214
|
+
// [0, 2*PI] -> [-PI/2, PI/2]
|
|
1215
|
+
var signedAngle = function signedAngle(angle) {
|
|
1216
|
+
return angle > Math.PI ? 1.5 * Math.PI - angle : 0.5 * Math.PI - angle;
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
/**
|
|
1220
|
+
* angles are stored in radians from in [0, 2*PI], where 0 in 12 o'clock.
|
|
1221
|
+
* However, one can only select lines from 0 to PI, so we compute the
|
|
1222
|
+
* 'signed' angle, where 0 is the horizontal line (3 o'clock), and +/- PI/2
|
|
1223
|
+
* are 12 and 6 o'clock respectively.
|
|
1224
|
+
*/
|
|
1225
|
+
var containmentTest = function containmentTest(arc) {
|
|
1226
|
+
return function (a) {
|
|
1227
|
+
var startAngle = signedAngle(arc.startAngle);
|
|
1228
|
+
var endAngle = signedAngle(arc.endAngle);
|
|
1229
|
+
if (startAngle > endAngle) {
|
|
1230
|
+
var tmp = startAngle;
|
|
1231
|
+
startAngle = endAngle;
|
|
1232
|
+
endAngle = tmp;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// test if segment angle is contained in angle interval
|
|
1236
|
+
return a >= startAngle && a <= endAngle;
|
|
1237
|
+
};
|
|
1238
|
+
};
|
|
1239
|
+
var crossesStrum = function crossesStrum(state, config) {
|
|
1240
|
+
return function (d, id) {
|
|
1241
|
+
var arc = state.arcs[id],
|
|
1242
|
+
test = containmentTest(arc),
|
|
1243
|
+
d1 = arc.dims.left,
|
|
1244
|
+
d2 = arc.dims.right,
|
|
1245
|
+
y1 = config.dimensions[d1].yscale,
|
|
1246
|
+
y2 = config.dimensions[d2].yscale,
|
|
1247
|
+
a = state.arcs.width(id),
|
|
1248
|
+
b = y1(d[d1]) - y2(d[d2]),
|
|
1249
|
+
c = hypothenuse(a, b),
|
|
1250
|
+
angle = Math.asin(b / c); // rad in [-PI/2, PI/2]
|
|
1251
|
+
return test(angle);
|
|
1252
|
+
};
|
|
1253
|
+
};
|
|
1254
|
+
var selected$1 = function selected(brushGroup, state, config) {
|
|
1255
|
+
var ids = Object.getOwnPropertyNames(state.arcs).filter(function (d) {
|
|
1256
|
+
return !isNaN(d);
|
|
1257
|
+
});
|
|
1258
|
+
var brushed = config.data;
|
|
1259
|
+
if (ids.length === 0) {
|
|
1260
|
+
return brushed;
|
|
1261
|
+
}
|
|
1262
|
+
var crossTest = crossesStrum(state, config);
|
|
1263
|
+
return brushed.filter(function (d) {
|
|
1264
|
+
switch (brushGroup.predicate) {
|
|
1265
|
+
case 'AND':
|
|
1266
|
+
return ids.every(function (id) {
|
|
1267
|
+
return crossTest(d, id);
|
|
1268
|
+
});
|
|
1269
|
+
case 'OR':
|
|
1270
|
+
return ids.some(function (id) {
|
|
1271
|
+
return crossTest(d, id);
|
|
1272
|
+
});
|
|
1273
|
+
default:
|
|
1274
|
+
throw new Error('Unknown brush predicate ' + config.brushPredicate);
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
var removeStrum = function removeStrum(state, pc) {
|
|
1280
|
+
var arc = state.arcs[state.arcs.active],
|
|
1281
|
+
svg = pc.selection.select('svg').select('g#arcs');
|
|
1282
|
+
delete state.arcs[state.arcs.active];
|
|
1283
|
+
state.arcs.active = undefined;
|
|
1284
|
+
svg.selectAll('line#arc-' + arc.dims.i).remove();
|
|
1285
|
+
svg.selectAll('circle#arc-' + arc.dims.i).remove();
|
|
1286
|
+
svg.selectAll('path#arc-' + arc.dims.i).remove();
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
var onDragEnd = function onDragEnd(brushGroup, state, config, pc, events) {
|
|
1290
|
+
return function () {
|
|
1291
|
+
var arc = state.arcs[state.arcs.active];
|
|
1292
|
+
|
|
1293
|
+
// Okay, somewhat unexpected, but not totally unsurprising, a mousclick is
|
|
1294
|
+
// considered a drag without move. So we have to deal with that case
|
|
1295
|
+
if (arc && arc.p1[0] === arc.p2[0] && arc.p1[1] === arc.p2[1]) {
|
|
1296
|
+
removeStrum(state, pc);
|
|
1297
|
+
}
|
|
1298
|
+
if (arc) {
|
|
1299
|
+
var angle = state.arcs.startAngle(state.arcs.active);
|
|
1300
|
+
arc.startAngle = angle;
|
|
1301
|
+
arc.endAngle = angle;
|
|
1302
|
+
arc.arc.outerRadius(state.arcs.length(state.arcs.active)).startAngle(angle).endAngle(angle);
|
|
1303
|
+
}
|
|
1304
|
+
state.arcs.active = undefined;
|
|
1305
|
+
config.brushed = selected$1(brushGroup, state, config);
|
|
1306
|
+
pc.renderBrushed();
|
|
1307
|
+
events.call('brushend', pc, config.brushed);
|
|
1308
|
+
};
|
|
1309
|
+
};
|
|
1310
|
+
|
|
1311
|
+
var _drawStrum = function drawStrum(brushGroup, state, config, pc, events, arc, activePoint) {
|
|
1312
|
+
var svg = pc.selection.select('svg').select('g#arcs'),
|
|
1313
|
+
id = arc.dims.i,
|
|
1314
|
+
points = [arc.p2, arc.p3],
|
|
1315
|
+
_line = svg.selectAll('line#arc-' + id).data([{
|
|
1316
|
+
p1: arc.p1,
|
|
1317
|
+
p2: arc.p2
|
|
1318
|
+
}, {
|
|
1319
|
+
p1: arc.p1,
|
|
1320
|
+
p2: arc.p3
|
|
1321
|
+
}]),
|
|
1322
|
+
circles = svg.selectAll('circle#arc-' + id).data(points),
|
|
1323
|
+
_drag = d3Drag.drag(),
|
|
1324
|
+
_path = svg.selectAll('path#arc-' + id).data([arc]);
|
|
1325
|
+
_path.enter().append('path').attr('id', 'arc-' + id).attr('class', 'arc').style('fill', 'orange').style('opacity', 0.5);
|
|
1326
|
+
_path.attr('d', arc.arc).attr('transform', 'translate(' + arc.p1[0] + ',' + arc.p1[1] + ')');
|
|
1327
|
+
_line.enter().append('line').attr('id', 'arc-' + id).attr('class', 'arc');
|
|
1328
|
+
_line.attr('x1', function (d) {
|
|
1329
|
+
return d.p1[0];
|
|
1330
|
+
}).attr('y1', function (d) {
|
|
1331
|
+
return d.p1[1];
|
|
1332
|
+
}).attr('x2', function (d) {
|
|
1333
|
+
return d.p2[0];
|
|
1334
|
+
}).attr('y2', function (d) {
|
|
1335
|
+
return d.p2[1];
|
|
1336
|
+
}).attr('stroke', 'black').attr('stroke-width', 2);
|
|
1337
|
+
_drag.on('drag', function (event, d) {
|
|
1338
|
+
var i = points.indexOf(d) + 2;
|
|
1339
|
+
arc['p' + i][0] = Math.min(Math.max(arc.minX + 1, event.x), arc.maxX);
|
|
1340
|
+
arc['p' + i][1] = Math.min(Math.max(arc.minY, event.y), arc.maxY);
|
|
1341
|
+
var angle = i === 3 ? state.arcs.startAngle(id) : state.arcs.endAngle(id);
|
|
1342
|
+
if (arc.startAngle < Math.PI && arc.endAngle < Math.PI && angle < Math.PI || arc.startAngle >= Math.PI && arc.endAngle >= Math.PI && angle >= Math.PI) {
|
|
1343
|
+
if (i === 2) {
|
|
1344
|
+
arc.endAngle = angle;
|
|
1345
|
+
arc.arc.endAngle(angle);
|
|
1346
|
+
} else if (i === 3) {
|
|
1347
|
+
arc.startAngle = angle;
|
|
1348
|
+
arc.arc.startAngle(angle);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
_drawStrum(brushGroup, state, config, pc, events, arc, i - 2);
|
|
1352
|
+
}).on('end', onDragEnd(brushGroup, state, config, pc, events));
|
|
1353
|
+
circles.enter().append('circle').attr('id', 'arc-' + id).attr('class', 'arc');
|
|
1354
|
+
circles.attr('cx', function (d) {
|
|
1355
|
+
return d[0];
|
|
1356
|
+
}).attr('cy', function (d) {
|
|
1357
|
+
return d[1];
|
|
1358
|
+
}).attr('r', 5).style('opacity', function (d, i) {
|
|
1359
|
+
return activePoint !== undefined && i === activePoint ? 0.8 : 0;
|
|
1360
|
+
}).on('mouseover', function () {
|
|
1361
|
+
d3Selection.select(this).style('opacity', 0.8);
|
|
1362
|
+
}).on('mouseout', function () {
|
|
1363
|
+
d3Selection.select(this).style('opacity', 0);
|
|
1364
|
+
}).call(_drag);
|
|
1365
|
+
};
|
|
1366
|
+
var onDrag = function onDrag(brushGroup, state, config, pc, events) {
|
|
1367
|
+
return function (event) {
|
|
1368
|
+
var arc = state.arcs[state.arcs.active];
|
|
1369
|
+
|
|
1370
|
+
// Make sure that the point is within the bounds
|
|
1371
|
+
arc.p2[0] = Math.min(Math.max(arc.minX + 1, event.x - config.margin.left), arc.maxX);
|
|
1372
|
+
arc.p2[1] = Math.min(Math.max(arc.minY, event.y - config.margin.top), arc.maxY);
|
|
1373
|
+
arc.p3 = arc.p2.slice();
|
|
1374
|
+
_drawStrum(brushGroup, state, config, pc, events, arc, 1);
|
|
1375
|
+
};
|
|
1376
|
+
};
|
|
1377
|
+
|
|
1378
|
+
// First we need to determine between which two axes the arc was started.
|
|
1379
|
+
// This will determine the freedom of movement, because a arc can
|
|
1380
|
+
// logically only happen between two axes, so no movement outside these axes
|
|
1381
|
+
// should be allowed.
|
|
1382
|
+
var onDragStart = function onDragStart(state, config, pc, xscale) {
|
|
1383
|
+
return function (event) {
|
|
1384
|
+
var p = d3Selection.pointer(event, state.strumRect.node());
|
|
1385
|
+
p[0] = p[0] - config.margin.left;
|
|
1386
|
+
p[1] = p[1] - config.margin.top;
|
|
1387
|
+
var dims = dimensionsForPoint(config, pc, xscale, p);
|
|
1388
|
+
var arc = {
|
|
1389
|
+
p1: p,
|
|
1390
|
+
dims: dims,
|
|
1391
|
+
minX: xscale(dims.left),
|
|
1392
|
+
maxX: xscale(dims.right),
|
|
1393
|
+
minY: 0,
|
|
1394
|
+
maxY: h(config),
|
|
1395
|
+
startAngle: undefined,
|
|
1396
|
+
endAngle: undefined,
|
|
1397
|
+
arc: d3Shape.arc().innerRadius(0)
|
|
1398
|
+
};
|
|
1399
|
+
|
|
1400
|
+
// Make sure that the point is within the bounds
|
|
1401
|
+
arc.p1[0] = Math.min(Math.max(arc.minX, p[0]), arc.maxX);
|
|
1402
|
+
arc.p2 = arc.p1.slice();
|
|
1403
|
+
arc.p3 = arc.p1.slice();
|
|
1404
|
+
state.arcs[dims.i] = arc;
|
|
1405
|
+
state.arcs.active = dims.i;
|
|
1406
|
+
};
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
var brushReset$1 = function brushReset(brushGroup, state, config, pc, events) {
|
|
1410
|
+
return function () {
|
|
1411
|
+
var ids = Object.getOwnPropertyNames(state.arcs).filter(function (d) {
|
|
1412
|
+
return !isNaN(d);
|
|
1413
|
+
});
|
|
1414
|
+
ids.forEach(function (d) {
|
|
1415
|
+
state.arcs.active = d;
|
|
1416
|
+
removeStrum(state, pc);
|
|
1417
|
+
});
|
|
1418
|
+
onDragEnd(brushGroup, state, config, pc, events)();
|
|
1419
|
+
};
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1422
|
+
// returns angles in [-PI/2, PI/2]
|
|
1423
|
+
var angle = function angle(p1, p2) {
|
|
1424
|
+
var a = p1[0] - p2[0],
|
|
1425
|
+
b = p1[1] - p2[1],
|
|
1426
|
+
c = hypothenuse(a, b);
|
|
1427
|
+
return Math.asin(b / c);
|
|
1428
|
+
};
|
|
1429
|
+
var endAngle = function endAngle(state) {
|
|
1430
|
+
return function (id) {
|
|
1431
|
+
var arc = state.arcs[id];
|
|
1432
|
+
if (arc === undefined) {
|
|
1433
|
+
return undefined;
|
|
1434
|
+
}
|
|
1435
|
+
var sAngle = angle(arc.p1, arc.p2),
|
|
1436
|
+
uAngle = -sAngle + Math.PI / 2;
|
|
1437
|
+
if (arc.p1[0] > arc.p2[0]) {
|
|
1438
|
+
uAngle = 2 * Math.PI - uAngle;
|
|
1439
|
+
}
|
|
1440
|
+
return uAngle;
|
|
1441
|
+
};
|
|
1442
|
+
};
|
|
1443
|
+
var startAngle = function startAngle(state) {
|
|
1444
|
+
return function (id) {
|
|
1445
|
+
var arc = state.arcs[id];
|
|
1446
|
+
if (arc === undefined) {
|
|
1447
|
+
return undefined;
|
|
1448
|
+
}
|
|
1449
|
+
var sAngle = angle(arc.p1, arc.p3),
|
|
1450
|
+
uAngle = -sAngle + Math.PI / 2;
|
|
1451
|
+
if (arc.p1[0] > arc.p3[0]) {
|
|
1452
|
+
uAngle = 2 * Math.PI - uAngle;
|
|
1453
|
+
}
|
|
1454
|
+
return uAngle;
|
|
1455
|
+
};
|
|
1456
|
+
};
|
|
1457
|
+
var length = function length(state) {
|
|
1458
|
+
return function (id) {
|
|
1459
|
+
var arc = state.arcs[id];
|
|
1460
|
+
if (arc === undefined) {
|
|
1461
|
+
return undefined;
|
|
1462
|
+
}
|
|
1463
|
+
var a = arc.p1[0] - arc.p2[0],
|
|
1464
|
+
b = arc.p1[1] - arc.p2[1];
|
|
1465
|
+
return hypothenuse(a, b);
|
|
1466
|
+
};
|
|
1467
|
+
};
|
|
1468
|
+
var install = function install(brushGroup, state, config, pc, events, xscale) {
|
|
1469
|
+
return function () {
|
|
1470
|
+
if (!pc.g()) {
|
|
1471
|
+
pc.createAxes();
|
|
1472
|
+
}
|
|
1473
|
+
var _drag = d3Drag.drag();
|
|
1474
|
+
|
|
1475
|
+
// Map of current arcs. arcs are stored per segment of the PC. A segment,
|
|
1476
|
+
// being the area between two axes. The left most area is indexed at 0.
|
|
1477
|
+
state.arcs.active = undefined;
|
|
1478
|
+
// Returns the width of the PC segment where currently a arc is being
|
|
1479
|
+
// placed. NOTE: even though they are evenly spaced in our current
|
|
1480
|
+
// implementation, we keep for when non-even spaced segments are supported as
|
|
1481
|
+
// well.
|
|
1482
|
+
state.arcs.width = function (id) {
|
|
1483
|
+
var arc = state.arcs[id];
|
|
1484
|
+
return arc === undefined ? undefined : arc.maxX - arc.minX;
|
|
1485
|
+
};
|
|
1486
|
+
|
|
1487
|
+
// returns angles in [0, 2 * PI]
|
|
1488
|
+
state.arcs.endAngle = endAngle(state);
|
|
1489
|
+
state.arcs.startAngle = startAngle(state);
|
|
1490
|
+
state.arcs.length = length(state);
|
|
1491
|
+
pc.on('axesreorder.arcs', function () {
|
|
1492
|
+
var ids = Object.getOwnPropertyNames(arcs).filter(function (d) {
|
|
1493
|
+
return !isNaN(d);
|
|
1494
|
+
});
|
|
1495
|
+
if (ids.length > 0) {
|
|
1496
|
+
// We have some arcs, which might need to be removed.
|
|
1497
|
+
ids.forEach(function (d) {
|
|
1498
|
+
var dims = arcs[d].dims;
|
|
1499
|
+
state.arcs.active = d;
|
|
1500
|
+
// If the two dimensions of the current arc are not next to each other
|
|
1501
|
+
// any more, than we'll need to remove the arc. Otherwise we keep it.
|
|
1502
|
+
if (!consecutive(dims)(dims.left, dims.right)) {
|
|
1503
|
+
removeStrum(state, pc);
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
onDragEnd(brushGroup, state, config, pc, events)();
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
// Add a new svg group in which we draw the arcs.
|
|
1511
|
+
pc.selection.select('svg').append('g').attr('id', 'arcs').attr('transform', 'translate(' + config.margin.left + ',' + config.margin.top + ')');
|
|
1512
|
+
|
|
1513
|
+
// Install the required brushReset function
|
|
1514
|
+
pc.brushReset = brushReset$1(brushGroup, state, config, pc, events);
|
|
1515
|
+
_drag.on('start', onDragStart(state, config, pc, xscale)).on('drag', onDrag(brushGroup, state, config, pc, events)).on('end', onDragEnd(brushGroup, state, config, pc, events));
|
|
1516
|
+
|
|
1517
|
+
// NOTE: The styling needs to be done here and not in the css. This is because
|
|
1518
|
+
// for 1D brushing, the canvas layers should not listen to
|
|
1519
|
+
// pointer-events._.
|
|
1520
|
+
state.strumRect = pc.selection.select('svg').insert('rect', 'g#arcs').attr('id', 'arc-events').attr('x', config.margin.left).attr('y', config.margin.top).attr('width', w(config)).attr('height', h(config) + 2).style('opacity', 0).call(_drag);
|
|
1521
|
+
};
|
|
1522
|
+
};
|
|
1523
|
+
|
|
1524
|
+
var installAngularBrush = function installAngularBrush(brushGroup, config, pc, events, xscale) {
|
|
1525
|
+
var state = {
|
|
1526
|
+
arcs: {},
|
|
1527
|
+
strumRect: {}
|
|
1528
|
+
};
|
|
1529
|
+
brushGroup.modes['angular'] = {
|
|
1530
|
+
install: install(brushGroup, state, config, pc, events, xscale),
|
|
1531
|
+
uninstall: uninstall(state, pc),
|
|
1532
|
+
selected: selected$1(brushGroup, state, config),
|
|
1533
|
+
brushState: function brushState() {
|
|
1534
|
+
return state.arcs;
|
|
1535
|
+
}
|
|
1536
|
+
};
|
|
1537
|
+
};
|
|
1538
|
+
|
|
1539
|
+
// calculate 2d intersection of line a->b with line c->d
|
|
1540
|
+
// points are objects with x and y properties
|
|
1541
|
+
var intersection = function intersection(a, b, c, d) {
|
|
1542
|
+
return {
|
|
1543
|
+
x: ((a.x * b.y - a.y * b.x) * (c.x - d.x) - (a.x - b.x) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x)),
|
|
1544
|
+
y: ((a.x * b.y - a.y * b.x) * (c.y - d.y) - (a.y - b.y) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x))
|
|
1545
|
+
};
|
|
1546
|
+
};
|
|
1547
|
+
|
|
1548
|
+
// Merges the canvases and SVG elements into one canvas element which is then passed into the callback
|
|
1549
|
+
// (so you can choose to save it to disk, etc.)
|
|
1550
|
+
var mergeParcoords = function mergeParcoords(pc) {
|
|
1551
|
+
return function (callback) {
|
|
1552
|
+
// Retina display, etc.
|
|
1553
|
+
var devicePixelRatio = window.devicePixelRatio || 1;
|
|
1554
|
+
|
|
1555
|
+
// Create a canvas element to store the merged canvases
|
|
1556
|
+
var mergedCanvas = document.createElement('canvas');
|
|
1557
|
+
var foregroundCanvas = pc.canvas.foreground;
|
|
1558
|
+
// We will need to adjust for canvas margins to align the svg and canvas
|
|
1559
|
+
var canvasMarginLeft = Number(foregroundCanvas.style.marginLeft.replace('px', ''));
|
|
1560
|
+
var textTopAdjust = 15;
|
|
1561
|
+
var canvasMarginTop = Number(foregroundCanvas.style.marginTop.replace('px', '')) + textTopAdjust;
|
|
1562
|
+
var width = (foregroundCanvas.clientWidth + canvasMarginLeft) * devicePixelRatio;
|
|
1563
|
+
var height = (foregroundCanvas.clientHeight + canvasMarginTop) * devicePixelRatio;
|
|
1564
|
+
mergedCanvas.width = width + 50; // pad so that svg labels at right will not get cut off
|
|
1565
|
+
mergedCanvas.height = height + 30; // pad so that svg labels at bottom will not get cut off
|
|
1566
|
+
mergedCanvas.style.width = mergedCanvas.width / devicePixelRatio + 'px';
|
|
1567
|
+
mergedCanvas.style.height = mergedCanvas.height / devicePixelRatio + 'px';
|
|
1568
|
+
|
|
1569
|
+
// Give the canvas a white background
|
|
1570
|
+
var context = mergedCanvas.getContext('2d');
|
|
1571
|
+
context.fillStyle = '#ffffff';
|
|
1572
|
+
context.fillRect(0, 0, mergedCanvas.width, mergedCanvas.height);
|
|
1573
|
+
|
|
1574
|
+
// Merge all the canvases
|
|
1575
|
+
for (var key in pc.canvas) {
|
|
1576
|
+
context.drawImage(pc.canvas[key], canvasMarginLeft * devicePixelRatio, canvasMarginTop * devicePixelRatio, width - canvasMarginLeft * devicePixelRatio, height - canvasMarginTop * devicePixelRatio);
|
|
1577
|
+
}
|
|
1578
|
+
var serializer = new XMLSerializer();
|
|
1579
|
+
// axis labels are translated (0,-5) so we will clone the svg
|
|
1580
|
+
// and translate down so the labels are drawn on the canvas
|
|
1581
|
+
var svgNodeCopy = pc.selection.select('svg').node().cloneNode(true);
|
|
1582
|
+
svgNodeCopy.setAttribute('transform', 'translate(0,' + textTopAdjust + ')');
|
|
1583
|
+
svgNodeCopy.setAttribute('height', svgNodeCopy.getAttribute('height') + textTopAdjust);
|
|
1584
|
+
// text will need fill attribute since css styles will not get picked up
|
|
1585
|
+
// this is not sophisticated since it doesn't look up css styles
|
|
1586
|
+
// if the user changes
|
|
1587
|
+
d3Selection.select(svgNodeCopy).selectAll('text').attr('fill', 'black');
|
|
1588
|
+
var svgStr = serializer.serializeToString(svgNodeCopy);
|
|
1589
|
+
|
|
1590
|
+
// Create a Data URI.
|
|
1591
|
+
var src = 'data:image/svg+xml;base64,' + window.btoa(svgStr);
|
|
1592
|
+
var img = new Image();
|
|
1593
|
+
img.onload = function () {
|
|
1594
|
+
context.drawImage(img, 0, 0, img.width * devicePixelRatio, img.height * devicePixelRatio);
|
|
1595
|
+
if (typeof callback === 'function') {
|
|
1596
|
+
callback(mergedCanvas);
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
img.src = src;
|
|
1600
|
+
};
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
var selected = function selected(config, pc) {
|
|
1604
|
+
return function () {
|
|
1605
|
+
var actives = [];
|
|
1606
|
+
var extents = [];
|
|
1607
|
+
var ranges = {};
|
|
1608
|
+
//get brush selections from each node, convert to actual values
|
|
1609
|
+
//invert order of values in array to comply with the parcoords architecture
|
|
1610
|
+
if (config.brushes.length === 0) {
|
|
1611
|
+
var nodes = pc.g().selectAll('.brush').nodes();
|
|
1612
|
+
for (var k = 0; k < nodes.length; k++) {
|
|
1613
|
+
if (d3Brush.brushSelection(nodes[k]) !== null) {
|
|
1614
|
+
actives.push(nodes[k].__data__);
|
|
1615
|
+
var values = [];
|
|
1616
|
+
var ranger = d3Brush.brushSelection(nodes[k]);
|
|
1617
|
+
if (typeof config.dimensions[nodes[k].__data__].yscale.domain()[0] === 'number') {
|
|
1618
|
+
for (var i = 0; i < ranger.length; i++) {
|
|
1619
|
+
if (actives.includes(nodes[k].__data__) && config.flipAxes.includes(nodes[k].__data__)) {
|
|
1620
|
+
values.push(config.dimensions[nodes[k].__data__].yscale.invert(ranger[i]));
|
|
1621
|
+
} else if (config.dimensions[nodes[k].__data__].yscale() !== 1) {
|
|
1622
|
+
values.unshift(config.dimensions[nodes[k].__data__].yscale.invert(ranger[i]));
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
extents.push(values);
|
|
1626
|
+
for (var ii = 0; ii < extents.length; ii++) {
|
|
1627
|
+
if (extents[ii].length === 0) {
|
|
1628
|
+
extents[ii] = [1, 1];
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
} else {
|
|
1632
|
+
ranges[nodes[k].__data__] = d3Brush.brushSelection(nodes[k]);
|
|
1633
|
+
var dimRange = config.dimensions[nodes[k].__data__].yscale.range();
|
|
1634
|
+
config.dimensions[nodes[k].__data__].yscale.domain();
|
|
1635
|
+
for (var j = 0; j < dimRange.length; j++) {
|
|
1636
|
+
if (dimRange[j] >= ranger[0] && dimRange[j] <= ranger[1] && actives.includes(nodes[k].__data__) && config.flipAxes.includes(nodes[k].__data__)) {
|
|
1637
|
+
values.push(dimRange[j]);
|
|
1638
|
+
} else if (dimRange[j] >= ranger[0] && dimRange[j] <= ranger[1]) {
|
|
1639
|
+
values.unshift(dimRange[j]);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
extents.push(values);
|
|
1643
|
+
for (var _ii = 0; _ii < extents.length; _ii++) {
|
|
1644
|
+
if (extents[_ii].length === 0) {
|
|
1645
|
+
extents[_ii] = [1, 1];
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
// test if within range
|
|
1652
|
+
var within = {
|
|
1653
|
+
date: function date(d, p, dimension) {
|
|
1654
|
+
var category = d[p];
|
|
1655
|
+
var categoryIndex = config.dimensions[p].yscale.domain().indexOf(category);
|
|
1656
|
+
var categoryRangeValue = config.dimensions[p].yscale.range()[categoryIndex];
|
|
1657
|
+
return categoryRangeValue >= ranges[p][0] && categoryRangeValue <= ranges[p][1];
|
|
1658
|
+
},
|
|
1659
|
+
number: function number(d, p, dimension) {
|
|
1660
|
+
return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1];
|
|
1661
|
+
},
|
|
1662
|
+
string: function string(d, p, dimension) {
|
|
1663
|
+
var category = d[p];
|
|
1664
|
+
var categoryIndex = config.dimensions[p].yscale.domain().indexOf(category);
|
|
1665
|
+
var categoryRangeValue = config.dimensions[p].yscale.range()[categoryIndex];
|
|
1666
|
+
return categoryRangeValue >= ranges[p][0] && categoryRangeValue <= ranges[p][1];
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
return config.data.filter(function (d) {
|
|
1670
|
+
return actives.every(function (p, dimension) {
|
|
1671
|
+
return within[config.dimensions[p].type](d, p, dimension);
|
|
1672
|
+
});
|
|
1673
|
+
});
|
|
1674
|
+
} else {
|
|
1675
|
+
// need to get data from each brush instead of each axis
|
|
1676
|
+
// first must find active axes by iterating through all brushes
|
|
1677
|
+
// then go through similiar process as above.
|
|
1678
|
+
var multiBrushData = [];
|
|
1679
|
+
var _loop = function _loop(idx) {
|
|
1680
|
+
var brush = config.brushes[idx];
|
|
1681
|
+
var values = [];
|
|
1682
|
+
var ranger = brush.extent;
|
|
1683
|
+
var actives = [brush.data];
|
|
1684
|
+
if (typeof config.dimensions[brush.data].yscale.domain()[0] === 'number') {
|
|
1685
|
+
for (var _i = 0; _i < ranger.length; _i++) {
|
|
1686
|
+
if (actives.includes(brush.data) && config.flipAxes.includes(brush.data)) {
|
|
1687
|
+
values.push(config.dimensions[brush.data].yscale.invert(ranger[_i]));
|
|
1688
|
+
} else if (config.dimensions[brush.data].yscale() !== 1) {
|
|
1689
|
+
values.unshift(config.dimensions[brush.data].yscale.invert(ranger[_i]));
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
extents.push(values);
|
|
1693
|
+
for (var _ii2 = 0; _ii2 < extents.length; _ii2++) {
|
|
1694
|
+
if (extents[_ii2].length === 0) {
|
|
1695
|
+
extents[_ii2] = [1, 1];
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
} else {
|
|
1699
|
+
ranges[brush.data] = brush.extent;
|
|
1700
|
+
var _dimRange = config.dimensions[brush.data].yscale.range();
|
|
1701
|
+
config.dimensions[brush.data].yscale.domain();
|
|
1702
|
+
for (var _j = 0; _j < _dimRange.length; _j++) {
|
|
1703
|
+
if (_dimRange[_j] >= ranger[0] && _dimRange[_j] <= ranger[1] && actives.includes(brush.data) && config.flipAxes.includes(brush.data)) {
|
|
1704
|
+
values.push(_dimRange[_j]);
|
|
1705
|
+
} else if (_dimRange[_j] >= ranger[0] && _dimRange[_j] <= ranger[1]) {
|
|
1706
|
+
values.unshift(_dimRange[_j]);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
extents.push(values);
|
|
1710
|
+
for (var _ii3 = 0; _ii3 < extents.length; _ii3++) {
|
|
1711
|
+
if (extents[_ii3].length === 0) {
|
|
1712
|
+
extents[_ii3] = [1, 1];
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
var within = {
|
|
1717
|
+
date: function date(d, p, dimension) {
|
|
1718
|
+
var category = d[p];
|
|
1719
|
+
var categoryIndex = config.dimensions[p].yscale.domain().indexOf(category);
|
|
1720
|
+
var categoryRangeValue = config.dimensions[p].yscale.range()[categoryIndex];
|
|
1721
|
+
return categoryRangeValue >= ranges[p][0] && categoryRangeValue <= ranges[p][1];
|
|
1722
|
+
},
|
|
1723
|
+
number: function number(d, p, dimension) {
|
|
1724
|
+
return extents[idx][0] <= d[p] && d[p] <= extents[idx][1];
|
|
1725
|
+
},
|
|
1726
|
+
string: function string(d, p, dimension) {
|
|
1727
|
+
var category = d[p];
|
|
1728
|
+
var categoryIndex = config.dimensions[p].yscale.domain().indexOf(category);
|
|
1729
|
+
var categoryRangeValue = config.dimensions[p].yscale.range()[categoryIndex];
|
|
1730
|
+
return categoryRangeValue >= ranges[p][0] && categoryRangeValue <= ranges[p][1];
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
|
|
1734
|
+
// filter data, but instead of returning it now,
|
|
1735
|
+
// put it into multiBrush data which is returned after
|
|
1736
|
+
// all brushes are iterated through.
|
|
1737
|
+
var filtered = config.data.filter(function (d) {
|
|
1738
|
+
return actives.every(function (p, dimension) {
|
|
1739
|
+
return within[config.dimensions[p].type](d, p, dimension);
|
|
1740
|
+
});
|
|
1741
|
+
});
|
|
1742
|
+
for (var z = 0; z < filtered.length; z++) {
|
|
1743
|
+
multiBrushData.push(filtered[z]);
|
|
1744
|
+
}
|
|
1745
|
+
actives = [];
|
|
1746
|
+
ranges = {};
|
|
1747
|
+
};
|
|
1748
|
+
for (var idx = 0; idx < config.brushes.length; idx++) {
|
|
1749
|
+
_loop(idx);
|
|
1750
|
+
}
|
|
1751
|
+
return multiBrushData;
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
};
|
|
1755
|
+
|
|
1756
|
+
var brushPredicate = function brushPredicate(brushGroup, config, pc) {
|
|
1757
|
+
return function () {
|
|
1758
|
+
var predicate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
1759
|
+
if (predicate === null) {
|
|
1760
|
+
return brushGroup.predicate;
|
|
1761
|
+
}
|
|
1762
|
+
predicate = String(predicate).toUpperCase();
|
|
1763
|
+
if (predicate !== 'AND' && predicate !== 'OR') {
|
|
1764
|
+
throw new Error('Invalid predicate ' + predicate);
|
|
1765
|
+
}
|
|
1766
|
+
brushGroup.predicate = predicate;
|
|
1767
|
+
config.brushed = brushGroup.currentMode().selected();
|
|
1768
|
+
pc.renderBrushed();
|
|
1769
|
+
return pc;
|
|
1770
|
+
};
|
|
1771
|
+
};
|
|
1772
|
+
var brushMode = function brushMode(brushGroup, config, pc) {
|
|
1773
|
+
return function () {
|
|
1774
|
+
var mode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
1775
|
+
if (mode === null) {
|
|
1776
|
+
return brushGroup.mode;
|
|
1777
|
+
}
|
|
1778
|
+
if (pc.brushModes().indexOf(mode) === -1) {
|
|
1779
|
+
throw new Error('pc.brushmode: Unsupported brush mode: ' + mode);
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// Make sure that we don't trigger unnecessary events by checking if the mode
|
|
1783
|
+
// actually changes.
|
|
1784
|
+
if (mode !== brushGroup.mode) {
|
|
1785
|
+
// When changing brush modes, the first thing we need to do is clearing any
|
|
1786
|
+
// brushes from the current mode, if any.
|
|
1787
|
+
if (brushGroup.mode !== 'None') {
|
|
1788
|
+
pc.brushReset();
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// Next, we need to 'uninstall' the current brushMode.
|
|
1792
|
+
brushGroup.modes[brushGroup.mode].uninstall(pc);
|
|
1793
|
+
// Finally, we can install the requested one.
|
|
1794
|
+
brushGroup.mode = mode;
|
|
1795
|
+
brushGroup.modes[brushGroup.mode].install();
|
|
1796
|
+
if (mode === 'None') {
|
|
1797
|
+
delete pc.brushPredicate;
|
|
1798
|
+
} else {
|
|
1799
|
+
pc.brushPredicate = brushPredicate(brushGroup, config, pc);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
return pc;
|
|
1803
|
+
};
|
|
1804
|
+
};
|
|
1805
|
+
|
|
1806
|
+
/**
|
|
1807
|
+
* dimension display names
|
|
1808
|
+
*
|
|
1809
|
+
* @param config
|
|
1810
|
+
* @param d
|
|
1811
|
+
* @returns {*}
|
|
1812
|
+
*/
|
|
1813
|
+
var dimensionLabels = function dimensionLabels(config) {
|
|
1814
|
+
return function (d) {
|
|
1815
|
+
return config.dimensions[d].title ? config.dimensions[d].title : d;
|
|
1816
|
+
};
|
|
1817
|
+
};
|
|
1818
|
+
|
|
1819
|
+
var flipAxisAndUpdatePCP = function flipAxisAndUpdatePCP(config, pc, axis) {
|
|
1820
|
+
return function (dimension) {
|
|
1821
|
+
pc.flip(dimension);
|
|
1822
|
+
pc.brushReset(dimension);
|
|
1823
|
+
|
|
1824
|
+
// select(this.parentElement)
|
|
1825
|
+
pc.selection.select('svg').selectAll('g.axis').filter(function (d) {
|
|
1826
|
+
return d === dimension;
|
|
1827
|
+
}).transition().duration(config.animationTime).call(axis.scale(config.dimensions[dimension].yscale));
|
|
1828
|
+
pc.render();
|
|
1829
|
+
};
|
|
1830
|
+
};
|
|
1831
|
+
|
|
1832
|
+
var rotateLabels = function rotateLabels(config, pc) {
|
|
1833
|
+
return function (event) {
|
|
1834
|
+
if (!config.rotateLabels) return;
|
|
1835
|
+
var delta = event.deltaY;
|
|
1836
|
+
delta = delta < 0 ? -5 : delta;
|
|
1837
|
+
delta = delta > 0 ? 5 : delta;
|
|
1838
|
+
config.dimensionTitleRotation += delta;
|
|
1839
|
+
pc.svg.selectAll('text.label').attr('transform', 'translate(0,-5) rotate(' + config.dimensionTitleRotation + ')');
|
|
1840
|
+
event.preventDefault();
|
|
1841
|
+
};
|
|
1842
|
+
};
|
|
1843
|
+
|
|
1844
|
+
var _this$3 = undefined;
|
|
1845
|
+
var updateAxes = function updateAxes(config, pc, position, axis, flags) {
|
|
1846
|
+
return function () {
|
|
1847
|
+
var animationTime = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
1848
|
+
if (animationTime === null) {
|
|
1849
|
+
animationTime = config.animationTime;
|
|
1850
|
+
}
|
|
1851
|
+
var g_data = pc.svg.selectAll('.dimension').data(pc.getOrderedDimensionKeys());
|
|
1852
|
+
// Enter
|
|
1853
|
+
g_data.enter().append('svg:g').attr('class', 'dimension').attr('transform', function (p) {
|
|
1854
|
+
return 'translate(' + position(p) + ')';
|
|
1855
|
+
}).style('opacity', 0).append('svg:g').attr('class', 'axis').attr('transform', 'translate(0,0)').each(function (d) {
|
|
1856
|
+
var axisElement = d3Selection.select(this).call(pc.applyAxisConfig(axis, config.dimensions[d]));
|
|
1857
|
+
axisElement.selectAll('path').style('fill', 'none').style('stroke', '#222').style('shape-rendering', 'crispEdges');
|
|
1858
|
+
axisElement.selectAll('line').style('fill', 'none').style('stroke', '#222').style('shape-rendering', 'crispEdges');
|
|
1859
|
+
}).append('svg:text').attr('text-anchor', 'middle').attr('class', 'label').attr('x', 0).attr('y', 0).attr('transform', 'translate(0,-5) rotate(' + config.dimensionTitleRotation + ')').text(dimensionLabels(config)).on('dblclick', flipAxisAndUpdatePCP(config, pc, axis)).on('wheel', rotateLabels(config, pc));
|
|
1860
|
+
|
|
1861
|
+
// Update
|
|
1862
|
+
g_data.attr('opacity', 0);
|
|
1863
|
+
g_data.select('.axis').transition().duration(animationTime).each(function (d) {
|
|
1864
|
+
d3Selection.select(this).call(pc.applyAxisConfig(axis, config.dimensions[d]));
|
|
1865
|
+
});
|
|
1866
|
+
g_data.select('.label').transition().duration(animationTime).text(dimensionLabels(config)).attr('transform', 'translate(0,-5) rotate(' + config.dimensionTitleRotation + ')');
|
|
1867
|
+
|
|
1868
|
+
// Exit
|
|
1869
|
+
g_data.exit().remove();
|
|
1870
|
+
var g = pc.svg.selectAll('.dimension');
|
|
1871
|
+
g.transition().duration(animationTime).attr('transform', function (p) {
|
|
1872
|
+
return 'translate(' + position(p) + ')';
|
|
1873
|
+
}).style('opacity', 1);
|
|
1874
|
+
pc.svg.selectAll('.axis').transition().duration(animationTime).each(function (d) {
|
|
1875
|
+
d3Selection.select(this).call(pc.applyAxisConfig(axis, config.dimensions[d]));
|
|
1876
|
+
});
|
|
1877
|
+
if (flags.brushable) pc.brushable();
|
|
1878
|
+
if (flags.reorderable) pc.reorderable();
|
|
1879
|
+
if (pc.brushMode() !== 'None') {
|
|
1880
|
+
var mode = pc.brushMode();
|
|
1881
|
+
pc.brushMode('None');
|
|
1882
|
+
pc.brushMode(mode);
|
|
1883
|
+
}
|
|
1884
|
+
return _this$3;
|
|
1885
|
+
};
|
|
1886
|
+
};
|
|
1887
|
+
|
|
1888
|
+
/** adjusts an axis' default range [h()+1, 1] if a NullValueSeparator is set */
|
|
1889
|
+
var getRange = function getRange(config) {
|
|
1890
|
+
var h = config.height - config.margin.top - config.margin.bottom;
|
|
1891
|
+
if (config.nullValueSeparator == 'bottom') {
|
|
1892
|
+
return [h + 1 - config.nullValueSeparatorPadding.bottom - config.nullValueSeparatorPadding.top, 1];
|
|
1893
|
+
} else if (config.nullValueSeparator == 'top') {
|
|
1894
|
+
return [h + 1, 1 + config.nullValueSeparatorPadding.bottom + config.nullValueSeparatorPadding.top];
|
|
1895
|
+
}
|
|
1896
|
+
return [h + 1, 1];
|
|
1897
|
+
};
|
|
1898
|
+
|
|
1899
|
+
var autoscale = function autoscale(config, pc, xscale, ctx) {
|
|
1900
|
+
return function () {
|
|
1901
|
+
// yscale
|
|
1902
|
+
var defaultScales = {
|
|
1903
|
+
date: function date(k) {
|
|
1904
|
+
var _extent = d3Array.extent(config.data, function (d) {
|
|
1905
|
+
return d[k] ? d[k].getTime() : null;
|
|
1906
|
+
});
|
|
1907
|
+
// special case if single value
|
|
1908
|
+
if (_extent[0] === _extent[1]) {
|
|
1909
|
+
return d3Scale.scalePoint().domain(_extent).range(getRange(config));
|
|
1910
|
+
}
|
|
1911
|
+
if (config.flipAxes.includes(k)) {
|
|
1912
|
+
_extent = _extent.map(function (val) {
|
|
1913
|
+
return tempDate.unshift(val);
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
return d3Scale.scaleTime().domain(_extent).range(getRange(config));
|
|
1917
|
+
},
|
|
1918
|
+
number: function number(k) {
|
|
1919
|
+
var _extent = d3Array.extent(config.data, function (d) {
|
|
1920
|
+
return +d[k];
|
|
1921
|
+
});
|
|
1922
|
+
// special case if single value
|
|
1923
|
+
if (_extent[0] === _extent[1]) {
|
|
1924
|
+
return d3Scale.scalePoint().domain(_extent).range(getRange(config));
|
|
1925
|
+
}
|
|
1926
|
+
if (config.flipAxes.includes(k)) {
|
|
1927
|
+
_extent = _extent.map(function (val) {
|
|
1928
|
+
return tempDate.unshift(val);
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
return d3Scale.scaleLinear().domain(_extent).range(getRange(config));
|
|
1932
|
+
},
|
|
1933
|
+
string: function string(k) {
|
|
1934
|
+
var counts = {},
|
|
1935
|
+
domain = [];
|
|
1936
|
+
// Let's get the count for each value so that we can sort the domain based
|
|
1937
|
+
// on the number of items for each value.
|
|
1938
|
+
config.data.map(function (p) {
|
|
1939
|
+
if (p[k] === undefined && config.nullValueSeparator !== 'undefined') {
|
|
1940
|
+
return null; // null values will be drawn beyond the horizontal null value separator!
|
|
1941
|
+
}
|
|
1942
|
+
if (counts[p[k]] === undefined) {
|
|
1943
|
+
counts[p[k]] = 1;
|
|
1944
|
+
} else {
|
|
1945
|
+
counts[p[k]] = counts[p[k]] + 1;
|
|
1946
|
+
}
|
|
1947
|
+
});
|
|
1948
|
+
if (config.flipAxes.includes(k)) {
|
|
1949
|
+
domain = Object.getOwnPropertyNames(counts).sort();
|
|
1950
|
+
} else {
|
|
1951
|
+
var tempArr = Object.getOwnPropertyNames(counts).sort();
|
|
1952
|
+
for (var i = 0; i < Object.getOwnPropertyNames(counts).length; i++) {
|
|
1953
|
+
domain.push(tempArr.pop());
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
//need to create an ordinal scale for categorical data
|
|
1958
|
+
var categoricalRange = [];
|
|
1959
|
+
if (domain.length === 1) {
|
|
1960
|
+
//edge case
|
|
1961
|
+
domain = [' ', domain[0], ' '];
|
|
1962
|
+
}
|
|
1963
|
+
var addBy = getRange(config)[0] / (domain.length - 1);
|
|
1964
|
+
for (var j = 0; j < domain.length; j++) {
|
|
1965
|
+
if (categoricalRange.length === 0) {
|
|
1966
|
+
categoricalRange.push(0);
|
|
1967
|
+
continue;
|
|
1968
|
+
}
|
|
1969
|
+
categoricalRange.push(categoricalRange[j - 1] + addBy);
|
|
1970
|
+
}
|
|
1971
|
+
return d3Scale.scaleOrdinal().domain(domain).range(categoricalRange);
|
|
1972
|
+
}
|
|
1973
|
+
};
|
|
1974
|
+
Object.keys(config.dimensions).forEach(function (k) {
|
|
1975
|
+
if (config.dimensions[k].yscale === undefined || config.dimensions[k].yscale === null) {
|
|
1976
|
+
config.dimensions[k].yscale = defaultScales[config.dimensions[k].type](k);
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
|
|
1980
|
+
// xscale
|
|
1981
|
+
// add padding for d3 >= v4 default 0.2
|
|
1982
|
+
xscale.range([0, w(config)]).padding(0.2);
|
|
1983
|
+
|
|
1984
|
+
// Retina display, etc.
|
|
1985
|
+
var devicePixelRatio = window.devicePixelRatio || 1;
|
|
1986
|
+
|
|
1987
|
+
// canvas sizes
|
|
1988
|
+
pc.selection.selectAll('canvas').style('margin-top', config.margin.top + 'px').style('margin-left', config.margin.left + 'px').style('width', w(config) + 2 + 'px').style('height', h(config) + 2 + 'px').attr('width', (w(config) + 2) * devicePixelRatio).attr('height', (h(config) + 2) * devicePixelRatio);
|
|
1989
|
+
// default styles, needs to be set when canvas width changes
|
|
1990
|
+
ctx.foreground.strokeStyle = config.color;
|
|
1991
|
+
ctx.foreground.lineWidth = config.lineWidth;
|
|
1992
|
+
ctx.foreground.globalCompositeOperation = config.composite;
|
|
1993
|
+
ctx.foreground.globalAlpha = config.alpha;
|
|
1994
|
+
ctx.foreground.scale(devicePixelRatio, devicePixelRatio);
|
|
1995
|
+
ctx.brushed.strokeStyle = config.brushedColor;
|
|
1996
|
+
ctx.brushed.lineWidth = config.lineWidth;
|
|
1997
|
+
ctx.brushed.globalCompositeOperation = config.composite;
|
|
1998
|
+
ctx.brushed.globalAlpha = config.alpha;
|
|
1999
|
+
ctx.brushed.scale(devicePixelRatio, devicePixelRatio);
|
|
2000
|
+
ctx.highlight.lineWidth = config.highlightedLineWidth;
|
|
2001
|
+
ctx.highlight.scale(devicePixelRatio, devicePixelRatio);
|
|
2002
|
+
ctx.marked.lineWidth = config.markedLineWidth;
|
|
2003
|
+
ctx.marked.shadowColor = config.markedShadowColor;
|
|
2004
|
+
ctx.marked.shadowBlur = config.markedShadowBlur;
|
|
2005
|
+
ctx.marked.scale(devicePixelRatio, devicePixelRatio);
|
|
2006
|
+
return this;
|
|
2007
|
+
};
|
|
2008
|
+
};
|
|
2009
|
+
|
|
2010
|
+
var brushable = function brushable(config, pc, flags) {
|
|
2011
|
+
return function () {
|
|
2012
|
+
if (!pc.g()) {
|
|
2013
|
+
pc.createAxes();
|
|
2014
|
+
}
|
|
2015
|
+
var g = pc.g();
|
|
2016
|
+
|
|
2017
|
+
// Add and store a brush for each axis.
|
|
2018
|
+
g.append('svg:g').attr('class', 'brush').each(function (d) {
|
|
2019
|
+
if (config.dimensions[d] !== undefined) {
|
|
2020
|
+
config.dimensions[d]['brush'] = d3Brush.brushY(d3Selection.select(this)).extent([[-15, 0], [15, config.dimensions[d].yscale.range()[0]]]);
|
|
2021
|
+
d3Selection.select(this).call(config.dimensions[d]['brush'].on('start', function (event) {
|
|
2022
|
+
if (event.sourceEvent !== null && !event.sourceEvent.ctrlKey) {
|
|
2023
|
+
pc.brushReset();
|
|
2024
|
+
}
|
|
2025
|
+
}).on('brush', function (event) {
|
|
2026
|
+
if (!event.sourceEvent.ctrlKey) {
|
|
2027
|
+
pc.brush();
|
|
2028
|
+
}
|
|
2029
|
+
}).on('end', function (event) {
|
|
2030
|
+
// save brush selection is ctrl key is held
|
|
2031
|
+
// store important brush information and
|
|
2032
|
+
// the html element of the selection,
|
|
2033
|
+
// to make a dummy selection element
|
|
2034
|
+
if (event.sourceEvent.ctrlKey) {
|
|
2035
|
+
var html = d3Selection.select(this).select('.selection').nodes()[0].outerHTML;
|
|
2036
|
+
html = html.replace('class="selection"', 'class="selection dummy' + ' selection-' + config.brushes.length + '"');
|
|
2037
|
+
var dat = d3Selection.select(this).nodes()[0].__data__;
|
|
2038
|
+
var brush = {
|
|
2039
|
+
id: config.brushes.length,
|
|
2040
|
+
extent: d3Brush.brushSelection(this),
|
|
2041
|
+
html: html,
|
|
2042
|
+
data: dat
|
|
2043
|
+
};
|
|
2044
|
+
config.brushes.push(brush);
|
|
2045
|
+
d3Selection.select(d3Selection.select(this).nodes()[0].parentNode).select('.axis').nodes()[0].outerHTML += html;
|
|
2046
|
+
pc.brush();
|
|
2047
|
+
config.dimensions[d].brush.move(d3Selection.select(this, null));
|
|
2048
|
+
d3Selection.select(this).select('.selection').attr('style', 'display:none');
|
|
2049
|
+
pc.brushable();
|
|
2050
|
+
} else {
|
|
2051
|
+
pc.brush();
|
|
2052
|
+
}
|
|
2053
|
+
}));
|
|
2054
|
+
d3Selection.select(this).on('dblclick', function () {
|
|
2055
|
+
pc.brushReset(d);
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
});
|
|
2059
|
+
flags.brushable = true;
|
|
2060
|
+
return this;
|
|
2061
|
+
};
|
|
2062
|
+
};
|
|
2063
|
+
|
|
2064
|
+
var commonScale = function commonScale(config, pc) {
|
|
2065
|
+
return function (global, type) {
|
|
2066
|
+
var t = type || 'number';
|
|
2067
|
+
if (typeof global === 'undefined') {
|
|
2068
|
+
global = true;
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
// try to autodetect dimensions and create scales
|
|
2072
|
+
if (!Object.keys(config.dimensions).length) {
|
|
2073
|
+
pc.detectDimensions();
|
|
2074
|
+
}
|
|
2075
|
+
pc.autoscale();
|
|
2076
|
+
|
|
2077
|
+
// scales of the same type
|
|
2078
|
+
var scales = Object.keys(config.dimensions).filter(function (p) {
|
|
2079
|
+
return config.dimensions[p].type == t;
|
|
2080
|
+
});
|
|
2081
|
+
if (global) {
|
|
2082
|
+
var _extent = d3Array.extent(scales.map(function (d) {
|
|
2083
|
+
return config.dimensions[d].yscale.domain();
|
|
2084
|
+
}).reduce(function (cur, acc) {
|
|
2085
|
+
return cur.concat(acc);
|
|
2086
|
+
}));
|
|
2087
|
+
scales.forEach(function (d) {
|
|
2088
|
+
config.dimensions[d].yscale.domain(_extent);
|
|
2089
|
+
});
|
|
2090
|
+
} else {
|
|
2091
|
+
scales.forEach(function (d) {
|
|
2092
|
+
config.dimensions[d].yscale.domain(d3Array.extent(config.data, function (d) {
|
|
2093
|
+
return +d[k];
|
|
2094
|
+
}));
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
// update centroids
|
|
2099
|
+
if (config.bundleDimension !== null) {
|
|
2100
|
+
pc.bundleDimension(config.bundleDimension);
|
|
2101
|
+
}
|
|
2102
|
+
return this;
|
|
2103
|
+
};
|
|
2104
|
+
};
|
|
2105
|
+
|
|
2106
|
+
var computeRealCentroids = function computeRealCentroids(config, position) {
|
|
2107
|
+
return function (row) {
|
|
2108
|
+
return Object.keys(config.dimensions).map(function (d) {
|
|
2109
|
+
var x = position(d);
|
|
2110
|
+
var y = config.dimensions[d].yscale(row[d]);
|
|
2111
|
+
return [x, y];
|
|
2112
|
+
});
|
|
2113
|
+
};
|
|
2114
|
+
};
|
|
2115
|
+
|
|
2116
|
+
var isValid = function isValid(d) {
|
|
2117
|
+
return d !== null && d !== undefined;
|
|
2118
|
+
};
|
|
2119
|
+
var applyDimensionDefaults = function applyDimensionDefaults(config, pc) {
|
|
2120
|
+
return function (dims) {
|
|
2121
|
+
var types = pc.detectDimensionTypes(config.data);
|
|
2122
|
+
dims = dims ? dims : Object.keys(types);
|
|
2123
|
+
return dims.reduce(function (acc, cur, i) {
|
|
2124
|
+
var k = config.dimensions[cur] ? config.dimensions[cur] : {};
|
|
2125
|
+
acc[cur] = _objectSpread2(_objectSpread2({}, k), {}, {
|
|
2126
|
+
orient: isValid(k.orient) ? k.orient : 'left',
|
|
2127
|
+
ticks: isValid(k.ticks) ? k.ticks : 5,
|
|
2128
|
+
innerTickSize: isValid(k.innerTickSize) ? k.innerTickSize : 6,
|
|
2129
|
+
outerTickSize: isValid(k.outerTickSize) ? k.outerTickSize : 0,
|
|
2130
|
+
tickPadding: isValid(k.tickPadding) ? k.tickPadding : 3,
|
|
2131
|
+
type: isValid(k.type) ? k.type : types[cur],
|
|
2132
|
+
index: isValid(k.index) ? k.index : i
|
|
2133
|
+
});
|
|
2134
|
+
return acc;
|
|
2135
|
+
}, {});
|
|
2136
|
+
};
|
|
2137
|
+
};
|
|
2138
|
+
|
|
2139
|
+
/**
|
|
2140
|
+
* Create static SVG axes with dimension names, ticks, and labels.
|
|
2141
|
+
*
|
|
2142
|
+
* @param config
|
|
2143
|
+
* @param pc
|
|
2144
|
+
* @param xscale
|
|
2145
|
+
* @param flags
|
|
2146
|
+
* @param axis
|
|
2147
|
+
* @returns {Function}
|
|
2148
|
+
*/
|
|
2149
|
+
var createAxes = function createAxes(config, pc, xscale, flags, axis) {
|
|
2150
|
+
return function () {
|
|
2151
|
+
if (pc.g() !== undefined) {
|
|
2152
|
+
pc.removeAxes();
|
|
2153
|
+
}
|
|
2154
|
+
// Add a group element for each dimension.
|
|
2155
|
+
pc._g = pc.svg.selectAll('.dimension').data(pc.getOrderedDimensionKeys(), function (d) {
|
|
2156
|
+
return d;
|
|
2157
|
+
}).enter().append('svg:g').attr('class', 'dimension').attr('transform', function (d) {
|
|
2158
|
+
return 'translate(' + xscale(d) + ')';
|
|
2159
|
+
});
|
|
2160
|
+
// Add an axis and title.
|
|
2161
|
+
pc._g.append('svg:g').attr('class', 'axis').attr('transform', 'translate(0,0)').each(function (d) {
|
|
2162
|
+
var axisElement = d3Selection.select(this).call(pc.applyAxisConfig(axis, config.dimensions[d]));
|
|
2163
|
+
axisElement.selectAll('path').style('fill', 'none').style('stroke', '#222').style('shape-rendering', 'crispEdges');
|
|
2164
|
+
axisElement.selectAll('line').style('fill', 'none').style('stroke', '#222').style('shape-rendering', 'crispEdges');
|
|
2165
|
+
}).append('svg:text').attr('text-anchor', 'middle').attr('y', 0).attr('transform', 'translate(0,-5) rotate(' + config.dimensionTitleRotation + ')').attr('x', 0).attr('class', 'label').text(dimensionLabels(config)).on('dblclick', flipAxisAndUpdatePCP(config, pc, axis)).on('wheel', rotateLabels(config, pc));
|
|
2166
|
+
if (config.nullValueSeparator === 'top') {
|
|
2167
|
+
pc.svg.append('line').attr('x1', 0).attr('y1', 1 + config.nullValueSeparatorPadding.top).attr('x2', w(config)).attr('y2', 1 + config.nullValueSeparatorPadding.top).attr('stroke-width', 1).attr('stroke', '#777').attr('fill', 'none').attr('shape-rendering', 'crispEdges');
|
|
2168
|
+
} else if (config.nullValueSeparator === 'bottom') {
|
|
2169
|
+
pc.svg.append('line').attr('x1', 0).attr('y1', h(config) + 1 - config.nullValueSeparatorPadding.bottom).attr('x2', w(config)).attr('y2', h(config) + 1 - config.nullValueSeparatorPadding.bottom).attr('stroke-width', 1).attr('stroke', '#777').attr('fill', 'none').attr('shape-rendering', 'crispEdges');
|
|
2170
|
+
}
|
|
2171
|
+
flags.axes = true;
|
|
2172
|
+
return this;
|
|
2173
|
+
};
|
|
2174
|
+
};
|
|
2175
|
+
|
|
2176
|
+
var _this$2 = undefined;
|
|
2177
|
+
|
|
2178
|
+
//draw dots with radius r on the axis line where data intersects
|
|
2179
|
+
var axisDots = function axisDots(config, pc, position) {
|
|
2180
|
+
return function (_r) {
|
|
2181
|
+
var r = _r || 0.1;
|
|
2182
|
+
var ctx = pc.ctx.dots;
|
|
2183
|
+
var startAngle = 0;
|
|
2184
|
+
var endAngle = 2 * Math.PI;
|
|
2185
|
+
ctx.globalAlpha = d3Array.min([1 / Math.pow(config.data.length, 1 / 2), 1]);
|
|
2186
|
+
config.data.forEach(function (d) {
|
|
2187
|
+
Object.entries(config.dimensions).forEach(function (_ref, i) {
|
|
2188
|
+
var _ref2 = _slicedToArray(_ref, 1),
|
|
2189
|
+
key = _ref2[0];
|
|
2190
|
+
ctx.beginPath();
|
|
2191
|
+
ctx.arc(position(key), config.dimensions[key].yscale(d[key]), r, startAngle, endAngle);
|
|
2192
|
+
ctx.stroke();
|
|
2193
|
+
ctx.fill();
|
|
2194
|
+
});
|
|
2195
|
+
});
|
|
2196
|
+
return _this$2;
|
|
2197
|
+
};
|
|
2198
|
+
};
|
|
2199
|
+
|
|
2200
|
+
var applyAxisConfig = function applyAxisConfig(axis, dimension) {
|
|
2201
|
+
var axisCfg;
|
|
2202
|
+
switch (dimension.orient) {
|
|
2203
|
+
case 'left':
|
|
2204
|
+
axisCfg = d3Axis.axisLeft(dimension.yscale);
|
|
2205
|
+
break;
|
|
2206
|
+
case 'right':
|
|
2207
|
+
axisCfg = d3Axis.axisRight(dimension.yscale);
|
|
2208
|
+
break;
|
|
2209
|
+
case 'top':
|
|
2210
|
+
axisCfg = d3Axis.axisTop(dimension.yscale);
|
|
2211
|
+
break;
|
|
2212
|
+
case 'bottom':
|
|
2213
|
+
axisCfg = d3Axis.axisBottom(dimension.yscale);
|
|
2214
|
+
break;
|
|
2215
|
+
default:
|
|
2216
|
+
axisCfg = d3Axis.axisLeft(dimension.yscale);
|
|
2217
|
+
break;
|
|
2218
|
+
}
|
|
2219
|
+
axisCfg.ticks(dimension.ticks).tickValues(dimension.tickValues).tickSizeInner(dimension.innerTickSize).tickSizeOuter(dimension.outerTickSize).tickPadding(dimension.tickPadding).tickFormat(dimension.tickFormat);
|
|
2220
|
+
return axisCfg;
|
|
2221
|
+
};
|
|
2222
|
+
|
|
2223
|
+
// Jason Davies, http://bl.ocks.org/1341281
|
|
2224
|
+
var reorderable = function reorderable(config, pc, xscale, position, dragging, flags) {
|
|
2225
|
+
return function () {
|
|
2226
|
+
if (pc.g() === undefined) pc.createAxes();
|
|
2227
|
+
var g = pc.g();
|
|
2228
|
+
g.style('cursor', 'move').call(d3Drag.drag().on('start', function (event, d) {
|
|
2229
|
+
dragging[d] = this.__origin__ = xscale(d);
|
|
2230
|
+
}).on('drag', function (event, d) {
|
|
2231
|
+
dragging[d] = Math.min(w(config), Math.max(0, this.__origin__ += event.dx));
|
|
2232
|
+
pc.sortDimensions();
|
|
2233
|
+
xscale.domain(pc.getOrderedDimensionKeys());
|
|
2234
|
+
pc.render();
|
|
2235
|
+
g.attr('transform', function (d) {
|
|
2236
|
+
return 'translate(' + position(d) + ')';
|
|
2237
|
+
});
|
|
2238
|
+
}).on('end', function (event, d) {
|
|
2239
|
+
delete this.__origin__;
|
|
2240
|
+
delete dragging[d];
|
|
2241
|
+
d3Selection.select(this).transition().attr('transform', 'translate(' + xscale(d) + ')');
|
|
2242
|
+
pc.render();
|
|
2243
|
+
pc.renderMarked();
|
|
2244
|
+
}));
|
|
2245
|
+
flags.reorderable = true;
|
|
2246
|
+
return this;
|
|
2247
|
+
};
|
|
2248
|
+
};
|
|
2249
|
+
|
|
2250
|
+
// rescale for height, width and margins
|
|
2251
|
+
// TODO currently assumes chart is brushable, and destroys old brushes
|
|
2252
|
+
var resize = function resize(config, pc, flags, events) {
|
|
2253
|
+
return function () {
|
|
2254
|
+
// selection size
|
|
2255
|
+
pc.selection.select('svg').attr('width', config.width).attr('height', config.height);
|
|
2256
|
+
pc.svg.attr('transform', 'translate(' + config.margin.left + ',' + config.margin.top + ')');
|
|
2257
|
+
|
|
2258
|
+
// FIXME: the current brush state should pass through
|
|
2259
|
+
if (flags.brushable) pc.brushReset();
|
|
2260
|
+
|
|
2261
|
+
// scales
|
|
2262
|
+
pc.autoscale();
|
|
2263
|
+
|
|
2264
|
+
// axes, destroys old brushes.
|
|
2265
|
+
if (pc.g()) pc.createAxes();
|
|
2266
|
+
if (flags.brushable) pc.brushable();
|
|
2267
|
+
if (flags.reorderable) pc.reorderable();
|
|
2268
|
+
events.call('resize', this, {
|
|
2269
|
+
width: config.width,
|
|
2270
|
+
height: config.height,
|
|
2271
|
+
margin: config.margin
|
|
2272
|
+
});
|
|
2273
|
+
return this;
|
|
2274
|
+
};
|
|
2275
|
+
};
|
|
2276
|
+
|
|
2277
|
+
// Reorder dimensions, such that the highest value (visually) is on the left and
|
|
2278
|
+
// the lowest on the right. Visual values are determined by the data values in
|
|
2279
|
+
// the given row.
|
|
2280
|
+
var reorder = function reorder(config, pc, xscale) {
|
|
2281
|
+
return function (rowdata) {
|
|
2282
|
+
var firstDim = pc.getOrderedDimensionKeys()[0];
|
|
2283
|
+
pc.sortDimensionsByRowData(rowdata);
|
|
2284
|
+
// NOTE: this is relatively cheap given that:
|
|
2285
|
+
// number of dimensions < number of data items
|
|
2286
|
+
// Thus we check equality of order to prevent rerendering when this is the case.
|
|
2287
|
+
var reordered = firstDim !== pc.getOrderedDimensionKeys()[0];
|
|
2288
|
+
if (reordered) {
|
|
2289
|
+
xscale.domain(pc.getOrderedDimensionKeys());
|
|
2290
|
+
var highlighted = config.highlighted.slice(0);
|
|
2291
|
+
pc.unhighlight();
|
|
2292
|
+
var marked = config.marked.slice(0);
|
|
2293
|
+
pc.unmark();
|
|
2294
|
+
var g = pc.g();
|
|
2295
|
+
g.transition().duration(1500).attr('transform', function (d) {
|
|
2296
|
+
return 'translate(' + xscale(d) + ')';
|
|
2297
|
+
});
|
|
2298
|
+
pc.render();
|
|
2299
|
+
|
|
2300
|
+
// pc.highlight() does not check whether highlighted is length zero, so we do that here.
|
|
2301
|
+
if (highlighted.length !== 0) {
|
|
2302
|
+
pc.highlight(highlighted);
|
|
2303
|
+
}
|
|
2304
|
+
if (marked.length !== 0) {
|
|
2305
|
+
pc.mark(marked);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
};
|
|
2309
|
+
};
|
|
2310
|
+
|
|
2311
|
+
var sortDimensions = function sortDimensions(config, position) {
|
|
2312
|
+
return function () {
|
|
2313
|
+
var copy = Object.assign({}, config.dimensions);
|
|
2314
|
+
var positionSortedKeys = Object.keys(config.dimensions).sort(function (a, b) {
|
|
2315
|
+
return position(a) - position(b) === 0 ? 1 : position(a) - position(b);
|
|
2316
|
+
});
|
|
2317
|
+
config.dimensions = {};
|
|
2318
|
+
positionSortedKeys.forEach(function (p, i) {
|
|
2319
|
+
config.dimensions[p] = copy[p];
|
|
2320
|
+
config.dimensions[p].index = i;
|
|
2321
|
+
});
|
|
2322
|
+
};
|
|
2323
|
+
};
|
|
2324
|
+
|
|
2325
|
+
var sortDimensionsByRowData = function sortDimensionsByRowData(config) {
|
|
2326
|
+
return function (rowdata) {
|
|
2327
|
+
var copy = Object.assign({}, config.dimensions);
|
|
2328
|
+
var positionSortedKeys = Object.keys(config.dimensions).sort(function (a, b) {
|
|
2329
|
+
var pixelDifference = config.dimensions[a].yscale(rowdata[a]) - config.dimensions[b].yscale(rowdata[b]);
|
|
2330
|
+
|
|
2331
|
+
// Array.sort is not necessarily stable, this means that if pixelDifference is zero
|
|
2332
|
+
// the ordering of dimensions might change unexpectedly. This is solved by sorting on
|
|
2333
|
+
// variable name in that case.
|
|
2334
|
+
return pixelDifference === 0 ? a.localeCompare(b) : pixelDifference;
|
|
2335
|
+
});
|
|
2336
|
+
config.dimensions = {};
|
|
2337
|
+
positionSortedKeys.forEach(function (p, i) {
|
|
2338
|
+
config.dimensions[p] = copy[p];
|
|
2339
|
+
config.dimensions[p].index = i;
|
|
2340
|
+
});
|
|
2341
|
+
};
|
|
2342
|
+
};
|
|
2343
|
+
|
|
2344
|
+
var isBrushed = function isBrushed(config, brushGroup) {
|
|
2345
|
+
if (config.brushed && config.brushed.length !== config.data.length) return true;
|
|
2346
|
+
var object = brushGroup.currentMode().brushState();
|
|
2347
|
+
for (var key in object) {
|
|
2348
|
+
if (object.hasOwnProperty(key)) {
|
|
2349
|
+
return true;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
return false;
|
|
2353
|
+
};
|
|
2354
|
+
|
|
2355
|
+
var clear = function clear(config, pc, ctx, brushGroup) {
|
|
2356
|
+
return function (layer) {
|
|
2357
|
+
ctx[layer].clearRect(0, 0, w(config) + 2, h(config) + 2);
|
|
2358
|
+
|
|
2359
|
+
// This will make sure that the foreground items are transparent
|
|
2360
|
+
// without the need for changing the opacity style of the foreground canvas
|
|
2361
|
+
// as this would stop the css styling from working
|
|
2362
|
+
if (layer === 'brushed' && isBrushed(config, brushGroup)) {
|
|
2363
|
+
ctx.brushed.fillStyle = pc.selection.style('background-color');
|
|
2364
|
+
ctx.brushed.globalAlpha = 1 - config.alphaOnBrushed;
|
|
2365
|
+
ctx.brushed.fillRect(0, 0, w(config) + 2, h(config) + 2);
|
|
2366
|
+
ctx.brushed.globalAlpha = config.alpha;
|
|
2367
|
+
}
|
|
2368
|
+
return this;
|
|
2369
|
+
};
|
|
2370
|
+
};
|
|
2371
|
+
|
|
2372
|
+
const PRECISION = 1e-6;
|
|
2373
|
+
|
|
2374
|
+
class Matrix {
|
|
2375
|
+
constructor(elements) {
|
|
2376
|
+
this.setElements(elements);
|
|
2377
|
+
}
|
|
2378
|
+
e(i, j) {
|
|
2379
|
+
if (i < 1 || i > this.elements.length || j < 1 || j > this.elements[0].length) {
|
|
2380
|
+
return null;
|
|
2381
|
+
}
|
|
2382
|
+
return this.elements[i - 1][j - 1];
|
|
2383
|
+
}
|
|
2384
|
+
row(i) {
|
|
2385
|
+
if (i > this.elements.length) {
|
|
2386
|
+
return null;
|
|
2387
|
+
}
|
|
2388
|
+
return new Vector(this.elements[i - 1]);
|
|
2389
|
+
}
|
|
2390
|
+
col(j) {
|
|
2391
|
+
if (this.elements.length === 0) {
|
|
2392
|
+
return null;
|
|
2393
|
+
}
|
|
2394
|
+
if (j > this.elements[0].length) {
|
|
2395
|
+
return null;
|
|
2396
|
+
}
|
|
2397
|
+
var col = [],
|
|
2398
|
+
n = this.elements.length;
|
|
2399
|
+
for (var i = 0; i < n; i++) {
|
|
2400
|
+
col.push(this.elements[i][j - 1]);
|
|
2401
|
+
}
|
|
2402
|
+
return new Vector(col);
|
|
2403
|
+
}
|
|
2404
|
+
dimensions() {
|
|
2405
|
+
var cols = this.elements.length === 0 ? 0 : this.elements[0].length;
|
|
2406
|
+
return {
|
|
2407
|
+
rows: this.elements.length,
|
|
2408
|
+
cols: cols
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
rows() {
|
|
2412
|
+
return this.elements.length;
|
|
2413
|
+
}
|
|
2414
|
+
cols() {
|
|
2415
|
+
if (this.elements.length === 0) {
|
|
2416
|
+
return 0;
|
|
2417
|
+
}
|
|
2418
|
+
return this.elements[0].length;
|
|
2419
|
+
}
|
|
2420
|
+
eql(matrix) {
|
|
2421
|
+
var M = matrix.elements || matrix;
|
|
2422
|
+
if (!M[0] || typeof M[0][0] === 'undefined') {
|
|
2423
|
+
M = new Matrix(M).elements;
|
|
2424
|
+
}
|
|
2425
|
+
if (this.elements.length === 0 || M.length === 0) {
|
|
2426
|
+
return this.elements.length === M.length;
|
|
2427
|
+
}
|
|
2428
|
+
if (this.elements.length !== M.length) {
|
|
2429
|
+
return false;
|
|
2430
|
+
}
|
|
2431
|
+
if (this.elements[0].length !== M[0].length) {
|
|
2432
|
+
return false;
|
|
2433
|
+
}
|
|
2434
|
+
var i = this.elements.length,
|
|
2435
|
+
nj = this.elements[0].length,
|
|
2436
|
+
j;
|
|
2437
|
+
while (i--) {
|
|
2438
|
+
j = nj;
|
|
2439
|
+
while (j--) {
|
|
2440
|
+
if (Math.abs(this.elements[i][j] - M[i][j]) > PRECISION) {
|
|
2441
|
+
return false;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
return true;
|
|
2446
|
+
}
|
|
2447
|
+
dup() {
|
|
2448
|
+
return new Matrix(this.elements);
|
|
2449
|
+
}
|
|
2450
|
+
map(fn, context) {
|
|
2451
|
+
if (this.elements.length === 0) {
|
|
2452
|
+
return new Matrix([]);
|
|
2453
|
+
}
|
|
2454
|
+
var els = [],
|
|
2455
|
+
i = this.elements.length,
|
|
2456
|
+
nj = this.elements[0].length,
|
|
2457
|
+
j;
|
|
2458
|
+
while (i--) {
|
|
2459
|
+
j = nj;
|
|
2460
|
+
els[i] = [];
|
|
2461
|
+
while (j--) {
|
|
2462
|
+
els[i][j] = fn.call(context, this.elements[i][j], i + 1, j + 1);
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
return new Matrix(els);
|
|
2466
|
+
}
|
|
2467
|
+
isSameSizeAs(matrix) {
|
|
2468
|
+
var M = matrix.elements || matrix;
|
|
2469
|
+
if (typeof M[0][0] === 'undefined') {
|
|
2470
|
+
M = new Matrix(M).elements;
|
|
2471
|
+
}
|
|
2472
|
+
if (this.elements.length === 0) {
|
|
2473
|
+
return M.length === 0;
|
|
2474
|
+
}
|
|
2475
|
+
return this.elements.length === M.length && this.elements[0].length === M[0].length;
|
|
2476
|
+
}
|
|
2477
|
+
add(matrix) {
|
|
2478
|
+
if (this.elements.length === 0) {
|
|
2479
|
+
return this.map(function (x) {
|
|
2480
|
+
return x;
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
var M = matrix.elements || matrix;
|
|
2484
|
+
if (typeof M[0][0] === 'undefined') {
|
|
2485
|
+
M = new Matrix(M).elements;
|
|
2486
|
+
}
|
|
2487
|
+
if (!this.isSameSizeAs(M)) {
|
|
2488
|
+
return null;
|
|
2489
|
+
}
|
|
2490
|
+
return this.map(function (x, i, j) {
|
|
2491
|
+
return x + M[i - 1][j - 1];
|
|
2492
|
+
});
|
|
2493
|
+
}
|
|
2494
|
+
subtract(matrix) {
|
|
2495
|
+
if (this.elements.length === 0) {
|
|
2496
|
+
return this.map(function (x) {
|
|
2497
|
+
return x;
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
var M = matrix.elements || matrix;
|
|
2501
|
+
if (typeof M[0][0] === 'undefined') {
|
|
2502
|
+
M = new Matrix(M).elements;
|
|
2503
|
+
}
|
|
2504
|
+
if (!this.isSameSizeAs(M)) {
|
|
2505
|
+
return null;
|
|
2506
|
+
}
|
|
2507
|
+
return this.map(function (x, i, j) {
|
|
2508
|
+
return x - M[i - 1][j - 1];
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
canMultiplyFromLeft(matrix) {
|
|
2512
|
+
if (this.elements.length === 0) {
|
|
2513
|
+
return false;
|
|
2514
|
+
}
|
|
2515
|
+
var M = matrix.elements || matrix;
|
|
2516
|
+
if (typeof M[0][0] === 'undefined') {
|
|
2517
|
+
M = new Matrix(M).elements;
|
|
2518
|
+
}
|
|
2519
|
+
// this.columns should equal matrix.rows
|
|
2520
|
+
return this.elements[0].length === M.length;
|
|
2521
|
+
}
|
|
2522
|
+
multiply(matrix) {
|
|
2523
|
+
if (this.elements.length === 0) {
|
|
2524
|
+
return null;
|
|
2525
|
+
}
|
|
2526
|
+
if (!matrix.elements) {
|
|
2527
|
+
return this.map(function (x) {
|
|
2528
|
+
return x * matrix;
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
var returnVector = matrix.modulus ? true : false;
|
|
2532
|
+
var M = matrix.elements || matrix;
|
|
2533
|
+
if (typeof M[0][0] === 'undefined') {
|
|
2534
|
+
M = new Matrix(M).elements;
|
|
2535
|
+
}
|
|
2536
|
+
if (!this.canMultiplyFromLeft(M)) {
|
|
2537
|
+
return null;
|
|
2538
|
+
}
|
|
2539
|
+
var i = this.elements.length,
|
|
2540
|
+
nj = M[0].length,
|
|
2541
|
+
j;
|
|
2542
|
+
var cols = this.elements[0].length,
|
|
2543
|
+
c,
|
|
2544
|
+
elements = [],
|
|
2545
|
+
sum;
|
|
2546
|
+
while (i--) {
|
|
2547
|
+
j = nj;
|
|
2548
|
+
elements[i] = [];
|
|
2549
|
+
while (j--) {
|
|
2550
|
+
c = cols;
|
|
2551
|
+
sum = 0;
|
|
2552
|
+
while (c--) {
|
|
2553
|
+
sum += this.elements[i][c] * M[c][j];
|
|
2554
|
+
}
|
|
2555
|
+
elements[i][j] = sum;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
var M = new Matrix(elements);
|
|
2559
|
+
return returnVector ? M.col(1) : M;
|
|
2560
|
+
}
|
|
2561
|
+
minor(a, b, c, d) {
|
|
2562
|
+
if (this.elements.length === 0) {
|
|
2563
|
+
return null;
|
|
2564
|
+
}
|
|
2565
|
+
var elements = [],
|
|
2566
|
+
ni = c,
|
|
2567
|
+
i,
|
|
2568
|
+
nj,
|
|
2569
|
+
j;
|
|
2570
|
+
var rows = this.elements.length,
|
|
2571
|
+
cols = this.elements[0].length;
|
|
2572
|
+
while (ni--) {
|
|
2573
|
+
i = c - ni - 1;
|
|
2574
|
+
elements[i] = [];
|
|
2575
|
+
nj = d;
|
|
2576
|
+
while (nj--) {
|
|
2577
|
+
j = d - nj - 1;
|
|
2578
|
+
elements[i][j] = this.elements[(a + i - 1) % rows][(b + j - 1) % cols];
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
return new Matrix(elements);
|
|
2582
|
+
}
|
|
2583
|
+
transpose() {
|
|
2584
|
+
if (this.elements.length === 0) {
|
|
2585
|
+
return new Matrix([]);
|
|
2586
|
+
}
|
|
2587
|
+
var rows = this.elements.length,
|
|
2588
|
+
i,
|
|
2589
|
+
cols = this.elements[0].length,
|
|
2590
|
+
j;
|
|
2591
|
+
var elements = [],
|
|
2592
|
+
i = cols;
|
|
2593
|
+
while (i--) {
|
|
2594
|
+
j = rows;
|
|
2595
|
+
elements[i] = [];
|
|
2596
|
+
while (j--) {
|
|
2597
|
+
elements[i][j] = this.elements[j][i];
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
return new Matrix(elements);
|
|
2601
|
+
}
|
|
2602
|
+
isSquare() {
|
|
2603
|
+
var cols = this.elements.length === 0 ? 0 : this.elements[0].length;
|
|
2604
|
+
return this.elements.length === cols;
|
|
2605
|
+
}
|
|
2606
|
+
max() {
|
|
2607
|
+
if (this.elements.length === 0) {
|
|
2608
|
+
return null;
|
|
2609
|
+
}
|
|
2610
|
+
var m = 0,
|
|
2611
|
+
i = this.elements.length,
|
|
2612
|
+
nj = this.elements[0].length,
|
|
2613
|
+
j;
|
|
2614
|
+
while (i--) {
|
|
2615
|
+
j = nj;
|
|
2616
|
+
while (j--) {
|
|
2617
|
+
if (Math.abs(this.elements[i][j]) > Math.abs(m)) {
|
|
2618
|
+
m = this.elements[i][j];
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
return m;
|
|
2623
|
+
}
|
|
2624
|
+
indexOf(x) {
|
|
2625
|
+
if (this.elements.length === 0) {
|
|
2626
|
+
return null;
|
|
2627
|
+
}
|
|
2628
|
+
var ni = this.elements.length,
|
|
2629
|
+
i,
|
|
2630
|
+
nj = this.elements[0].length,
|
|
2631
|
+
j;
|
|
2632
|
+
for (i = 0; i < ni; i++) {
|
|
2633
|
+
for (j = 0; j < nj; j++) {
|
|
2634
|
+
if (this.elements[i][j] === x) {
|
|
2635
|
+
return {
|
|
2636
|
+
i: i + 1,
|
|
2637
|
+
j: j + 1
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
return null;
|
|
2643
|
+
}
|
|
2644
|
+
diagonal() {
|
|
2645
|
+
if (!this.isSquare) {
|
|
2646
|
+
return null;
|
|
2647
|
+
}
|
|
2648
|
+
var els = [],
|
|
2649
|
+
n = this.elements.length;
|
|
2650
|
+
for (var i = 0; i < n; i++) {
|
|
2651
|
+
els.push(this.elements[i][i]);
|
|
2652
|
+
}
|
|
2653
|
+
return new Vector(els);
|
|
2654
|
+
}
|
|
2655
|
+
toRightTriangular() {
|
|
2656
|
+
if (this.elements.length === 0) {
|
|
2657
|
+
return new Matrix([]);
|
|
2658
|
+
}
|
|
2659
|
+
var M = this.dup(),
|
|
2660
|
+
els;
|
|
2661
|
+
var n = this.elements.length,
|
|
2662
|
+
i,
|
|
2663
|
+
j,
|
|
2664
|
+
np = this.elements[0].length,
|
|
2665
|
+
p;
|
|
2666
|
+
for (i = 0; i < n; i++) {
|
|
2667
|
+
if (M.elements[i][i] === 0) {
|
|
2668
|
+
for (j = i + 1; j < n; j++) {
|
|
2669
|
+
if (M.elements[j][i] !== 0) {
|
|
2670
|
+
els = [];
|
|
2671
|
+
for (p = 0; p < np; p++) {
|
|
2672
|
+
els.push(M.elements[i][p] + M.elements[j][p]);
|
|
2673
|
+
}
|
|
2674
|
+
M.elements[i] = els;
|
|
2675
|
+
break;
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
if (M.elements[i][i] !== 0) {
|
|
2680
|
+
for (j = i + 1; j < n; j++) {
|
|
2681
|
+
var multiplier = M.elements[j][i] / M.elements[i][i];
|
|
2682
|
+
els = [];
|
|
2683
|
+
for (p = 0; p < np; p++) {
|
|
2684
|
+
// Elements with column numbers up to an including the number of the
|
|
2685
|
+
// row that we're subtracting can safely be set straight to zero,
|
|
2686
|
+
// since that's the point of this routine and it avoids having to
|
|
2687
|
+
// loop over and correct rounding errors later
|
|
2688
|
+
els.push(p <= i ? 0 : M.elements[j][p] - M.elements[i][p] * multiplier);
|
|
2689
|
+
}
|
|
2690
|
+
M.elements[j] = els;
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
return M;
|
|
2695
|
+
}
|
|
2696
|
+
determinant() {
|
|
2697
|
+
if (this.elements.length === 0) {
|
|
2698
|
+
return 1;
|
|
2699
|
+
}
|
|
2700
|
+
if (!this.isSquare()) {
|
|
2701
|
+
return null;
|
|
2702
|
+
}
|
|
2703
|
+
var M = this.toRightTriangular();
|
|
2704
|
+
var det = M.elements[0][0],
|
|
2705
|
+
n = M.elements.length;
|
|
2706
|
+
for (var i = 1; i < n; i++) {
|
|
2707
|
+
det = det * M.elements[i][i];
|
|
2708
|
+
}
|
|
2709
|
+
return det;
|
|
2710
|
+
}
|
|
2711
|
+
isSingular() {
|
|
2712
|
+
return this.isSquare() && this.determinant() === 0;
|
|
2713
|
+
}
|
|
2714
|
+
trace() {
|
|
2715
|
+
if (this.elements.length === 0) {
|
|
2716
|
+
return 0;
|
|
2717
|
+
}
|
|
2718
|
+
if (!this.isSquare()) {
|
|
2719
|
+
return null;
|
|
2720
|
+
}
|
|
2721
|
+
var tr = this.elements[0][0],
|
|
2722
|
+
n = this.elements.length;
|
|
2723
|
+
for (var i = 1; i < n; i++) {
|
|
2724
|
+
tr += this.elements[i][i];
|
|
2725
|
+
}
|
|
2726
|
+
return tr;
|
|
2727
|
+
}
|
|
2728
|
+
rank() {
|
|
2729
|
+
if (this.elements.length === 0) {
|
|
2730
|
+
return 0;
|
|
2731
|
+
}
|
|
2732
|
+
var M = this.toRightTriangular(),
|
|
2733
|
+
rank = 0;
|
|
2734
|
+
var i = this.elements.length,
|
|
2735
|
+
nj = this.elements[0].length,
|
|
2736
|
+
j;
|
|
2737
|
+
while (i--) {
|
|
2738
|
+
j = nj;
|
|
2739
|
+
while (j--) {
|
|
2740
|
+
if (Math.abs(M.elements[i][j]) > PRECISION) {
|
|
2741
|
+
rank++;
|
|
2742
|
+
break;
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
return rank;
|
|
2747
|
+
}
|
|
2748
|
+
augment(matrix) {
|
|
2749
|
+
if (this.elements.length === 0) {
|
|
2750
|
+
return this.dup();
|
|
2751
|
+
}
|
|
2752
|
+
var M = matrix.elements || matrix;
|
|
2753
|
+
if (typeof M[0][0] === 'undefined') {
|
|
2754
|
+
M = new Matrix(M).elements;
|
|
2755
|
+
}
|
|
2756
|
+
var T = this.dup(),
|
|
2757
|
+
cols = T.elements[0].length;
|
|
2758
|
+
var i = T.elements.length,
|
|
2759
|
+
nj = M[0].length,
|
|
2760
|
+
j;
|
|
2761
|
+
if (i !== M.length) {
|
|
2762
|
+
return null;
|
|
2763
|
+
}
|
|
2764
|
+
while (i--) {
|
|
2765
|
+
j = nj;
|
|
2766
|
+
while (j--) {
|
|
2767
|
+
T.elements[i][cols + j] = M[i][j];
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
return T;
|
|
2771
|
+
}
|
|
2772
|
+
inverse() {
|
|
2773
|
+
if (this.elements.length === 0) {
|
|
2774
|
+
return null;
|
|
2775
|
+
}
|
|
2776
|
+
if (!this.isSquare() || this.isSingular()) {
|
|
2777
|
+
return null;
|
|
2778
|
+
}
|
|
2779
|
+
var n = this.elements.length,
|
|
2780
|
+
i = n,
|
|
2781
|
+
j;
|
|
2782
|
+
var M = this.augment(Matrix.I(n)).toRightTriangular();
|
|
2783
|
+
var np = M.elements[0].length,
|
|
2784
|
+
p,
|
|
2785
|
+
els,
|
|
2786
|
+
divisor;
|
|
2787
|
+
var inverse_elements = [],
|
|
2788
|
+
new_element;
|
|
2789
|
+
// Matrix is non-singular so there will be no zeros on the
|
|
2790
|
+
// diagonal. Cycle through rows from last to first.
|
|
2791
|
+
while (i--) {
|
|
2792
|
+
// First, normalise diagonal elements to 1
|
|
2793
|
+
els = [];
|
|
2794
|
+
inverse_elements[i] = [];
|
|
2795
|
+
divisor = M.elements[i][i];
|
|
2796
|
+
for (p = 0; p < np; p++) {
|
|
2797
|
+
new_element = M.elements[i][p] / divisor;
|
|
2798
|
+
els.push(new_element);
|
|
2799
|
+
// Shuffle off the current row of the right hand side into the results
|
|
2800
|
+
// array as it will not be modified by later runs through this loop
|
|
2801
|
+
if (p >= n) {
|
|
2802
|
+
inverse_elements[i].push(new_element);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
M.elements[i] = els;
|
|
2806
|
+
// Then, subtract this row from those above it to give the identity matrix
|
|
2807
|
+
// on the left hand side
|
|
2808
|
+
j = i;
|
|
2809
|
+
while (j--) {
|
|
2810
|
+
els = [];
|
|
2811
|
+
for (p = 0; p < np; p++) {
|
|
2812
|
+
els.push(M.elements[j][p] - M.elements[i][p] * M.elements[j][i]);
|
|
2813
|
+
}
|
|
2814
|
+
M.elements[j] = els;
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
return new Matrix(inverse_elements);
|
|
2818
|
+
}
|
|
2819
|
+
round() {
|
|
2820
|
+
return this.map(function (x) {
|
|
2821
|
+
return Math.round(x);
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2824
|
+
snapTo(x) {
|
|
2825
|
+
return this.map(function (p) {
|
|
2826
|
+
return Math.abs(p - x) <= PRECISION ? x : p;
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
inspect() {
|
|
2830
|
+
var matrix_rows = [];
|
|
2831
|
+
var n = this.elements.length;
|
|
2832
|
+
if (n === 0) return '[]';
|
|
2833
|
+
for (var i = 0; i < n; i++) {
|
|
2834
|
+
matrix_rows.push(new Vector(this.elements[i]).inspect());
|
|
2835
|
+
}
|
|
2836
|
+
return matrix_rows.join('\n');
|
|
2837
|
+
}
|
|
2838
|
+
setElements(els) {
|
|
2839
|
+
var i,
|
|
2840
|
+
j,
|
|
2841
|
+
elements = els.elements || els;
|
|
2842
|
+
if (elements[0] && typeof elements[0][0] !== 'undefined') {
|
|
2843
|
+
i = elements.length;
|
|
2844
|
+
this.elements = [];
|
|
2845
|
+
while (i--) {
|
|
2846
|
+
j = elements[i].length;
|
|
2847
|
+
this.elements[i] = [];
|
|
2848
|
+
while (j--) {
|
|
2849
|
+
this.elements[i][j] = elements[i][j];
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
return this;
|
|
2853
|
+
}
|
|
2854
|
+
var n = elements.length;
|
|
2855
|
+
this.elements = [];
|
|
2856
|
+
for (i = 0; i < n; i++) {
|
|
2857
|
+
this.elements.push([elements[i]]);
|
|
2858
|
+
}
|
|
2859
|
+
return this;
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
//From glUtils.js
|
|
2863
|
+
flatten() {
|
|
2864
|
+
var result = [];
|
|
2865
|
+
if (this.elements.length == 0) {
|
|
2866
|
+
return [];
|
|
2867
|
+
}
|
|
2868
|
+
for (var j = 0; j < this.elements[0].length; j++) {
|
|
2869
|
+
for (var i = 0; i < this.elements.length; i++) {
|
|
2870
|
+
result.push(this.elements[i][j]);
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
return result;
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
//From glUtils.js
|
|
2877
|
+
ensure4x4() {
|
|
2878
|
+
if (this.elements.length == 4 && this.elements[0].length == 4) {
|
|
2879
|
+
return this;
|
|
2880
|
+
}
|
|
2881
|
+
if (this.elements.length > 4 || this.elements[0].length > 4) {
|
|
2882
|
+
return null;
|
|
2883
|
+
}
|
|
2884
|
+
for (var i = 0; i < this.elements.length; i++) {
|
|
2885
|
+
for (var j = this.elements[i].length; j < 4; j++) {
|
|
2886
|
+
if (i == j) {
|
|
2887
|
+
this.elements[i].push(1);
|
|
2888
|
+
} else {
|
|
2889
|
+
this.elements[i].push(0);
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
for (var i = this.elements.length; i < 4; i++) {
|
|
2894
|
+
if (i == 0) {
|
|
2895
|
+
this.elements.push([1, 0, 0, 0]);
|
|
2896
|
+
} else if (i == 1) {
|
|
2897
|
+
this.elements.push([0, 1, 0, 0]);
|
|
2898
|
+
} else if (i == 2) {
|
|
2899
|
+
this.elements.push([0, 0, 1, 0]);
|
|
2900
|
+
} else if (i == 3) {
|
|
2901
|
+
this.elements.push([0, 0, 0, 1]);
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
return this;
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
//From glUtils.js
|
|
2908
|
+
make3x3() {
|
|
2909
|
+
if (this.elements.length != 4 || this.elements[0].length != 4) {
|
|
2910
|
+
return null;
|
|
2911
|
+
}
|
|
2912
|
+
return new Matrix([[this.elements[0][0], this.elements[0][1], this.elements[0][2]], [this.elements[1][0], this.elements[1][1], this.elements[1][2]], [this.elements[2][0], this.elements[2][1], this.elements[2][2]]]);
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
Matrix.I = function (n) {
|
|
2916
|
+
var els = [],
|
|
2917
|
+
i = n,
|
|
2918
|
+
j;
|
|
2919
|
+
while (i--) {
|
|
2920
|
+
j = n;
|
|
2921
|
+
els[i] = [];
|
|
2922
|
+
while (j--) {
|
|
2923
|
+
els[i][j] = i === j ? 1 : 0;
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
return new Matrix(els);
|
|
2927
|
+
};
|
|
2928
|
+
Matrix.Diagonal = function (elements) {
|
|
2929
|
+
var i = elements.length;
|
|
2930
|
+
var M = Matrix.I(i);
|
|
2931
|
+
while (i--) {
|
|
2932
|
+
M.elements[i][i] = elements[i];
|
|
2933
|
+
}
|
|
2934
|
+
return M;
|
|
2935
|
+
};
|
|
2936
|
+
Matrix.Rotation = function (theta, a) {
|
|
2937
|
+
if (!a) {
|
|
2938
|
+
return new Matrix([[Math.cos(theta), -Math.sin(theta)], [Math.sin(theta), Math.cos(theta)]]);
|
|
2939
|
+
}
|
|
2940
|
+
var axis = a.dup();
|
|
2941
|
+
if (axis.elements.length !== 3) {
|
|
2942
|
+
return null;
|
|
2943
|
+
}
|
|
2944
|
+
var mod = axis.modulus();
|
|
2945
|
+
var x = axis.elements[0] / mod,
|
|
2946
|
+
y = axis.elements[1] / mod,
|
|
2947
|
+
z = axis.elements[2] / mod;
|
|
2948
|
+
var s = Math.sin(theta),
|
|
2949
|
+
c = Math.cos(theta),
|
|
2950
|
+
t = 1 - c;
|
|
2951
|
+
// Formula derived here: http://www.gamedev.net/reference/articles/article1199.asp
|
|
2952
|
+
// That proof rotates the co-ordinate system so theta becomes -theta and sin
|
|
2953
|
+
// becomes -sin here.
|
|
2954
|
+
return new Matrix([[t * x * x + c, t * x * y - s * z, t * x * z + s * y], [t * x * y + s * z, t * y * y + c, t * y * z - s * x], [t * x * z - s * y, t * y * z + s * x, t * z * z + c]]);
|
|
2955
|
+
};
|
|
2956
|
+
Matrix.RotationX = function (t) {
|
|
2957
|
+
var c = Math.cos(t),
|
|
2958
|
+
s = Math.sin(t);
|
|
2959
|
+
return new Matrix([[1, 0, 0], [0, c, -s], [0, s, c]]);
|
|
2960
|
+
};
|
|
2961
|
+
Matrix.RotationY = function (t) {
|
|
2962
|
+
var c = Math.cos(t),
|
|
2963
|
+
s = Math.sin(t);
|
|
2964
|
+
return new Matrix([[c, 0, s], [0, 1, 0], [-s, 0, c]]);
|
|
2965
|
+
};
|
|
2966
|
+
Matrix.RotationZ = function (t) {
|
|
2967
|
+
var c = Math.cos(t),
|
|
2968
|
+
s = Math.sin(t);
|
|
2969
|
+
return new Matrix([[c, -s, 0], [s, c, 0], [0, 0, 1]]);
|
|
2970
|
+
};
|
|
2971
|
+
Matrix.Random = function (n, m) {
|
|
2972
|
+
return Matrix.Zero(n, m).map(function () {
|
|
2973
|
+
return Math.random();
|
|
2974
|
+
});
|
|
2975
|
+
};
|
|
2976
|
+
|
|
2977
|
+
//From glUtils.js
|
|
2978
|
+
Matrix.Translation = function (v) {
|
|
2979
|
+
if (v.elements.length == 2) {
|
|
2980
|
+
var r = Matrix.I(3);
|
|
2981
|
+
r.elements[2][0] = v.elements[0];
|
|
2982
|
+
r.elements[2][1] = v.elements[1];
|
|
2983
|
+
return r;
|
|
2984
|
+
}
|
|
2985
|
+
if (v.elements.length == 3) {
|
|
2986
|
+
var r = Matrix.I(4);
|
|
2987
|
+
r.elements[0][3] = v.elements[0];
|
|
2988
|
+
r.elements[1][3] = v.elements[1];
|
|
2989
|
+
r.elements[2][3] = v.elements[2];
|
|
2990
|
+
return r;
|
|
2991
|
+
}
|
|
2992
|
+
throw "Invalid length for Translation";
|
|
2993
|
+
};
|
|
2994
|
+
Matrix.Zero = function (n, m) {
|
|
2995
|
+
var els = [],
|
|
2996
|
+
i = n,
|
|
2997
|
+
j;
|
|
2998
|
+
while (i--) {
|
|
2999
|
+
j = m;
|
|
3000
|
+
els[i] = [];
|
|
3001
|
+
while (j--) {
|
|
3002
|
+
els[i][j] = 0;
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
return new Matrix(els);
|
|
3006
|
+
};
|
|
3007
|
+
Matrix.prototype.toUpperTriangular = Matrix.prototype.toRightTriangular;
|
|
3008
|
+
Matrix.prototype.det = Matrix.prototype.determinant;
|
|
3009
|
+
Matrix.prototype.tr = Matrix.prototype.trace;
|
|
3010
|
+
Matrix.prototype.rk = Matrix.prototype.rank;
|
|
3011
|
+
Matrix.prototype.inv = Matrix.prototype.inverse;
|
|
3012
|
+
Matrix.prototype.x = Matrix.prototype.multiply;
|
|
3013
|
+
|
|
3014
|
+
class Vector {
|
|
3015
|
+
constructor(elements) {
|
|
3016
|
+
this.setElements(elements);
|
|
3017
|
+
}
|
|
3018
|
+
e(i) {
|
|
3019
|
+
return i < 1 || i > this.elements.length ? null : this.elements[i - 1];
|
|
3020
|
+
}
|
|
3021
|
+
dimensions() {
|
|
3022
|
+
return this.elements.length;
|
|
3023
|
+
}
|
|
3024
|
+
modulus() {
|
|
3025
|
+
return Math.sqrt(this.dot(this));
|
|
3026
|
+
}
|
|
3027
|
+
eql(vector) {
|
|
3028
|
+
var n = this.elements.length;
|
|
3029
|
+
var V = vector.elements || vector;
|
|
3030
|
+
if (n !== V.length) {
|
|
3031
|
+
return false;
|
|
3032
|
+
}
|
|
3033
|
+
while (n--) {
|
|
3034
|
+
if (Math.abs(this.elements[n] - V[n]) > PRECISION) {
|
|
3035
|
+
return false;
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
return true;
|
|
3039
|
+
}
|
|
3040
|
+
dup() {
|
|
3041
|
+
return new Vector(this.elements);
|
|
3042
|
+
}
|
|
3043
|
+
map(fn, context) {
|
|
3044
|
+
var elements = [];
|
|
3045
|
+
this.each(function (x, i) {
|
|
3046
|
+
elements.push(fn.call(context, x, i));
|
|
3047
|
+
});
|
|
3048
|
+
return new Vector(elements);
|
|
3049
|
+
}
|
|
3050
|
+
forEach(fn, context) {
|
|
3051
|
+
var n = this.elements.length;
|
|
3052
|
+
for (var i = 0; i < n; i++) {
|
|
3053
|
+
fn.call(context, this.elements[i], i + 1);
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
toUnitVector() {
|
|
3057
|
+
var r = this.modulus();
|
|
3058
|
+
if (r === 0) {
|
|
3059
|
+
return this.dup();
|
|
3060
|
+
}
|
|
3061
|
+
return this.map(function (x) {
|
|
3062
|
+
return x / r;
|
|
3063
|
+
});
|
|
3064
|
+
}
|
|
3065
|
+
angleFrom(vector) {
|
|
3066
|
+
var V = vector.elements || vector;
|
|
3067
|
+
var n = this.elements.length;
|
|
3068
|
+
if (n !== V.length) {
|
|
3069
|
+
return null;
|
|
3070
|
+
}
|
|
3071
|
+
var dot = 0,
|
|
3072
|
+
mod1 = 0,
|
|
3073
|
+
mod2 = 0;
|
|
3074
|
+
// Work things out in parallel to save time
|
|
3075
|
+
this.each(function (x, i) {
|
|
3076
|
+
dot += x * V[i - 1];
|
|
3077
|
+
mod1 += x * x;
|
|
3078
|
+
mod2 += V[i - 1] * V[i - 1];
|
|
3079
|
+
});
|
|
3080
|
+
mod1 = Math.sqrt(mod1);
|
|
3081
|
+
mod2 = Math.sqrt(mod2);
|
|
3082
|
+
if (mod1 * mod2 === 0) {
|
|
3083
|
+
return null;
|
|
3084
|
+
}
|
|
3085
|
+
var theta = dot / (mod1 * mod2);
|
|
3086
|
+
if (theta < -1) {
|
|
3087
|
+
theta = -1;
|
|
3088
|
+
}
|
|
3089
|
+
if (theta > 1) {
|
|
3090
|
+
theta = 1;
|
|
3091
|
+
}
|
|
3092
|
+
return Math.acos(theta);
|
|
3093
|
+
}
|
|
3094
|
+
isParallelTo(vector) {
|
|
3095
|
+
var angle = this.angleFrom(vector);
|
|
3096
|
+
return angle === null ? null : angle <= PRECISION;
|
|
3097
|
+
}
|
|
3098
|
+
isAntiparallelTo(vector) {
|
|
3099
|
+
var angle = this.angleFrom(vector);
|
|
3100
|
+
return angle === null ? null : Math.abs(angle - Math.PI) <= PRECISION;
|
|
3101
|
+
}
|
|
3102
|
+
isPerpendicularTo(vector) {
|
|
3103
|
+
var dot = this.dot(vector);
|
|
3104
|
+
return dot === null ? null : Math.abs(dot) <= PRECISION;
|
|
3105
|
+
}
|
|
3106
|
+
add(vector) {
|
|
3107
|
+
var V = vector.elements || vector;
|
|
3108
|
+
if (this.elements.length !== V.length) {
|
|
3109
|
+
return null;
|
|
3110
|
+
}
|
|
3111
|
+
return this.map(function (x, i) {
|
|
3112
|
+
return x + V[i - 1];
|
|
3113
|
+
});
|
|
3114
|
+
}
|
|
3115
|
+
subtract(vector) {
|
|
3116
|
+
var V = vector.elements || vector;
|
|
3117
|
+
if (this.elements.length !== V.length) {
|
|
3118
|
+
return null;
|
|
3119
|
+
}
|
|
3120
|
+
return this.map(function (x, i) {
|
|
3121
|
+
return x - V[i - 1];
|
|
3122
|
+
});
|
|
3123
|
+
}
|
|
3124
|
+
multiply(k) {
|
|
3125
|
+
return this.map(function (x) {
|
|
3126
|
+
return x * k;
|
|
3127
|
+
});
|
|
3128
|
+
}
|
|
3129
|
+
dot(vector) {
|
|
3130
|
+
var V = vector.elements || vector;
|
|
3131
|
+
var product = 0,
|
|
3132
|
+
n = this.elements.length;
|
|
3133
|
+
if (n !== V.length) {
|
|
3134
|
+
return null;
|
|
3135
|
+
}
|
|
3136
|
+
while (n--) {
|
|
3137
|
+
product += this.elements[n] * V[n];
|
|
3138
|
+
}
|
|
3139
|
+
return product;
|
|
3140
|
+
}
|
|
3141
|
+
cross(vector) {
|
|
3142
|
+
var B = vector.elements || vector;
|
|
3143
|
+
if (this.elements.length !== 3 || B.length !== 3) {
|
|
3144
|
+
return null;
|
|
3145
|
+
}
|
|
3146
|
+
var A = this.elements;
|
|
3147
|
+
return new Vector([A[1] * B[2] - A[2] * B[1], A[2] * B[0] - A[0] * B[2], A[0] * B[1] - A[1] * B[0]]);
|
|
3148
|
+
}
|
|
3149
|
+
max() {
|
|
3150
|
+
var m = 0,
|
|
3151
|
+
i = this.elements.length;
|
|
3152
|
+
while (i--) {
|
|
3153
|
+
if (Math.abs(this.elements[i]) > Math.abs(m)) {
|
|
3154
|
+
m = this.elements[i];
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
return m;
|
|
3158
|
+
}
|
|
3159
|
+
indexOf(x) {
|
|
3160
|
+
var index = null,
|
|
3161
|
+
n = this.elements.length;
|
|
3162
|
+
for (var i = 0; i < n; i++) {
|
|
3163
|
+
if (index === null && this.elements[i] === x) {
|
|
3164
|
+
index = i + 1;
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
return index;
|
|
3168
|
+
}
|
|
3169
|
+
toDiagonalMatrix() {
|
|
3170
|
+
return Matrix.Diagonal(this.elements);
|
|
3171
|
+
}
|
|
3172
|
+
round() {
|
|
3173
|
+
return this.map(function (x) {
|
|
3174
|
+
return Math.round(x);
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3177
|
+
snapTo(x) {
|
|
3178
|
+
return this.map(function (y) {
|
|
3179
|
+
return Math.abs(y - x) <= PRECISION ? x : y;
|
|
3180
|
+
});
|
|
3181
|
+
}
|
|
3182
|
+
distanceFrom(obj) {
|
|
3183
|
+
if (obj.anchor || obj.start && obj.end) {
|
|
3184
|
+
return obj.distanceFrom(this);
|
|
3185
|
+
}
|
|
3186
|
+
var V = obj.elements || obj;
|
|
3187
|
+
if (V.length !== this.elements.length) {
|
|
3188
|
+
return null;
|
|
3189
|
+
}
|
|
3190
|
+
var sum = 0,
|
|
3191
|
+
part;
|
|
3192
|
+
this.each(function (x, i) {
|
|
3193
|
+
part = x - V[i - 1];
|
|
3194
|
+
sum += part * part;
|
|
3195
|
+
});
|
|
3196
|
+
return Math.sqrt(sum);
|
|
3197
|
+
}
|
|
3198
|
+
liesOn(line) {
|
|
3199
|
+
return line.contains(this);
|
|
3200
|
+
}
|
|
3201
|
+
liesIn(plane) {
|
|
3202
|
+
return plane.contains(this);
|
|
3203
|
+
}
|
|
3204
|
+
rotate(t, obj) {
|
|
3205
|
+
var V,
|
|
3206
|
+
R = null,
|
|
3207
|
+
x,
|
|
3208
|
+
y,
|
|
3209
|
+
z;
|
|
3210
|
+
if (t.determinant) {
|
|
3211
|
+
R = t.elements;
|
|
3212
|
+
}
|
|
3213
|
+
switch (this.elements.length) {
|
|
3214
|
+
case 2:
|
|
3215
|
+
{
|
|
3216
|
+
V = obj.elements || obj;
|
|
3217
|
+
if (V.length !== 2) {
|
|
3218
|
+
return null;
|
|
3219
|
+
}
|
|
3220
|
+
if (!R) {
|
|
3221
|
+
R = Matrix.Rotation(t).elements;
|
|
3222
|
+
}
|
|
3223
|
+
x = this.elements[0] - V[0];
|
|
3224
|
+
y = this.elements[1] - V[1];
|
|
3225
|
+
return new Vector([V[0] + R[0][0] * x + R[0][1] * y, V[1] + R[1][0] * x + R[1][1] * y]);
|
|
3226
|
+
}
|
|
3227
|
+
case 3:
|
|
3228
|
+
{
|
|
3229
|
+
if (!obj.direction) {
|
|
3230
|
+
return null;
|
|
3231
|
+
}
|
|
3232
|
+
var C = obj.pointClosestTo(this).elements;
|
|
3233
|
+
if (!R) {
|
|
3234
|
+
R = Matrix.Rotation(t, obj.direction).elements;
|
|
3235
|
+
}
|
|
3236
|
+
x = this.elements[0] - C[0];
|
|
3237
|
+
y = this.elements[1] - C[1];
|
|
3238
|
+
z = this.elements[2] - C[2];
|
|
3239
|
+
return new Vector([C[0] + R[0][0] * x + R[0][1] * y + R[0][2] * z, C[1] + R[1][0] * x + R[1][1] * y + R[1][2] * z, C[2] + R[2][0] * x + R[2][1] * y + R[2][2] * z]);
|
|
3240
|
+
}
|
|
3241
|
+
default:
|
|
3242
|
+
{
|
|
3243
|
+
return null;
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
reflectionIn(obj) {
|
|
3248
|
+
if (obj.anchor) {
|
|
3249
|
+
// obj is a plane or line
|
|
3250
|
+
var P = this.elements.slice();
|
|
3251
|
+
var C = obj.pointClosestTo(P).elements;
|
|
3252
|
+
return new Vector([C[0] + (C[0] - P[0]), C[1] + (C[1] - P[1]), C[2] + (C[2] - (P[2] || 0))]);
|
|
3253
|
+
} else {
|
|
3254
|
+
// obj is a point
|
|
3255
|
+
var Q = obj.elements || obj;
|
|
3256
|
+
if (this.elements.length !== Q.length) {
|
|
3257
|
+
return null;
|
|
3258
|
+
}
|
|
3259
|
+
return this.map(function (x, i) {
|
|
3260
|
+
return Q[i - 1] + (Q[i - 1] - x);
|
|
3261
|
+
});
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
to3D() {
|
|
3265
|
+
var V = this.dup();
|
|
3266
|
+
switch (V.elements.length) {
|
|
3267
|
+
case 3:
|
|
3268
|
+
{
|
|
3269
|
+
break;
|
|
3270
|
+
}
|
|
3271
|
+
case 2:
|
|
3272
|
+
{
|
|
3273
|
+
V.elements.push(0);
|
|
3274
|
+
break;
|
|
3275
|
+
}
|
|
3276
|
+
default:
|
|
3277
|
+
{
|
|
3278
|
+
return null;
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
return V;
|
|
3282
|
+
}
|
|
3283
|
+
inspect() {
|
|
3284
|
+
return '[' + this.elements.join(', ') + ']';
|
|
3285
|
+
}
|
|
3286
|
+
setElements(els) {
|
|
3287
|
+
this.elements = (els.elements || els).slice();
|
|
3288
|
+
return this;
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
//From glUtils.js
|
|
3292
|
+
flatten() {
|
|
3293
|
+
return this.elements;
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
Vector.Random = function (n) {
|
|
3297
|
+
var elements = [];
|
|
3298
|
+
while (n--) {
|
|
3299
|
+
elements.push(Math.random());
|
|
3300
|
+
}
|
|
3301
|
+
return new Vector(elements);
|
|
3302
|
+
};
|
|
3303
|
+
Vector.Zero = function (n) {
|
|
3304
|
+
var elements = [];
|
|
3305
|
+
while (n--) {
|
|
3306
|
+
elements.push(0);
|
|
3307
|
+
}
|
|
3308
|
+
return new Vector(elements);
|
|
3309
|
+
};
|
|
3310
|
+
Vector.prototype.x = Vector.prototype.multiply;
|
|
3311
|
+
Vector.prototype.each = Vector.prototype.forEach;
|
|
3312
|
+
Vector.i = new Vector([1, 0, 0]);
|
|
3313
|
+
Vector.j = new Vector([0, 1, 0]);
|
|
3314
|
+
Vector.k = new Vector([0, 0, 1]);
|
|
3315
|
+
|
|
3316
|
+
var computeCentroids = function computeCentroids(config, position, row) {
|
|
3317
|
+
var centroids = [];
|
|
3318
|
+
var p = Object.keys(config.dimensions);
|
|
3319
|
+
var cols = p.length;
|
|
3320
|
+
var a = 0.5; // center between axes
|
|
3321
|
+
for (var i = 0; i < cols; ++i) {
|
|
3322
|
+
// centroids on 'real' axes
|
|
3323
|
+
var x = position(p[i]);
|
|
3324
|
+
var y = config.dimensions[p[i]].yscale(row[p[i]]);
|
|
3325
|
+
centroids.push(new Vector([x, y]));
|
|
3326
|
+
|
|
3327
|
+
// centroids on 'virtual' axes
|
|
3328
|
+
if (i < cols - 1) {
|
|
3329
|
+
var cx = x + a * (position(p[i + 1]) - x);
|
|
3330
|
+
var cy = y + a * (config.dimensions[p[i + 1]].yscale(row[p[i + 1]]) - y);
|
|
3331
|
+
if (config.bundleDimension !== null) {
|
|
3332
|
+
var leftCentroid = config.clusterCentroids.get(config.dimensions[config.bundleDimension].yscale(row[config.bundleDimension])).get(p[i]);
|
|
3333
|
+
var rightCentroid = config.clusterCentroids.get(config.dimensions[config.bundleDimension].yscale(row[config.bundleDimension])).get(p[i + 1]);
|
|
3334
|
+
var centroid = 0.5 * (leftCentroid + rightCentroid);
|
|
3335
|
+
cy = centroid + (1 - config.bundlingStrength) * (cy - centroid);
|
|
3336
|
+
}
|
|
3337
|
+
centroids.push(new Vector([cx, cy]));
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
return centroids;
|
|
3341
|
+
};
|
|
3342
|
+
|
|
3343
|
+
var computeControlPoints = function computeControlPoints(smoothness, centroids) {
|
|
3344
|
+
var cols = centroids.length;
|
|
3345
|
+
var a = smoothness;
|
|
3346
|
+
var cps = [];
|
|
3347
|
+
cps.push(centroids[0]);
|
|
3348
|
+
cps.push(new Vector([centroids[0].e(1) + a * 2 * (centroids[1].e(1) - centroids[0].e(1)), centroids[0].e(2)]));
|
|
3349
|
+
for (var col = 1; col < cols - 1; ++col) {
|
|
3350
|
+
var mid = centroids[col];
|
|
3351
|
+
var left = centroids[col - 1];
|
|
3352
|
+
var right = centroids[col + 1];
|
|
3353
|
+
var diff = left.subtract(right);
|
|
3354
|
+
cps.push(mid.add(diff.x(a)));
|
|
3355
|
+
cps.push(mid);
|
|
3356
|
+
cps.push(mid.subtract(diff.x(a)));
|
|
3357
|
+
}
|
|
3358
|
+
cps.push(new Vector([centroids[cols - 1].e(1) + a * 2 * (centroids[cols - 2].e(1) - centroids[cols - 1].e(1)), centroids[cols - 1].e(2)]));
|
|
3359
|
+
cps.push(centroids[cols - 1]);
|
|
3360
|
+
return cps;
|
|
3361
|
+
};
|
|
3362
|
+
|
|
3363
|
+
// draw single cubic bezier curve
|
|
3364
|
+
var singleCurve = function singleCurve(config, position, d, ctx) {
|
|
3365
|
+
var centroids = computeCentroids(config, position, d);
|
|
3366
|
+
var cps = computeControlPoints(config.smoothness, centroids);
|
|
3367
|
+
ctx.moveTo(cps[0].e(1), cps[0].e(2));
|
|
3368
|
+
for (var i = 1; i < cps.length; i += 3) {
|
|
3369
|
+
if (config.showControlPoints) {
|
|
3370
|
+
for (var j = 0; j < 3; j++) {
|
|
3371
|
+
ctx.fillRect(cps[i + j].e(1), cps[i + j].e(2), 2, 2);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
ctx.bezierCurveTo(cps[i].e(1), cps[i].e(2), cps[i + 1].e(1), cps[i + 1].e(2), cps[i + 2].e(1), cps[i + 2].e(2));
|
|
3375
|
+
}
|
|
3376
|
+
};
|
|
3377
|
+
|
|
3378
|
+
// returns the y-position just beyond the separating null value line
|
|
3379
|
+
var getNullPosition = function getNullPosition(config) {
|
|
3380
|
+
if (config.nullValueSeparator === 'bottom') {
|
|
3381
|
+
return h(config) + 1;
|
|
3382
|
+
} else if (config.nullValueSeparator === 'top') {
|
|
3383
|
+
return 1;
|
|
3384
|
+
} else {
|
|
3385
|
+
console.log("A value is NULL, but nullValueSeparator is not set; set it to 'bottom' or 'top'.");
|
|
3386
|
+
}
|
|
3387
|
+
return h(config) + 1;
|
|
3388
|
+
};
|
|
3389
|
+
var singlePath = function singlePath(config, position, d, ctx) {
|
|
3390
|
+
Object.keys(config.dimensions).map(function (p) {
|
|
3391
|
+
return [position(p), d[p] === undefined ? getNullPosition(config) : config.dimensions[p].yscale(d[p])];
|
|
3392
|
+
}).sort(function (a, b) {
|
|
3393
|
+
return a[0] - b[0];
|
|
3394
|
+
}).forEach(function (p, i) {
|
|
3395
|
+
i === 0 ? ctx.moveTo(p[0], p[1]) : ctx.lineTo(p[0], p[1]);
|
|
3396
|
+
});
|
|
3397
|
+
};
|
|
3398
|
+
|
|
3399
|
+
// draw single polyline
|
|
3400
|
+
var colorPath = function colorPath(config, position, d, ctx) {
|
|
3401
|
+
ctx.beginPath();
|
|
3402
|
+
if (config.bundleDimension !== null && config.bundlingStrength > 0 || config.smoothness > 0) {
|
|
3403
|
+
singleCurve(config, position, d, ctx);
|
|
3404
|
+
} else {
|
|
3405
|
+
singlePath(config, position, d, ctx);
|
|
3406
|
+
}
|
|
3407
|
+
ctx.stroke();
|
|
3408
|
+
};
|
|
3409
|
+
|
|
3410
|
+
var _functor = function _functor(v) {
|
|
3411
|
+
return typeof v === 'function' ? v : function () {
|
|
3412
|
+
return v;
|
|
3413
|
+
};
|
|
3414
|
+
};
|
|
3415
|
+
|
|
3416
|
+
var pathMark = function pathMark(config, ctx, position) {
|
|
3417
|
+
return function (d, i) {
|
|
3418
|
+
ctx.marked.strokeStyle = _functor(config.color)(d, i);
|
|
3419
|
+
return colorPath(config, position, d, ctx.marked);
|
|
3420
|
+
};
|
|
3421
|
+
};
|
|
3422
|
+
var renderMarkedDefault = function renderMarkedDefault(config, pc, ctx, position) {
|
|
3423
|
+
return function () {
|
|
3424
|
+
pc.clear('marked');
|
|
3425
|
+
if (config.marked.length) {
|
|
3426
|
+
config.marked.forEach(pathMark(config, ctx, position));
|
|
3427
|
+
}
|
|
3428
|
+
};
|
|
3429
|
+
};
|
|
3430
|
+
var renderMarkedQueue = function renderMarkedQueue(config, markedQueue) {
|
|
3431
|
+
return function () {
|
|
3432
|
+
if (config.marked) {
|
|
3433
|
+
markedQueue(config.marked);
|
|
3434
|
+
} else {
|
|
3435
|
+
markedQueue([]); // This is needed to clear the currently marked items
|
|
3436
|
+
}
|
|
3437
|
+
};
|
|
3438
|
+
};
|
|
3439
|
+
var renderMarked = function renderMarked(config, pc, events) {
|
|
3440
|
+
return function () {
|
|
3441
|
+
if (!Object.keys(config.dimensions).length) pc.detectDimensions();
|
|
3442
|
+
pc.renderMarked[config.mode]();
|
|
3443
|
+
events.call('render', this);
|
|
3444
|
+
return this;
|
|
3445
|
+
};
|
|
3446
|
+
};
|
|
3447
|
+
|
|
3448
|
+
var pathBrushed = function pathBrushed(config, ctx, position) {
|
|
3449
|
+
return function (d, i) {
|
|
3450
|
+
if (config.brushedColor !== null) {
|
|
3451
|
+
ctx.brushed.strokeStyle = _functor(config.brushedColor)(d, i);
|
|
3452
|
+
} else {
|
|
3453
|
+
ctx.brushed.strokeStyle = _functor(config.color)(d, i);
|
|
3454
|
+
}
|
|
3455
|
+
return colorPath(config, position, d, ctx.brushed);
|
|
3456
|
+
};
|
|
3457
|
+
};
|
|
3458
|
+
var renderBrushedDefault = function renderBrushedDefault(config, ctx, position, pc, brushGroup) {
|
|
3459
|
+
return function () {
|
|
3460
|
+
pc.clear('brushed');
|
|
3461
|
+
if (isBrushed(config, brushGroup) && config.brushed !== false) {
|
|
3462
|
+
config.brushed.forEach(pathBrushed(config, ctx, position));
|
|
3463
|
+
}
|
|
3464
|
+
};
|
|
3465
|
+
};
|
|
3466
|
+
var renderBrushedQueue = function renderBrushedQueue(config, brushGroup, brushedQueue) {
|
|
3467
|
+
return function () {
|
|
3468
|
+
if (isBrushed(config, brushGroup)) {
|
|
3469
|
+
brushedQueue(config.brushed);
|
|
3470
|
+
} else {
|
|
3471
|
+
brushedQueue([]); // This is needed to clear the currently brushed items
|
|
3472
|
+
}
|
|
3473
|
+
};
|
|
3474
|
+
};
|
|
3475
|
+
var renderBrushed = function renderBrushed(config, pc, events) {
|
|
3476
|
+
return function () {
|
|
3477
|
+
if (!Object.keys(config.dimensions).length) pc.detectDimensions();
|
|
3478
|
+
pc.renderBrushed[config.mode]();
|
|
3479
|
+
events.call('render', this);
|
|
3480
|
+
return this;
|
|
3481
|
+
};
|
|
3482
|
+
};
|
|
3483
|
+
|
|
3484
|
+
var brushReset = function brushReset(config, pc) {
|
|
3485
|
+
return function (dimension) {
|
|
3486
|
+
var brushesToKeep = [];
|
|
3487
|
+
for (var j = 0; j < config.brushes.length; j++) {
|
|
3488
|
+
if (config.brushes[j].data !== dimension) {
|
|
3489
|
+
brushesToKeep.push(config.brushes[j]);
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
config.brushes = brushesToKeep;
|
|
3493
|
+
config.brushed = false;
|
|
3494
|
+
if (pc.g() !== undefined) {
|
|
3495
|
+
var nodes = pc.g().selectAll('.brush').nodes();
|
|
3496
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
3497
|
+
if (nodes[i].__data__ === dimension) {
|
|
3498
|
+
// remove all dummy brushes for this axis or the real brush
|
|
3499
|
+
d3Selection.select(d3Selection.select(nodes[i]).nodes()[0].parentNode).selectAll('.dummy').remove();
|
|
3500
|
+
config.dimensions[dimension].brush.move(d3Selection.select(nodes[i], null));
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
return this;
|
|
3505
|
+
};
|
|
3506
|
+
};
|
|
3507
|
+
|
|
3508
|
+
// a better "typeof" from this post: http://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable
|
|
3509
|
+
var toType = function toType(v) {
|
|
3510
|
+
return {}.toString.call(v).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
|
|
3511
|
+
};
|
|
3512
|
+
|
|
3513
|
+
// this descriptive text should live with other introspective methods
|
|
3514
|
+
var toString = function toString(config) {
|
|
3515
|
+
return function () {
|
|
3516
|
+
return 'Parallel Coordinates: ' + Object.keys(config.dimensions).length + ' dimensions (' + Object.keys(config.data[0]).length + ' total) , ' + config.data.length + ' rows';
|
|
3517
|
+
};
|
|
3518
|
+
};
|
|
3519
|
+
|
|
3520
|
+
// pairs of adjacent dimensions
|
|
3521
|
+
var adjacentPairs = function adjacentPairs(arr) {
|
|
3522
|
+
var ret = [];
|
|
3523
|
+
for (var i = 0; i < arr.length - 1; i++) {
|
|
3524
|
+
ret.push([arr[i], arr[i + 1]]);
|
|
3525
|
+
}
|
|
3526
|
+
return ret;
|
|
3527
|
+
};
|
|
3528
|
+
|
|
3529
|
+
var pathHighlight = function pathHighlight(config, ctx, position) {
|
|
3530
|
+
return function (d, i) {
|
|
3531
|
+
ctx.highlight.strokeStyle = _functor(config.color)(d, i);
|
|
3532
|
+
return colorPath(config, position, d, ctx.highlight);
|
|
3533
|
+
};
|
|
3534
|
+
};
|
|
3535
|
+
|
|
3536
|
+
// highlight an array of data
|
|
3537
|
+
var highlight = function highlight(config, pc, canvas, events, ctx, position) {
|
|
3538
|
+
return function () {
|
|
3539
|
+
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
3540
|
+
if (data === null) {
|
|
3541
|
+
return config.highlighted;
|
|
3542
|
+
}
|
|
3543
|
+
config.highlighted = data;
|
|
3544
|
+
pc.clear('highlight');
|
|
3545
|
+
d3Selection.selectAll([canvas.foreground, canvas.brushed]).classed('faded', true);
|
|
3546
|
+
data.forEach(pathHighlight(config, ctx, position));
|
|
3547
|
+
events.call('highlight', this, data);
|
|
3548
|
+
return this;
|
|
3549
|
+
};
|
|
3550
|
+
};
|
|
3551
|
+
|
|
3552
|
+
// clear highlighting
|
|
3553
|
+
var unhighlight = function unhighlight(config, pc, canvas) {
|
|
3554
|
+
return function () {
|
|
3555
|
+
config.highlighted = [];
|
|
3556
|
+
pc.clear('highlight');
|
|
3557
|
+
d3Selection.selectAll([canvas.foreground, canvas.brushed]).classed('faded', false);
|
|
3558
|
+
return this;
|
|
3559
|
+
};
|
|
3560
|
+
};
|
|
3561
|
+
|
|
3562
|
+
// mark an array of data
|
|
3563
|
+
var mark = function mark(config, pc, canvas, events, ctx, position) {
|
|
3564
|
+
return function () {
|
|
3565
|
+
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
|
3566
|
+
if (data === null) {
|
|
3567
|
+
return config.marked;
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
// add array to already marked data
|
|
3571
|
+
config.marked = config.marked.concat(data);
|
|
3572
|
+
d3Selection.selectAll([canvas.foreground, canvas.brushed]).classed('dimmed', true);
|
|
3573
|
+
data.forEach(pathMark(config, ctx, position));
|
|
3574
|
+
events.call('mark', this, data);
|
|
3575
|
+
return this;
|
|
3576
|
+
};
|
|
3577
|
+
};
|
|
3578
|
+
|
|
3579
|
+
// clear marked data arrays
|
|
3580
|
+
var unmark = function unmark(config, pc, canvas) {
|
|
3581
|
+
return function () {
|
|
3582
|
+
config.marked = [];
|
|
3583
|
+
pc.clear('marked');
|
|
3584
|
+
d3Selection.selectAll([canvas.foreground, canvas.brushed]).classed('dimmed', false);
|
|
3585
|
+
return this;
|
|
3586
|
+
};
|
|
3587
|
+
};
|
|
3588
|
+
|
|
3589
|
+
var removeAxes = function removeAxes(pc) {
|
|
3590
|
+
return function () {
|
|
3591
|
+
pc._g.remove();
|
|
3592
|
+
delete pc._g;
|
|
3593
|
+
return this;
|
|
3594
|
+
};
|
|
3595
|
+
};
|
|
3596
|
+
|
|
3597
|
+
/**
|
|
3598
|
+
* Renders the polylines.
|
|
3599
|
+
* If no dimensions have been specified, it will attempt to detect quantitative
|
|
3600
|
+
* dimensions based on the first data entry. If scales haven't been set, it will
|
|
3601
|
+
* autoscale based on the extent for each dimension.
|
|
3602
|
+
*
|
|
3603
|
+
* @param config
|
|
3604
|
+
* @param pc
|
|
3605
|
+
* @param events
|
|
3606
|
+
* @returns {Function}
|
|
3607
|
+
*/
|
|
3608
|
+
var render = function render(config, pc, events) {
|
|
3609
|
+
return function () {
|
|
3610
|
+
// try to autodetect dimensions and create scales
|
|
3611
|
+
if (!Object.keys(config.dimensions).length) {
|
|
3612
|
+
pc.detectDimensions();
|
|
3613
|
+
}
|
|
3614
|
+
pc.autoscale();
|
|
3615
|
+
pc.render[config.mode]();
|
|
3616
|
+
events.call('render', this);
|
|
3617
|
+
return this;
|
|
3618
|
+
};
|
|
3619
|
+
};
|
|
3620
|
+
|
|
3621
|
+
var pathForeground = function pathForeground(config, ctx, position) {
|
|
3622
|
+
return function (d, i) {
|
|
3623
|
+
ctx.foreground.strokeStyle = _functor(config.color)(d, i);
|
|
3624
|
+
return colorPath(config, position, d, ctx.foreground);
|
|
3625
|
+
};
|
|
3626
|
+
};
|
|
3627
|
+
var renderDefault = function renderDefault(config, pc, ctx, position) {
|
|
3628
|
+
return function () {
|
|
3629
|
+
pc.clear('foreground');
|
|
3630
|
+
pc.clear('highlight');
|
|
3631
|
+
pc.renderBrushed["default"]();
|
|
3632
|
+
pc.renderMarked["default"]();
|
|
3633
|
+
config.data.forEach(pathForeground(config, ctx, position));
|
|
3634
|
+
};
|
|
3635
|
+
};
|
|
3636
|
+
var renderDefaultQueue = function renderDefaultQueue(config, pc, foregroundQueue) {
|
|
3637
|
+
return function () {
|
|
3638
|
+
pc.renderBrushed.queue();
|
|
3639
|
+
pc.renderMarked.queue();
|
|
3640
|
+
foregroundQueue(config.data);
|
|
3641
|
+
};
|
|
3642
|
+
};
|
|
3643
|
+
|
|
3644
|
+
// try to coerce to number before returning type
|
|
3645
|
+
var toTypeCoerceNumbers = function toTypeCoerceNumbers(v) {
|
|
3646
|
+
return parseFloat(v) == v && v !== null ? 'number' : toType(v);
|
|
3647
|
+
};
|
|
3648
|
+
|
|
3649
|
+
// attempt to determine types of each dimension based on first row of data
|
|
3650
|
+
var detectDimensionTypes = function detectDimensionTypes(data) {
|
|
3651
|
+
return Object.keys(data[0]).reduce(function (acc, cur) {
|
|
3652
|
+
var key = isNaN(Number(cur)) ? cur : parseInt(cur);
|
|
3653
|
+
acc[key] = toTypeCoerceNumbers(data[0][cur]);
|
|
3654
|
+
return acc;
|
|
3655
|
+
}, {});
|
|
3656
|
+
};
|
|
3657
|
+
|
|
3658
|
+
var getOrderedDimensionKeys = function getOrderedDimensionKeys(config) {
|
|
3659
|
+
return function () {
|
|
3660
|
+
return Object.keys(config.dimensions).sort(function (x, y) {
|
|
3661
|
+
return d3Array.ascending(config.dimensions[x].index, config.dimensions[y].index);
|
|
3662
|
+
});
|
|
3663
|
+
};
|
|
3664
|
+
};
|
|
3665
|
+
|
|
3666
|
+
var interactive = function interactive(flags) {
|
|
3667
|
+
return function () {
|
|
3668
|
+
flags.interactive = true;
|
|
3669
|
+
return this;
|
|
3670
|
+
};
|
|
3671
|
+
};
|
|
3672
|
+
|
|
3673
|
+
var shadows = function shadows(flags, pc) {
|
|
3674
|
+
return function () {
|
|
3675
|
+
flags.shadows = true;
|
|
3676
|
+
pc.alphaOnBrushed(0.1);
|
|
3677
|
+
pc.render();
|
|
3678
|
+
return this;
|
|
3679
|
+
};
|
|
3680
|
+
};
|
|
3681
|
+
|
|
3682
|
+
/**
|
|
3683
|
+
* Setup a new parallel coordinates chart.
|
|
3684
|
+
*
|
|
3685
|
+
* @param config
|
|
3686
|
+
* @param canvas
|
|
3687
|
+
* @param ctx
|
|
3688
|
+
* @returns {pc} a parcoords closure
|
|
3689
|
+
*/
|
|
3690
|
+
var init = function init(config, canvas, ctx) {
|
|
3691
|
+
/**
|
|
3692
|
+
* Create the chart within a container. The selector can also be a d3 selection.
|
|
3693
|
+
*
|
|
3694
|
+
* @param selection a d3 selection
|
|
3695
|
+
* @returns {pc} instance for chained api
|
|
3696
|
+
*/
|
|
3697
|
+
var _pc = function pc(selection) {
|
|
3698
|
+
selection = _pc.selection = d3Selection.select(selection);
|
|
3699
|
+
config.width = selection.node().clientWidth;
|
|
3700
|
+
config.height = selection.node().clientHeight;
|
|
3701
|
+
// canvas data layers
|
|
3702
|
+
['dots', 'foreground', 'brushed', 'marked', 'highlight'].forEach(function (layer) {
|
|
3703
|
+
canvas[layer] = selection.append('canvas').attr('class', layer).node();
|
|
3704
|
+
ctx[layer] = canvas[layer].getContext('2d');
|
|
3705
|
+
});
|
|
3706
|
+
|
|
3707
|
+
// svg tick and brush layers
|
|
3708
|
+
_pc.svg = selection.append('svg').attr('width', config.width).attr('height', config.height).style('font', '14px sans-serif').style('position', 'absolute').append('svg:g').attr('transform', 'translate(' + config.margin.left + ',' + config.margin.top + ')');
|
|
3709
|
+
// for chained api
|
|
3710
|
+
return _pc;
|
|
3711
|
+
};
|
|
3712
|
+
|
|
3713
|
+
// for partial-application style programming
|
|
3714
|
+
return _pc;
|
|
3715
|
+
};
|
|
3716
|
+
|
|
3717
|
+
var flip = function flip(config) {
|
|
3718
|
+
return function (d) {
|
|
3719
|
+
//__.dimensions[d].yscale.domain().reverse(); // does not work
|
|
3720
|
+
config.dimensions[d].yscale.domain(config.dimensions[d].yscale.domain().reverse()); // works
|
|
3721
|
+
|
|
3722
|
+
return this;
|
|
3723
|
+
};
|
|
3724
|
+
};
|
|
3725
|
+
|
|
3726
|
+
var detectDimensions = function detectDimensions(pc) {
|
|
3727
|
+
return function () {
|
|
3728
|
+
pc.dimensions(pc.applyDimensionDefaults());
|
|
3729
|
+
return this;
|
|
3730
|
+
};
|
|
3731
|
+
};
|
|
3732
|
+
|
|
3733
|
+
var scale = function scale(config, pc) {
|
|
3734
|
+
return function (d, domain) {
|
|
3735
|
+
config.dimensions[d].yscale.domain(domain);
|
|
3736
|
+
pc.render["default"]();
|
|
3737
|
+
pc.updateAxes();
|
|
3738
|
+
return this;
|
|
3739
|
+
};
|
|
3740
|
+
};
|
|
3741
|
+
|
|
3742
|
+
const version = "3.0.0";
|
|
3743
|
+
|
|
3744
|
+
var DefaultConfig = {
|
|
3745
|
+
data: [],
|
|
3746
|
+
highlighted: [],
|
|
3747
|
+
marked: [],
|
|
3748
|
+
dimensions: {},
|
|
3749
|
+
dimensionTitleRotation: 0,
|
|
3750
|
+
brushes: [],
|
|
3751
|
+
brushed: false,
|
|
3752
|
+
brushedColor: null,
|
|
3753
|
+
alphaOnBrushed: 0.0,
|
|
3754
|
+
lineWidth: 1.4,
|
|
3755
|
+
highlightedLineWidth: 3,
|
|
3756
|
+
mode: 'default',
|
|
3757
|
+
markedLineWidth: 3,
|
|
3758
|
+
markedShadowColor: '#ffffff',
|
|
3759
|
+
markedShadowBlur: 10,
|
|
3760
|
+
rate: 20,
|
|
3761
|
+
width: 600,
|
|
3762
|
+
height: 300,
|
|
3763
|
+
margin: {
|
|
3764
|
+
top: 24,
|
|
3765
|
+
right: 20,
|
|
3766
|
+
bottom: 12,
|
|
3767
|
+
left: 20
|
|
3768
|
+
},
|
|
3769
|
+
nullValueSeparator: 'undefined',
|
|
3770
|
+
// set to "top" or "bottom"
|
|
3771
|
+
nullValueSeparatorPadding: {
|
|
3772
|
+
top: 8,
|
|
3773
|
+
right: 0,
|
|
3774
|
+
bottom: 8,
|
|
3775
|
+
left: 0
|
|
3776
|
+
},
|
|
3777
|
+
color: '#069',
|
|
3778
|
+
composite: 'source-over',
|
|
3779
|
+
alpha: 0.7,
|
|
3780
|
+
bundlingStrength: 0.5,
|
|
3781
|
+
bundleDimension: null,
|
|
3782
|
+
smoothness: 0.0,
|
|
3783
|
+
showControlPoints: false,
|
|
3784
|
+
hideAxis: [],
|
|
3785
|
+
flipAxes: [],
|
|
3786
|
+
animationTime: 1100,
|
|
3787
|
+
// How long it takes to flip the axis when you double click
|
|
3788
|
+
rotateLabels: false
|
|
3789
|
+
};
|
|
3790
|
+
|
|
3791
|
+
var _this$1 = undefined;
|
|
3792
|
+
var initState = function initState(userConfig) {
|
|
3793
|
+
var config = Object.assign({}, DefaultConfig, userConfig);
|
|
3794
|
+
if (userConfig && userConfig.dimensionTitles) {
|
|
3795
|
+
console.warn('dimensionTitles passed in userConfig is deprecated. Add title to dimension object.');
|
|
3796
|
+
Object.entries(userConfig.dimensionTitles).forEach(function (_ref) {
|
|
3797
|
+
var _ref2 = _slicedToArray(_ref, 2),
|
|
3798
|
+
key = _ref2[0],
|
|
3799
|
+
value = _ref2[1];
|
|
3800
|
+
if (config.dimensions[key]) {
|
|
3801
|
+
config.dimensions[key].title = config.dimensions[key].title ? config.dimensions[key].title : value;
|
|
3802
|
+
} else {
|
|
3803
|
+
config.dimensions[key] = {
|
|
3804
|
+
title: value
|
|
3805
|
+
};
|
|
3806
|
+
}
|
|
3807
|
+
});
|
|
3808
|
+
}
|
|
3809
|
+
var eventTypes = ['render', 'resize', 'highlight', 'mark', 'brush', 'brushend', 'brushstart', 'axesreorder'].concat(Object.keys(config));
|
|
3810
|
+
var events = d3Dispatch.dispatch.apply(_this$1, eventTypes),
|
|
3811
|
+
flags = {
|
|
3812
|
+
brushable: false,
|
|
3813
|
+
reorderable: false,
|
|
3814
|
+
axes: false,
|
|
3815
|
+
interactive: false,
|
|
3816
|
+
debug: false
|
|
3817
|
+
},
|
|
3818
|
+
xscale = d3Scale.scalePoint(),
|
|
3819
|
+
dragging = {},
|
|
3820
|
+
axis = d3Axis.axisLeft().ticks(5),
|
|
3821
|
+
ctx = {},
|
|
3822
|
+
canvas = {};
|
|
3823
|
+
var brush = {
|
|
3824
|
+
modes: {
|
|
3825
|
+
None: {
|
|
3826
|
+
install: function install(pc) {},
|
|
3827
|
+
// Nothing to be done.
|
|
3828
|
+
uninstall: function uninstall(pc) {},
|
|
3829
|
+
// Nothing to be done.
|
|
3830
|
+
selected: function selected() {
|
|
3831
|
+
return [];
|
|
3832
|
+
},
|
|
3833
|
+
// Nothing to return
|
|
3834
|
+
brushState: function brushState() {
|
|
3835
|
+
return {};
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
},
|
|
3839
|
+
mode: 'None',
|
|
3840
|
+
predicate: 'AND',
|
|
3841
|
+
currentMode: function currentMode() {
|
|
3842
|
+
return this.modes[this.mode];
|
|
3843
|
+
}
|
|
3844
|
+
};
|
|
3845
|
+
return {
|
|
3846
|
+
config: config,
|
|
3847
|
+
events: events,
|
|
3848
|
+
eventTypes: eventTypes,
|
|
3849
|
+
flags: flags,
|
|
3850
|
+
xscale: xscale,
|
|
3851
|
+
dragging: dragging,
|
|
3852
|
+
axis: axis,
|
|
3853
|
+
ctx: ctx,
|
|
3854
|
+
canvas: canvas,
|
|
3855
|
+
brush: brush
|
|
3856
|
+
};
|
|
3857
|
+
};
|
|
3858
|
+
|
|
3859
|
+
var computeClusterCentroids = function computeClusterCentroids(config, d) {
|
|
3860
|
+
var clusterCentroids = new Map();
|
|
3861
|
+
var clusterCounts = new Map();
|
|
3862
|
+
// determine clusterCounts
|
|
3863
|
+
config.data.forEach(function (row) {
|
|
3864
|
+
var scaled = config.dimensions[d].yscale(row[d]);
|
|
3865
|
+
if (!clusterCounts.has(scaled)) {
|
|
3866
|
+
clusterCounts.set(scaled, 0);
|
|
3867
|
+
}
|
|
3868
|
+
var count = clusterCounts.get(scaled);
|
|
3869
|
+
clusterCounts.set(scaled, count + 1);
|
|
3870
|
+
});
|
|
3871
|
+
config.data.forEach(function (row) {
|
|
3872
|
+
Object.keys(config.dimensions).map(function (p) {
|
|
3873
|
+
var scaled = config.dimensions[d].yscale(row[d]);
|
|
3874
|
+
if (!clusterCentroids.has(scaled)) {
|
|
3875
|
+
var _map = new Map();
|
|
3876
|
+
clusterCentroids.set(scaled, _map);
|
|
3877
|
+
}
|
|
3878
|
+
if (!clusterCentroids.get(scaled).has(p)) {
|
|
3879
|
+
clusterCentroids.get(scaled).set(p, 0);
|
|
3880
|
+
}
|
|
3881
|
+
var value = clusterCentroids.get(scaled).get(p);
|
|
3882
|
+
value += config.dimensions[p].yscale(row[p]) / clusterCounts.get(scaled);
|
|
3883
|
+
clusterCentroids.get(scaled).set(p, value);
|
|
3884
|
+
});
|
|
3885
|
+
});
|
|
3886
|
+
return clusterCentroids;
|
|
3887
|
+
};
|
|
3888
|
+
|
|
3889
|
+
var _this = undefined;
|
|
3890
|
+
var without = function without(arr, items) {
|
|
3891
|
+
items.forEach(function (el) {
|
|
3892
|
+
delete arr[el];
|
|
3893
|
+
});
|
|
3894
|
+
return arr;
|
|
3895
|
+
};
|
|
3896
|
+
var sideEffects = function sideEffects(config, ctx, pc, xscale, axis, flags, brushedQueue, markedQueue, foregroundQueue) {
|
|
3897
|
+
return d3Dispatch.dispatch.apply(_this, Object.keys(config)).on('composite', function (d) {
|
|
3898
|
+
ctx.foreground.globalCompositeOperation = d.value;
|
|
3899
|
+
ctx.brushed.globalCompositeOperation = d.value;
|
|
3900
|
+
}).on('alpha', function (d) {
|
|
3901
|
+
ctx.foreground.globalAlpha = d.value;
|
|
3902
|
+
ctx.brushed.globalAlpha = d.value;
|
|
3903
|
+
}).on('brushedColor', function (d) {
|
|
3904
|
+
ctx.brushed.strokeStyle = d.value;
|
|
3905
|
+
}).on('width', function (d) {
|
|
3906
|
+
return pc.resize();
|
|
3907
|
+
}).on('height', function (d) {
|
|
3908
|
+
return pc.resize();
|
|
3909
|
+
}).on('margin', function (d) {
|
|
3910
|
+
return pc.resize();
|
|
3911
|
+
}).on('rate', function (d) {
|
|
3912
|
+
brushedQueue.rate(d.value);
|
|
3913
|
+
markedQueue.rate(d.value);
|
|
3914
|
+
foregroundQueue.rate(d.value);
|
|
3915
|
+
}).on('dimensions', function (d) {
|
|
3916
|
+
config.dimensions = pc.applyDimensionDefaults(Object.keys(d.value));
|
|
3917
|
+
xscale.domain(pc.getOrderedDimensionKeys());
|
|
3918
|
+
pc.sortDimensions();
|
|
3919
|
+
if (flags.interactive) {
|
|
3920
|
+
pc.render().updateAxes();
|
|
3921
|
+
}
|
|
3922
|
+
}).on('bundleDimension', function (d) {
|
|
3923
|
+
if (!Object.keys(config.dimensions).length) pc.detectDimensions();
|
|
3924
|
+
pc.autoscale();
|
|
3925
|
+
if (typeof d.value === 'number') {
|
|
3926
|
+
if (d.value < Object.keys(config.dimensions).length) {
|
|
3927
|
+
config.bundleDimension = config.dimensions[d.value];
|
|
3928
|
+
} else if (d.value < config.hideAxis.length) {
|
|
3929
|
+
config.bundleDimension = config.hideAxis[d.value];
|
|
3930
|
+
}
|
|
3931
|
+
} else {
|
|
3932
|
+
config.bundleDimension = d.value;
|
|
3933
|
+
}
|
|
3934
|
+
config.clusterCentroids = computeClusterCentroids(config, config.bundleDimension);
|
|
3935
|
+
if (flags.interactive) {
|
|
3936
|
+
pc.render();
|
|
3937
|
+
}
|
|
3938
|
+
}).on('hideAxis', function (d) {
|
|
3939
|
+
pc.brushReset();
|
|
3940
|
+
pc.dimensions(pc.applyDimensionDefaults());
|
|
3941
|
+
pc.dimensions(without(config.dimensions, d.value));
|
|
3942
|
+
pc.render();
|
|
3943
|
+
}).on('flipAxes', function (d) {
|
|
3944
|
+
if (d.value && d.value.length) {
|
|
3945
|
+
d.value.forEach(function (dimension) {
|
|
3946
|
+
flipAxisAndUpdatePCP(config, pc, axis)(dimension);
|
|
3947
|
+
});
|
|
3948
|
+
pc.updateAxes(0);
|
|
3949
|
+
}
|
|
3950
|
+
});
|
|
3951
|
+
};
|
|
3952
|
+
|
|
3953
|
+
var getset = function getset(obj, state, events, side_effects) {
|
|
3954
|
+
Object.keys(state).forEach(function (key) {
|
|
3955
|
+
obj[key] = function (x) {
|
|
3956
|
+
if (!arguments.length) {
|
|
3957
|
+
return state[key];
|
|
3958
|
+
}
|
|
3959
|
+
if (key === 'dimensions' && Object.prototype.toString.call(x) === '[object Array]') {
|
|
3960
|
+
console.warn('pc.dimensions([]) is deprecated, use pc.dimensions({})');
|
|
3961
|
+
x = obj.applyDimensionDefaults(x);
|
|
3962
|
+
}
|
|
3963
|
+
var old = state[key];
|
|
3964
|
+
state[key] = x;
|
|
3965
|
+
side_effects.call(key, obj, {
|
|
3966
|
+
value: x,
|
|
3967
|
+
previous: old
|
|
3968
|
+
});
|
|
3969
|
+
events.call(key, obj, {
|
|
3970
|
+
value: x,
|
|
3971
|
+
previous: old
|
|
3972
|
+
});
|
|
3973
|
+
return obj;
|
|
3974
|
+
};
|
|
3975
|
+
});
|
|
3976
|
+
};
|
|
3977
|
+
|
|
3978
|
+
// side effects for setters
|
|
3979
|
+
var d3_rebind = function d3_rebind(target, source, method) {
|
|
3980
|
+
return function () {
|
|
3981
|
+
var value = method.apply(source, arguments);
|
|
3982
|
+
return value === source ? target : value;
|
|
3983
|
+
};
|
|
3984
|
+
};
|
|
3985
|
+
var _rebind = function _rebind(target, source, method) {
|
|
3986
|
+
target[method] = d3_rebind(target, source, source[method]);
|
|
3987
|
+
return target;
|
|
3988
|
+
};
|
|
3989
|
+
var bindEvents = function bindEvents(__, ctx, pc, xscale, flags, brushedQueue, markedQueue, foregroundQueue, events, axis) {
|
|
3990
|
+
var side_effects = sideEffects(__, ctx, pc, xscale, axis, flags, brushedQueue, markedQueue, foregroundQueue);
|
|
3991
|
+
|
|
3992
|
+
// create getter/setters
|
|
3993
|
+
getset(pc, __, events, side_effects);
|
|
3994
|
+
|
|
3995
|
+
// expose events
|
|
3996
|
+
// getter/setter with event firing
|
|
3997
|
+
_rebind(pc, events, 'on');
|
|
3998
|
+
_rebind(pc, axis, 'ticks');
|
|
3999
|
+
};
|
|
4000
|
+
|
|
4001
|
+
// misc
|
|
4002
|
+
var ParCoords = function ParCoords(userConfig) {
|
|
4003
|
+
var state = initState(userConfig);
|
|
4004
|
+
var config = state.config,
|
|
4005
|
+
events = state.events,
|
|
4006
|
+
flags = state.flags,
|
|
4007
|
+
xscale = state.xscale,
|
|
4008
|
+
dragging = state.dragging,
|
|
4009
|
+
axis = state.axis,
|
|
4010
|
+
ctx = state.ctx,
|
|
4011
|
+
canvas = state.canvas,
|
|
4012
|
+
brush = state.brush;
|
|
4013
|
+
var pc = init(config, canvas, ctx);
|
|
4014
|
+
var position = function position(d) {
|
|
4015
|
+
if (xscale.range().length === 0) {
|
|
4016
|
+
xscale.range([0, w(config)], 1);
|
|
4017
|
+
}
|
|
4018
|
+
return dragging[d] == null ? xscale(d) : dragging[d];
|
|
4019
|
+
};
|
|
4020
|
+
var brushedQueue = renderQueue(pathBrushed(config, ctx, position)).rate(50).clear(function () {
|
|
4021
|
+
return pc.clear('brushed');
|
|
4022
|
+
});
|
|
4023
|
+
var markedQueue = renderQueue(pathMark(config, ctx, position)).rate(50).clear(function () {
|
|
4024
|
+
return pc.clear('marked');
|
|
4025
|
+
});
|
|
4026
|
+
var foregroundQueue = renderQueue(pathForeground(config, ctx, position)).rate(50).clear(function () {
|
|
4027
|
+
pc.clear('foreground');
|
|
4028
|
+
pc.clear('highlight');
|
|
4029
|
+
});
|
|
4030
|
+
bindEvents(config, ctx, pc, xscale, flags, brushedQueue, markedQueue, foregroundQueue, events, axis);
|
|
4031
|
+
|
|
4032
|
+
// expose the state of the chart
|
|
4033
|
+
pc.state = config;
|
|
4034
|
+
pc.flags = flags;
|
|
4035
|
+
pc.autoscale = autoscale(config, pc, xscale, ctx);
|
|
4036
|
+
pc.scale = scale(config, pc);
|
|
4037
|
+
pc.flip = flip(config);
|
|
4038
|
+
pc.commonScale = commonScale(config, pc);
|
|
4039
|
+
pc.detectDimensions = detectDimensions(pc);
|
|
4040
|
+
// attempt to determine types of each dimension based on first row of data
|
|
4041
|
+
pc.detectDimensionTypes = detectDimensionTypes;
|
|
4042
|
+
pc.applyDimensionDefaults = applyDimensionDefaults(config, pc);
|
|
4043
|
+
pc.getOrderedDimensionKeys = getOrderedDimensionKeys(config);
|
|
4044
|
+
|
|
4045
|
+
//Renders the polylines.
|
|
4046
|
+
pc.render = render(config, pc, events);
|
|
4047
|
+
pc.renderBrushed = renderBrushed(config, pc, events);
|
|
4048
|
+
pc.renderMarked = renderMarked(config, pc, events);
|
|
4049
|
+
pc.render["default"] = renderDefault(config, pc, ctx, position);
|
|
4050
|
+
pc.render.queue = renderDefaultQueue(config, pc, foregroundQueue);
|
|
4051
|
+
pc.renderBrushed["default"] = renderBrushedDefault(config, ctx, position, pc, brush);
|
|
4052
|
+
pc.renderBrushed.queue = renderBrushedQueue(config, brush, brushedQueue);
|
|
4053
|
+
pc.renderMarked["default"] = renderMarkedDefault(config, pc, ctx, position);
|
|
4054
|
+
pc.renderMarked.queue = renderMarkedQueue(config, markedQueue);
|
|
4055
|
+
pc.compute_real_centroids = computeRealCentroids(config, position);
|
|
4056
|
+
pc.shadows = shadows(flags, pc);
|
|
4057
|
+
pc.axisDots = axisDots(config, pc, position);
|
|
4058
|
+
pc.clear = clear(config, pc, ctx, brush);
|
|
4059
|
+
pc.createAxes = createAxes(config, pc, xscale, flags, axis);
|
|
4060
|
+
pc.removeAxes = removeAxes(pc);
|
|
4061
|
+
pc.updateAxes = updateAxes(config, pc, position, axis, flags);
|
|
4062
|
+
pc.applyAxisConfig = applyAxisConfig;
|
|
4063
|
+
pc.brushable = brushable(config, pc, flags);
|
|
4064
|
+
pc.brushReset = brushReset(config, pc);
|
|
4065
|
+
pc.selected = selected(config, pc);
|
|
4066
|
+
pc.reorderable = reorderable(config, pc, xscale, position, dragging, flags);
|
|
4067
|
+
|
|
4068
|
+
// Reorder dimensions, such that the highest value (visually) is on the left and
|
|
4069
|
+
// the lowest on the right. Visual values are determined by the data values in
|
|
4070
|
+
// the given row.
|
|
4071
|
+
pc.reorder = reorder(config, pc, xscale);
|
|
4072
|
+
pc.sortDimensionsByRowData = sortDimensionsByRowData(config);
|
|
4073
|
+
pc.sortDimensions = sortDimensions(config, position);
|
|
4074
|
+
|
|
4075
|
+
// pairs of adjacent dimensions
|
|
4076
|
+
pc.adjacent_pairs = adjacentPairs;
|
|
4077
|
+
pc.interactive = interactive(flags);
|
|
4078
|
+
|
|
4079
|
+
// expose internal state
|
|
4080
|
+
pc.xscale = xscale;
|
|
4081
|
+
pc.ctx = ctx;
|
|
4082
|
+
pc.canvas = canvas;
|
|
4083
|
+
pc.g = function () {
|
|
4084
|
+
return pc._g;
|
|
4085
|
+
};
|
|
4086
|
+
|
|
4087
|
+
// rescale for height, width and margins
|
|
4088
|
+
// TODO currently assumes chart is brushable, and destroys old brushes
|
|
4089
|
+
pc.resize = resize(config, pc, flags, events);
|
|
4090
|
+
|
|
4091
|
+
// highlight an array of data
|
|
4092
|
+
pc.highlight = highlight(config, pc, canvas, events, ctx, position);
|
|
4093
|
+
// clear highlighting
|
|
4094
|
+
pc.unhighlight = unhighlight(config, pc, canvas);
|
|
4095
|
+
|
|
4096
|
+
// mark an array of data
|
|
4097
|
+
pc.mark = mark(config, pc, canvas, events, ctx, position);
|
|
4098
|
+
// clear marked data
|
|
4099
|
+
pc.unmark = unmark(config, pc, canvas);
|
|
4100
|
+
|
|
4101
|
+
// calculate 2d intersection of line a->b with line c->d
|
|
4102
|
+
// points are objects with x and y properties
|
|
4103
|
+
pc.intersection = intersection;
|
|
4104
|
+
|
|
4105
|
+
// Merges the canvases and SVG elements into one canvas element which is then passed into the callback
|
|
4106
|
+
// (so you can choose to save it to disk, etc.)
|
|
4107
|
+
pc.mergeParcoords = mergeParcoords(pc);
|
|
4108
|
+
pc.brushModes = function () {
|
|
4109
|
+
return Object.getOwnPropertyNames(brush.modes);
|
|
4110
|
+
};
|
|
4111
|
+
pc.brushMode = brushMode(brush, config, pc);
|
|
4112
|
+
|
|
4113
|
+
// install brushes
|
|
4114
|
+
install1DAxes(brush, config, pc, events);
|
|
4115
|
+
install2DStrums(brush, config, pc, events, xscale);
|
|
4116
|
+
installAngularBrush(brush, config, pc, events, xscale);
|
|
4117
|
+
install1DMultiAxes(brush, config, pc, events);
|
|
4118
|
+
pc.version = version;
|
|
4119
|
+
// this descriptive text should live with other introspective methods
|
|
4120
|
+
pc.toString = toString(config);
|
|
4121
|
+
pc.toType = toType;
|
|
4122
|
+
// try to coerce to number before returning type
|
|
4123
|
+
pc.toTypeCoerceNumbers = toTypeCoerceNumbers;
|
|
4124
|
+
return pc;
|
|
4125
|
+
};
|
|
4126
|
+
|
|
4127
|
+
return ParCoords;
|
|
4128
|
+
|
|
4129
|
+
}));
|
|
4130
|
+
//# sourceMappingURL=parcoords.js.map
|