UNPKG

12.1 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Selection = exports.select = void 0;
4const g_1 = require("@antv/g");
5const d3_array_1 = require("d3-array");
6const helper_1 = require("./helper");
7function select(node) {
8 return new Selection([node], null, node, node.ownerDocument);
9}
10exports.select = select;
11/**
12 * A simple implementation of d3-selection for @antv/g.
13 * It has the core features of d3-selection and extended ability.
14 * Every methods of selection returns new selection if elements
15 * are mutated(e.g. append, remove), otherwise return the selection itself(e.g. attr, style).
16 * @see https://github.com/d3/d3-selection
17 * @see https://github.com/antvis/g
18 * @todo Nested selections.
19 * @todo More useful functor.
20 */
21class Selection {
22 constructor(elements = null, data = null, parent = null, document = null, selections = [
23 null,
24 null,
25 null,
26 null,
27 null,
28 ], transitions = [], updateElements = []) {
29 this._elements = Array.from(elements);
30 this._data = data;
31 this._parent = parent;
32 this._document = document;
33 this._enter = selections[0];
34 this._update = selections[1];
35 this._exit = selections[2];
36 this._merge = selections[3];
37 this._split = selections[4];
38 this._transitions = transitions;
39 this._facetElements = updateElements;
40 }
41 selectAll(selector) {
42 const elements = typeof selector === 'string'
43 ? this._parent.querySelectorAll(selector)
44 : selector;
45 return new Selection(elements, null, this._elements[0], this._document);
46 }
47 selectFacetAll(selector) {
48 const elements = typeof selector === 'string'
49 ? this._parent.querySelectorAll(selector)
50 : selector;
51 return new Selection(this._elements, null, this._parent, this._document, undefined, undefined, elements);
52 }
53 /**
54 * @todo Replace with querySelector which has bug now.
55 */
56 select(selector) {
57 const element = typeof selector === 'string'
58 ? this._parent.querySelectorAll(selector)[0] || null
59 : selector;
60 return new Selection([element], null, element, this._document);
61 }
62 append(node) {
63 const callback = typeof node === 'function' ? node : () => this.createElement(node);
64 const elements = [];
65 if (this._data !== null) {
66 // For empty selection, append new element to parent.
67 // Each element is bind with datum.
68 for (let i = 0; i < this._data.length; i++) {
69 const d = this._data[i];
70 const [datum, from] = Array.isArray(d) ? d : [d, null];
71 const newElement = callback(datum, i);
72 newElement.__data__ = datum;
73 if (from !== null)
74 newElement.__fromElements__ = from;
75 this._parent.appendChild(newElement);
76 elements.push(newElement);
77 }
78 return new Selection(elements, null, this._parent, this._document);
79 }
80 else {
81 // For non-empty selection, append new element to
82 // selected element and return new selection.
83 for (let i = 0; i < this._elements.length; i++) {
84 const element = this._elements[i];
85 const datum = element.__data__;
86 const newElement = callback(datum, i);
87 element.appendChild(newElement);
88 elements.push(newElement);
89 }
90 return new Selection(elements, null, elements[0], this._document);
91 }
92 }
93 maybeAppend(id, node, className) {
94 const element = this._elements[0];
95 const child = element.getElementById(id);
96 if (child) {
97 return new Selection([child], null, this._parent, this._document);
98 }
99 const newChild = typeof node === 'string' ? this.createElement(node) : node();
100 newChild.id = id;
101 if (className)
102 newChild.className = className;
103 element.appendChild(newChild);
104 return new Selection([newChild], null, this._parent, this._document);
105 }
106 /**
107 * Bind data to elements, and produce three selection:
108 * Enter: Selection with empty elements and data to be bind to elements.
109 * Update: Selection with elements to be updated.
110 * Exit: Selection with elements to be removed.
111 */
112 data(data, id = (d) => d, groupId = () => null) {
113 // An Array of new data.
114 const enter = [];
115 // An Array of elements to be updated.
116 const update = [];
117 // A Set of elements to be removed.
118 const exit = new Set(this._elements);
119 // An Array of data to be merged into one element.
120 const merge = [];
121 // A Set of elements to be split into multiple datum.
122 const split = new Set();
123 // A Map from key to each element.
124 const keyElement = new Map(this._elements.map((d, i) => [id(d.__data__, i), d]));
125 // A Map from key to exist element. The Update Selection
126 // can get element from this map, this is for diff among
127 // facets.
128 const keyUpdateElement = new Map(this._facetElements.map((d, i) => [id(d.__data__, i), d]));
129 // A Map from groupKey to a group of elements.
130 const groupKeyElements = (0, d3_array_1.group)(this._elements, (d) => groupId(d.__data__));
131 // Diff data with selection(elements with data).
132 // !!! Note
133 // The switch is strictly ordered, not not change the order of them.
134 for (let i = 0; i < data.length; i++) {
135 const datum = data[i];
136 const key = id(datum, i);
137 const groupKey = groupId(datum, i);
138 // Append element to update selection if incoming data has
139 // exactly the same key with elements.
140 if (keyElement.has(key)) {
141 const element = keyElement.get(key);
142 element.__data__ = datum;
143 element.__facet__ = false;
144 update.push(element);
145 exit.delete(element);
146 keyElement.delete(key);
147 // Append element to update selection if incoming data has
148 // exactly the same key with updateElements.
149 }
150 else if (keyUpdateElement.has(key)) {
151 const element = keyUpdateElement.get(key);
152 element.__data__ = datum;
153 // Flag this element should update its parentNode.
154 element.__facet__ = true;
155 update.push(element);
156 keyUpdateElement.delete(key);
157 // Append datum to merge selection if existed elements has
158 // its key as groupKey.
159 }
160 else if (groupKeyElements.has(key)) {
161 const group = groupKeyElements.get(key);
162 merge.push([datum, group]);
163 for (const element of group)
164 exit.delete(element);
165 groupKeyElements.delete(key);
166 // Append element to split selection if incoming data has
167 // groupKey as its key, and bind to datum for it.
168 }
169 else if (keyElement.has(groupKey)) {
170 const element = keyElement.get(groupKey);
171 if (element.__toData__)
172 element.__toData__.push(datum);
173 else
174 element.__toData__ = [datum];
175 split.add(element);
176 exit.delete(element);
177 }
178 else {
179 // @todo Data with non-unique key.
180 enter.push(datum);
181 }
182 }
183 // Create new selection with enter, update and exit.
184 const S = [
185 new Selection([], enter, this._parent, this._document),
186 new Selection(update, null, this._parent, this._document),
187 new Selection(exit, null, this._parent, this._document),
188 new Selection([], merge, this._parent, this._document),
189 new Selection(split, null, this._parent, this._document),
190 ];
191 return new Selection(this._elements, null, this._parent, this._document, S);
192 }
193 merge(other) {
194 const elements = [...this._elements, ...other._elements];
195 const transitions = [...this._transitions, ...other._transitions];
196 return new Selection(elements, null, this._parent, this._document, undefined, transitions);
197 }
198 createElement(type) {
199 if (this._document) {
200 return this._document.createElement(type, {});
201 }
202 const Ctor = Selection.registry[type];
203 if (Ctor)
204 return new Ctor();
205 return (0, helper_1.error)(`Unknown node type: ${type}`);
206 }
207 /**
208 * Apply callback for each selection(enter, update, exit)
209 * and merge them into one selection.
210 */
211 join(enter = (d) => d, update = (d) => d, exit = (d) => d.remove(), merge = (d) => d, split = (d) => d.remove()) {
212 const newEnter = enter(this._enter);
213 const newUpdate = update(this._update);
214 const newExit = exit(this._exit);
215 const newMerge = merge(this._merge);
216 const newSplit = split(this._split);
217 return newUpdate
218 .merge(newEnter)
219 .merge(newExit)
220 .merge(newMerge)
221 .merge(newSplit);
222 }
223 remove() {
224 // Remove node immediately if there is no transition,
225 // otherwise wait until transition finished.
226 for (let i = 0; i < this._elements.length; i++) {
227 const transition = this._transitions[i];
228 if (transition) {
229 const T = Array.isArray(transition) ? transition : [transition];
230 Promise.all(T.map((d) => d.finished)).then(() => {
231 const element = this._elements[i];
232 element.remove();
233 });
234 }
235 else {
236 const element = this._elements[i];
237 element.remove();
238 }
239 }
240 return new Selection([], null, this._parent, this._document, undefined, this._transitions);
241 }
242 each(callback) {
243 for (let i = 0; i < this._elements.length; i++) {
244 const element = this._elements[i];
245 const datum = element.__data__;
246 callback(datum, i, element);
247 }
248 return this;
249 }
250 attr(key, value) {
251 const callback = typeof value !== 'function' ? () => value : value;
252 return this.each(function (d, i, element) {
253 if (value !== undefined)
254 element[key] = callback(d, i, element);
255 });
256 }
257 style(key, value) {
258 const callback = typeof value !== 'function' ? () => value : value;
259 return this.each(function (d, i, element) {
260 if (value !== undefined)
261 element.style[key] = callback(d, i, element);
262 });
263 }
264 transition(value) {
265 const callback = typeof value !== 'function' ? () => value : value;
266 const { _transitions: T } = this;
267 return this.each(function (d, i, element) {
268 T[i] = callback(d, i, element);
269 });
270 }
271 on(event, handler) {
272 this.each(function (d, i, element) {
273 element.addEventListener(event, handler);
274 });
275 return this;
276 }
277 call(callback, ...args) {
278 callback(this, ...args);
279 return this;
280 }
281 node() {
282 return this._elements[0];
283 }
284 nodes() {
285 return this._elements;
286 }
287 transitions() {
288 return this._transitions;
289 }
290 parent() {
291 return this._parent;
292 }
293}
294exports.Selection = Selection;
295Selection.registry = {
296 g: g_1.Group,
297 rect: g_1.Rect,
298 circle: g_1.Circle,
299 path: g_1.Path,
300 text: g_1.Text,
301 ellipse: g_1.Ellipse,
302 image: g_1.Image,
303 line: g_1.Line,
304 polygon: g_1.Polygon,
305 polyline: g_1.Polyline,
306 html: g_1.HTML,
307};
308//# sourceMappingURL=selection.js.map
\No newline at end of file