| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- import { Group, Rect, Circle, Path, Text, Ellipse, Image, Line, Polygon, Polyline, HTML, } from '@antv/g';
- import { group } from 'd3-array';
- import { error } from './helper';
- export function select(node) {
- return new Selection([node], null, node, node.ownerDocument);
- }
- /**
- * 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.
- */
- export 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) {
- 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;
- 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 = 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 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.call(element, datum, i);
- }
- return this;
- }
- attr(key, value) {
- const callback = typeof value !== 'function' ? () => value : value;
- return this.each(function (d, i) {
- if (value !== undefined)
- this[key] = callback.call(this, d, i);
- });
- }
- style(key, value) {
- const callback = typeof value !== 'function' ? () => value : value;
- return this.each(function (d, i) {
- if (value !== undefined)
- this.style[key] = callback.call(this, d, i);
- });
- }
- transition(value) {
- const callback = typeof value !== 'function' ? () => value : value;
- const { _transitions: T } = this;
- return this.each(function (d, i) {
- T[i] = callback.call(this, d, i);
- });
- }
- on(event, handler) {
- this.each(function () {
- this.addEventListener(event, handler);
- });
- return this;
- }
- call(callback, ...args) {
- callback.call(this._parent, this, ...args);
- return this;
- }
- node() {
- return this._elements[0];
- }
- nodes() {
- return this._elements;
- }
- transitions() {
- return this._transitions;
- }
- parent() {
- return this._parent;
- }
- }
- Selection.registry = {
- g: Group,
- rect: Rect,
- circle: Circle,
- path: Path,
- text: Text,
- ellipse: Ellipse,
- image: Image,
- line: Line,
- polygon: Polygon,
- polyline: Polyline,
- html: HTML,
- };
- //# sourceMappingURL=selection.js.map
|