UNPKG

@antv/g2

Version:

the Grammar of Graphics in Javascript

308 lines 12.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Selection = exports.select = void 0; const g_1 = require("@antv/g"); const d3_array_1 = require("d3-array"); const helper_1 = require("./helper"); function select(node) { return new Selection([node], null, node, node.ownerDocument); } exports.select = select; /** * A simple implementation of d3-selection for @antv/g. * It has the core features of d3-selection and extended ability. * Every methods of selection returns new selection if elements * are mutated(e.g. append, remove), otherwise return the selection itself(e.g. attr, style). * @see https://github.com/d3/d3-selection * @see https://github.com/antvis/g * @todo Nested selections. * @todo More useful functor. */ class Selection { constructor(elements = null, data = null, parent = null, document = null, selections = [ null, null, null, null, null, ], transitions = [], updateElements = []) { this._elements = Array.from(elements); this._data = data; this._parent = parent; this._document = document; this._enter = selections[0]; this._update = selections[1]; this._exit = selections[2]; this._merge = selections[3]; this._split = selections[4]; this._transitions = transitions; this._facetElements = updateElements; } selectAll(selector) { const elements = typeof selector === 'string' ? this._parent.querySelectorAll(selector) : selector; return new Selection(elements, null, this._elements[0], this._document); } selectFacetAll(selector) { const elements = typeof selector === 'string' ? this._parent.querySelectorAll(selector) : selector; return new Selection(this._elements, null, this._parent, this._document, undefined, undefined, elements); } /** * @todo Replace with querySelector which has bug now. */ select(selector) { const element = typeof selector === 'string' ? this._parent.querySelectorAll(selector)[0] || null : selector; return new Selection([element], null, element, this._document); } append(node) { const callback = typeof node === 'function' ? node : () => this.createElement(node); const elements = []; if (this._data !== null) { // For empty selection, append new element to parent. // Each element is bind with datum. for (let i = 0; i < this._data.length; i++) { const d = this._data[i]; const [datum, from] = Array.isArray(d) ? d : [d, null]; const newElement = callback(datum, i); newElement.__data__ = datum; if (from !== null) newElement.__fromElements__ = from; this._parent.appendChild(newElement); elements.push(newElement); } return new Selection(elements, null, this._parent, this._document); } else { // For non-empty selection, append new element to // selected element and return new selection. for (let i = 0; i < this._elements.length; i++) { const element = this._elements[i]; const datum = element.__data__; const newElement = callback(datum, i); element.appendChild(newElement); elements.push(newElement); } return new Selection(elements, null, elements[0], this._document); } } maybeAppend(id, node, className) { const element = this._elements[0]; const child = element.getElementById(id); if (child) { return new Selection([child], null, this._parent, this._document); } const newChild = typeof node === 'string' ? this.createElement(node) : node(); newChild.id = id; if (className) newChild.className = className; element.appendChild(newChild); return new Selection([newChild], null, this._parent, this._document); } /** * Bind data to elements, and produce three selection: * Enter: Selection with empty elements and data to be bind to elements. * Update: Selection with elements to be updated. * Exit: Selection with elements to be removed. */ data(data, id = (d) => d, groupId = () => null) { // An Array of new data. const enter = []; // An Array of elements to be updated. const update = []; // A Set of elements to be removed. const exit = new Set(this._elements); // An Array of data to be merged into one element. const merge = []; // A Set of elements to be split into multiple datum. const split = new Set(); // A Map from key to each element. const keyElement = new Map(this._elements.map((d, i) => [id(d.__data__, i), d])); // A Map from key to exist element. The Update Selection // can get element from this map, this is for diff among // facets. const keyUpdateElement = new Map(this._facetElements.map((d, i) => [id(d.__data__, i), d])); // A Map from groupKey to a group of elements. const groupKeyElements = (0, d3_array_1.group)(this._elements, (d) => groupId(d.__data__)); // Diff data with selection(elements with data). // !!! Note // The switch is strictly ordered, not not change the order of them. for (let i = 0; i < data.length; i++) { const datum = data[i]; const key = id(datum, i); const groupKey = groupId(datum, i); // Append element to update selection if incoming data has // exactly the same key with elements. if (keyElement.has(key)) { const element = keyElement.get(key); element.__data__ = datum; element.__facet__ = false; update.push(element); exit.delete(element); keyElement.delete(key); // Append element to update selection if incoming data has // exactly the same key with updateElements. } else if (keyUpdateElement.has(key)) { const element = keyUpdateElement.get(key); element.__data__ = datum; // Flag this element should update its parentNode. element.__facet__ = true; update.push(element); keyUpdateElement.delete(key); // Append datum to merge selection if existed elements has // its key as groupKey. } else if (groupKeyElements.has(key)) { const group = groupKeyElements.get(key); merge.push([datum, group]); for (const element of group) exit.delete(element); groupKeyElements.delete(key); // Append element to split selection if incoming data has // groupKey as its key, and bind to datum for it. } else if (keyElement.has(groupKey)) { const element = keyElement.get(groupKey); if (element.__toData__) element.__toData__.push(datum); else element.__toData__ = [datum]; split.add(element); exit.delete(element); } else { // @todo Data with non-unique key. enter.push(datum); } } // Create new selection with enter, update and exit. const S = [ new Selection([], enter, this._parent, this._document), new Selection(update, null, this._parent, this._document), new Selection(exit, null, this._parent, this._document), new Selection([], merge, this._parent, this._document), new Selection(split, null, this._parent, this._document), ]; return new Selection(this._elements, null, this._parent, this._document, S); } merge(other) { const elements = [...this._elements, ...other._elements]; const transitions = [...this._transitions, ...other._transitions]; return new Selection(elements, null, this._parent, this._document, undefined, transitions); } createElement(type) { if (this._document) { return this._document.createElement(type, {}); } const Ctor = Selection.registry[type]; if (Ctor) return new Ctor(); return (0, helper_1.error)(`Unknown node type: ${type}`); } /** * Apply callback for each selection(enter, update, exit) * and merge them into one selection. */ join(enter = (d) => d, update = (d) => d, exit = (d) => d.remove(), merge = (d) => d, split = (d) => d.remove()) { const newEnter = enter(this._enter); const newUpdate = update(this._update); const newExit = exit(this._exit); const newMerge = merge(this._merge); const newSplit = split(this._split); return newUpdate .merge(newEnter) .merge(newExit) .merge(newMerge) .merge(newSplit); } remove() { // Remove node immediately if there is no transition, // otherwise wait until transition finished. for (let i = 0; i < this._elements.length; i++) { const transition = this._transitions[i]; if (transition) { const T = Array.isArray(transition) ? transition : [transition]; Promise.all(T.map((d) => d.finished)).then(() => { const element = this._elements[i]; element.remove(); }); } else { const element = this._elements[i]; element.remove(); } } return new Selection([], null, this._parent, this._document, undefined, this._transitions); } each(callback) { for (let i = 0; i < this._elements.length; i++) { const element = this._elements[i]; const datum = element.__data__; callback(datum, i, element); } return this; } attr(key, value) { const callback = typeof value !== 'function' ? () => value : value; return this.each(function (d, i, element) { if (value !== undefined) element[key] = callback(d, i, element); }); } style(key, value) { const callback = typeof value !== 'function' ? () => value : value; return this.each(function (d, i, element) { if (value !== undefined) element.style[key] = callback(d, i, element); }); } transition(value) { const callback = typeof value !== 'function' ? () => value : value; const { _transitions: T } = this; return this.each(function (d, i, element) { T[i] = callback(d, i, element); }); } on(event, handler) { this.each(function (d, i, element) { element.addEventListener(event, handler); }); return this; } call(callback, ...args) { callback(this, ...args); return this; } node() { return this._elements[0]; } nodes() { return this._elements; } transitions() { return this._transitions; } parent() { return this._parent; } } exports.Selection = Selection; Selection.registry = { g: g_1.Group, rect: g_1.Rect, circle: g_1.Circle, path: g_1.Path, text: g_1.Text, ellipse: g_1.Ellipse, image: g_1.Image, line: g_1.Line, polygon: g_1.Polygon, polyline: g_1.Polyline, html: g_1.HTML, }; //# sourceMappingURL=selection.js.map