1 |
|
2 | const Util = require('./util');
|
3 | const Interaction = require('./base');
|
4 |
|
5 |
|
6 | const BRUSH_TYPES = ['X', 'Y', 'XY', 'POLYGON'];
|
7 | const DEFAULT_TYPE = 'XY';
|
8 |
|
9 | class Brush extends Interaction {
|
10 | getDefaultCfg() {
|
11 | const cfg = super.getDefaultCfg();
|
12 | return Util.mix({}, cfg, {
|
13 | type: DEFAULT_TYPE,
|
14 | startPoint: null,
|
15 | brushing: false,
|
16 | dragging: false,
|
17 | brushShape: null,
|
18 | container: null,
|
19 | polygonPath: null,
|
20 | style: {
|
21 | fill: '#C5D4EB',
|
22 | opacity: 0.3,
|
23 | lineWidth: 1,
|
24 | stroke: '#82A6DD'
|
25 | },
|
26 | draggable: false,
|
27 | dragOffX: 0,
|
28 | dragOffY: 0,
|
29 | inPlot: true,
|
30 | xField: null,
|
31 | yField: null
|
32 | });
|
33 | }
|
34 |
|
35 | constructor(cfg, view) {
|
36 | super(cfg, view);
|
37 | const me = this;
|
38 | me.filter = !me.draggable;
|
39 | me.type = me.type.toUpperCase();
|
40 | me.chart = view;
|
41 |
|
42 | if (BRUSH_TYPES.indexOf(me.type) === -1) {
|
43 | me.type = DEFAULT_TYPE;
|
44 | }
|
45 | const canvas = me.canvas;
|
46 | if (canvas) {
|
47 | let plotRange;
|
48 | canvas.get('children').map(child => {
|
49 | if (child.get('type') === 'plotBack') {
|
50 | plotRange = child.get('plotRange');
|
51 | return false;
|
52 | }
|
53 | return child;
|
54 | });
|
55 | me.plot = {
|
56 | start: plotRange.bl,
|
57 | end: plotRange.tr
|
58 | };
|
59 | }
|
60 | if (view) {
|
61 | const coord = view.get('coord');
|
62 | me.plot = {
|
63 | start: coord.start,
|
64 | end: coord.end
|
65 | };
|
66 | const xScales = view._getScales('x');
|
67 | const yScales = view._getScales('y');
|
68 | me.xScale = me.xField ? xScales[me.xField] : view.getXScale();
|
69 | me.yScale = me.yField ? yScales[me.yField] : view.getYScales()[0];
|
70 | }
|
71 | }
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 | start(ev) {
|
81 | const me = this;
|
82 | const { canvas, type, brushShape } = me;
|
83 |
|
84 | if (!type) return;
|
85 |
|
86 | const startPoint = { x: ev.offsetX, y: ev.offsetY };
|
87 | if (!startPoint.x) return;
|
88 | const isInPlot = me.plot && me.inPlot;
|
89 | const canvasDOM = canvas.get('canvasDOM');
|
90 | const pixelRatio = canvas.get('pixelRatio');
|
91 |
|
92 | if (me.selection) me.selection = null;
|
93 |
|
94 | if (me.draggable && brushShape && !brushShape.get('destroyed')) {
|
95 |
|
96 | if (brushShape.isHit(startPoint.x * pixelRatio, startPoint.y * pixelRatio)) {
|
97 | canvasDOM.style.cursor = 'move';
|
98 | me.selection = brushShape;
|
99 | me.dragging = true;
|
100 | if (type === 'X') {
|
101 | me.dragoffX = startPoint.x - brushShape.attr('x');
|
102 | me.dragoffY = 0;
|
103 | } else if (type === 'Y') {
|
104 | me.dragoffX = 0;
|
105 | me.dragoffY = startPoint.y - brushShape.attr('y');
|
106 | } else if (type === 'XY') {
|
107 | me.dragoffX = startPoint.x - brushShape.attr('x');
|
108 | me.dragoffY = startPoint.y - brushShape.attr('y');
|
109 | } else if (type === 'POLYGON') {
|
110 | const box = brushShape.getBBox();
|
111 | me.dragoffX = startPoint.x - box.minX;
|
112 | me.dragoffY = startPoint.y - box.minY;
|
113 | }
|
114 |
|
115 | if (isInPlot) {
|
116 | me.selection.attr('clip', canvas.addShape('rect', {
|
117 | attrs: {
|
118 | x: this.plot.start.x,
|
119 | y: this.plot.end.y,
|
120 | width: this.plot.end.x - this.plot.start.x,
|
121 | height: this.plot.start.y - this.plot.end.y,
|
122 | fill: '#fff',
|
123 | fillOpacity: 0
|
124 | }
|
125 | }));
|
126 | }
|
127 | me.onDragstart && me.onDragstart(ev);
|
128 | }
|
129 | me.prePoint = startPoint;
|
130 | }
|
131 |
|
132 | if (!me.dragging) {
|
133 |
|
134 | me.onBrushstart && me.onBrushstart(startPoint);
|
135 | let container = me.container;
|
136 | if (isInPlot) {
|
137 | const { start, end } = me.plot;
|
138 | if (startPoint.x < start.x || startPoint.x > end.x || startPoint.y < end.y || startPoint.y > start.y) return;
|
139 | }
|
140 | canvasDOM.style.cursor = 'crosshair';
|
141 | me.startPoint = startPoint;
|
142 | me.brushShape = null;
|
143 | me.brushing = true;
|
144 |
|
145 | if (!container) {
|
146 | container = canvas.addGroup({
|
147 | zIndex: 5
|
148 | });
|
149 | container.initTransform();
|
150 | } else {
|
151 | container.clear();
|
152 | }
|
153 | me.container = container;
|
154 |
|
155 | if (type === 'POLYGON') me.polygonPath = `M ${startPoint.x} ${startPoint.y}`;
|
156 | }
|
157 | }
|
158 | process(ev) {
|
159 | const me = this;
|
160 | const { brushing, dragging, type, plot, startPoint, xScale, yScale, canvas } = me;
|
161 |
|
162 | if (!brushing && !dragging) {
|
163 | return;
|
164 | }
|
165 | let currentPoint = {
|
166 | x: ev.offsetX,
|
167 | y: ev.offsetY
|
168 | };
|
169 | const canvasDOM = canvas.get('canvasDOM');
|
170 |
|
171 | if (brushing) {
|
172 | canvasDOM.style.cursor = 'crosshair';
|
173 | const { start, end } = plot;
|
174 | let polygonPath = me.polygonPath;
|
175 | let brushShape = me.brushShape;
|
176 | const container = me.container;
|
177 | if (me.plot && me.inPlot) {
|
178 | currentPoint = me._limitCoordScope(currentPoint);
|
179 | }
|
180 |
|
181 | let rectStartX;
|
182 | let rectStartY;
|
183 | let rectWidth;
|
184 | let rectHeight;
|
185 |
|
186 | if (type === 'Y') {
|
187 | rectStartX = start.x;
|
188 | rectStartY = currentPoint.y >= startPoint.y ? startPoint.y : currentPoint.y;
|
189 | rectWidth = Math.abs(start.x - end.x);
|
190 | rectHeight = Math.abs(startPoint.y - currentPoint.y);
|
191 | } else if (type === 'X') {
|
192 | rectStartX = currentPoint.x >= startPoint.x ? startPoint.x : currentPoint.x;
|
193 | rectStartY = end.y;
|
194 | rectWidth = Math.abs(startPoint.x - currentPoint.x);
|
195 | rectHeight = Math.abs(end.y - start.y);
|
196 | } else if (type === 'XY') {
|
197 | if (currentPoint.x >= startPoint.x) {
|
198 | rectStartX = startPoint.x;
|
199 | rectStartY = currentPoint.y >= startPoint.y ? startPoint.y : currentPoint.y;
|
200 | } else {
|
201 | rectStartX = currentPoint.x;
|
202 | rectStartY = currentPoint.y >= startPoint.y ? startPoint.y : currentPoint.y;
|
203 | }
|
204 | rectWidth = Math.abs(startPoint.x - currentPoint.x);
|
205 | rectHeight = Math.abs(startPoint.y - currentPoint.y);
|
206 | } else if (type === 'POLYGON') {
|
207 | polygonPath += `L ${currentPoint.x} ${currentPoint.y}`;
|
208 | me.polygonPath = polygonPath;
|
209 | if (!brushShape) {
|
210 | brushShape = container.addShape('path', {
|
211 | attrs: Util.mix(me.style, {
|
212 | path: polygonPath
|
213 | })
|
214 | });
|
215 | } else {
|
216 | !brushShape.get('destroyed') && brushShape.attr(Util.mix({}, brushShape.__attrs, {
|
217 | path: polygonPath
|
218 | }));
|
219 | }
|
220 | }
|
221 | if (type !== 'POLYGON') {
|
222 | if (!brushShape) {
|
223 | brushShape = container.addShape('rect', {
|
224 | attrs: Util.mix(me.style, {
|
225 | x: rectStartX,
|
226 | y: rectStartY,
|
227 | width: rectWidth,
|
228 | height: rectHeight
|
229 | })
|
230 | });
|
231 | } else {
|
232 | !brushShape.get('destroyed') && brushShape.attr(Util.mix({}, brushShape.__attrs, {
|
233 | x: rectStartX,
|
234 | y: rectStartY,
|
235 | width: rectWidth,
|
236 | height: rectHeight
|
237 | }));
|
238 | }
|
239 | }
|
240 |
|
241 | me.brushShape = brushShape;
|
242 | } else if (dragging) {
|
243 | canvasDOM.style.cursor = 'move';
|
244 | const selection = me.selection;
|
245 | if (selection && !selection.get('destroyed')) {
|
246 | if (type === 'POLYGON') {
|
247 | const prePoint = me.prePoint;
|
248 | me.selection.translate(currentPoint.x - prePoint.x, currentPoint.y - prePoint.y);
|
249 | } else {
|
250 | me.dragoffX && selection.attr('x', currentPoint.x - me.dragoffX);
|
251 | me.dragoffY && selection.attr('y', currentPoint.y - me.dragoffY);
|
252 | }
|
253 | }
|
254 | }
|
255 |
|
256 | me.prePoint = currentPoint;
|
257 | canvas.draw();
|
258 | const { data, shapes, xValues, yValues } = me._getSelected();
|
259 | const eventObj = {
|
260 | data,
|
261 | shapes,
|
262 | x: currentPoint.x,
|
263 | y: currentPoint.y
|
264 | };
|
265 |
|
266 | if (xScale) {
|
267 | eventObj[xScale.field] = xValues;
|
268 | }
|
269 | if (yScale) {
|
270 | eventObj[yScale.field] = yValues;
|
271 | }
|
272 | me.onDragmove && me.onDragmove(eventObj);
|
273 | me.onBrushmove && me.onBrushmove(eventObj);
|
274 | }
|
275 | end(ev) {
|
276 | const me = this;
|
277 | const { data, shapes, xValues, yValues, canvas, type, startPoint, chart, container, xScale, yScale } = me;
|
278 | const { offsetX, offsetY } = ev;
|
279 | const canvasDOM = canvas.get('canvasDOM');
|
280 | canvasDOM.style.cursor = 'default';
|
281 |
|
282 | if (Math.abs(startPoint.x - offsetX) <= 1 && Math.abs(startPoint.y - offsetY) <= 1) {
|
283 |
|
284 | me.brushing = false;
|
285 | me.dragging = false;
|
286 | return;
|
287 | }
|
288 |
|
289 | const eventObj = {
|
290 | data,
|
291 | shapes,
|
292 | x: offsetX,
|
293 | y: offsetY
|
294 | };
|
295 | if (xScale) {
|
296 | eventObj[xScale.field] = xValues;
|
297 | }
|
298 | if (yScale) {
|
299 | eventObj[yScale.field] = yValues;
|
300 | }
|
301 |
|
302 | if (me.dragging) {
|
303 | me.dragging = false;
|
304 | me.onDragend && me.onDragend(eventObj);
|
305 | } else if (me.brushing) {
|
306 | me.brushing = false;
|
307 | const brushShape = me.brushShape;
|
308 | let polygonPath = me.polygonPath;
|
309 |
|
310 | if (type === 'POLYGON') {
|
311 | polygonPath += 'z';
|
312 |
|
313 | brushShape && !brushShape.get('destroyed') && brushShape.attr(Util.mix({}, brushShape.__attrs, {
|
314 | path: polygonPath
|
315 | }));
|
316 | me.polygonPath = polygonPath;
|
317 | canvas.draw();
|
318 | }
|
319 |
|
320 | if (me.onBrushend) {
|
321 | me.onBrushend(eventObj);
|
322 | } else if (chart && me.filter) {
|
323 | container.clear();
|
324 |
|
325 | if (type === 'X') {
|
326 | xScale && chart.filter(xScale.field, val => {
|
327 | return xValues.indexOf(val) > -1;
|
328 | });
|
329 | } else if (type === 'Y') {
|
330 | yScale && chart.filter(yScale.field, val => {
|
331 | return yValues.indexOf(val) > -1;
|
332 | });
|
333 | } else {
|
334 | xScale && chart.filter(xScale.field, val => {
|
335 | return xValues.indexOf(val) > -1;
|
336 | });
|
337 | yScale && chart.filter(yScale.field, val => {
|
338 | return yValues.indexOf(val) > -1;
|
339 | });
|
340 | }
|
341 | chart.repaint();
|
342 | }
|
343 | }
|
344 | }
|
345 |
|
346 | reset() {
|
347 | const me = this;
|
348 | const { chart, filter } = me;
|
349 | if (chart && filter) {
|
350 | chart.get('options').filters = {};
|
351 | chart.repaint();
|
352 | }
|
353 | }
|
354 |
|
355 | _limitCoordScope(point) {
|
356 | const { plot } = this;
|
357 | const { start, end } = plot;
|
358 |
|
359 | if (point.x < start.x) {
|
360 | point.x = start.x;
|
361 | }
|
362 | if (point.x > end.x) {
|
363 | point.x = end.x;
|
364 | }
|
365 | if (point.y < end.y) {
|
366 | point.y = end.y;
|
367 | }
|
368 | if (point.y > start.y) {
|
369 | point.y = start.y;
|
370 | }
|
371 | return point;
|
372 | }
|
373 |
|
374 | _getSelected() {
|
375 | const me = this;
|
376 | const { chart, xScale, yScale, brushShape, canvas } = me;
|
377 | const pixelRatio = canvas.get('pixelRatio');
|
378 | const selectedShapes = [];
|
379 | const xValues = [];
|
380 | const yValues = [];
|
381 | const selectedData = [];
|
382 | if (chart) {
|
383 | const geoms = chart.get('geoms');
|
384 | geoms.map(geom => {
|
385 | const shapes = geom.getShapes();
|
386 | shapes.map(shape => {
|
387 | let shapeData = shape.get('origin');
|
388 | if (!Array.isArray(shapeData)) {
|
389 |
|
390 | shapeData = [shapeData];
|
391 | }
|
392 |
|
393 | shapeData.map(each => {
|
394 | if (brushShape.isHit(each.x * pixelRatio, each.y * pixelRatio)) {
|
395 | selectedShapes.push(shape);
|
396 | const origin = each._origin;
|
397 | selectedData.push(origin);
|
398 | xScale && xValues.push(origin[xScale.field]);
|
399 | yScale && yValues.push(origin[yScale.field]);
|
400 | }
|
401 | return each;
|
402 | });
|
403 |
|
404 | return shape;
|
405 | });
|
406 | return geom;
|
407 | });
|
408 | }
|
409 | me.shapes = selectedShapes;
|
410 | me.xValues = xValues;
|
411 | me.yValues = yValues;
|
412 | me.data = selectedData;
|
413 | return {
|
414 | data: selectedData,
|
415 | xValues,
|
416 | yValues,
|
417 | shapes: selectedShapes
|
418 | };
|
419 | }
|
420 | }
|
421 |
|
422 |
|
423 |
|
424 |
|
425 | module.exports = Brush; |
\ | No newline at end of file |