UNPKG

@antv/g2

Version:

the Grammar of Graphics in Javascript

213 lines 10.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DrillDown = DrillDown; const util_1 = require("@antv/util"); const selection_1 = require("../utils/selection"); const runtime_1 = require("../runtime"); const partition_1 = require("../mark/partition"); // Get partition element. const getElementsPartition = (plot) => { return plot.querySelectorAll('.element').filter((item) => { const itemStyle = item.style || {}; return itemStyle[partition_1.PARTITION_TYPE_FIELD] === partition_1.PARTITION_TYPE; }); }; function selectPlotArea(root) { return (0, selection_1.select)(root).select(`.${runtime_1.PLOT_CLASS_NAME}`).node(); } // Default breadCrumb config. const DEFAULT_BREADCRUMB = { rootText: 'root', style: { fill: 'rgba(0, 0, 0, 0.6)', fontSize: 11, }, y: 4, active: { fill: 'rgba(0, 0, 0, 0.4)', }, }; /** * DrillDown interaction for partition visualization. * Based on the proven sunburst drilldown implementation. */ function DrillDown(drillDownOptions = {}) { const { breadCrumb: textConfig = {} } = drillDownOptions; const breadCrumb = (0, util_1.deepMix)({}, DEFAULT_BREADCRUMB, textConfig); return (context) => { const { update, setState, container, view, options: viewOptions } = context; const document = container.ownerDocument; const plotArea = selectPlotArea(container); const partitionMark = viewOptions.marks.find(({ id }) => id === partition_1.PARTITION_TYPE); if (!partitionMark) return; const { state } = partitionMark; // Create breadCrumbTextsGroup to save textSeparator and drillTexts. const textGroup = document.createElement('g'); textGroup.style.transform = `translate(${breadCrumb.x || 0}px, ${breadCrumb.y || 0}px)`; textGroup.setAttribute('x', 'drilldown-breadcrumb'); plotArea.appendChild(textGroup); // Modify data and scale according to the path and level of current click to achieve drilling down, drilling up and initialization effects. const drillDownClick = (path) => __awaiter(this, void 0, void 0, function* () { // Clear text. textGroup.removeChildren(); // More path creation text. if (path && path.length > 0) { // Create root text. const rootText = document.createElement('text', { style: Object.assign({ text: breadCrumb.rootText }, breadCrumb.style), }); textGroup.appendChild(rootText); let name = ''; const pathArray = path; let y = breadCrumb.style.y; let x = rootText.getBBox().width; const maxWidth = plotArea.getBBox().width; // Create path: 'type1 / type2 / type3' -> '/ type1 / type2 / type3'. const drillTexts = pathArray.map((text, index) => { const textSeparator = document.createElement('text', { style: Object.assign(Object.assign({ x, text: ' / ' }, breadCrumb.style), { y }), }); textGroup.appendChild(textSeparator); x += textSeparator.getBBox().width; name = `${name}${text} / `; const drillText = document.createElement('text', { name: name.replace(/\s\/\s$/, ''), style: Object.assign(Object.assign({ text, x }, breadCrumb.style), { y }), }); textGroup.appendChild(drillText); x += drillText.getBBox().width; /** * Page width exceeds maximum, line feed. * | ----maxWidth---- | * | / type1 / type2 / type3 | * -> * | ----maxWidth---- | * | / type1 / type2 | * | / type3 | */ if (x > maxWidth) { y = textGroup.getBBox().height; x = 0; textSeparator.attr({ x, y, }); x += textSeparator.getBBox().width; drillText.attr({ x, y, }); x += drillText.getBBox().width; } return drillText; }); // Add active state and drilldown interaction. const textStack = [rootText, ...drillTexts]; textStack.forEach((item, index) => { // Skip the last drillText if (index === drillTexts.length) return; const originalAttrs = Object.assign({}, item.attributes); item.attr('cursor', 'pointer'); item.addEventListener('mouseenter', () => { item.attr(breadCrumb.active); }); item.addEventListener('mouseleave', () => { item.attr(originalAttrs); }); item.addEventListener('click', () => { drillDownClick(path.slice(0, index)); }); }); } // Update marks. setState('drillDown', (viewOptions) => { const { marks } = viewOptions; // Add filter transform for every mark, // which will skip for mark without color channel. const newMarks = marks.map((mark) => { if (mark.id !== partition_1.PARTITION_TYPE && mark.type !== 'rect') return mark; // Inset after aggregate transform, such as group and bin. const { data } = mark; const newData = data.filter((item) => { var _a; const itemPath = (_a = item.path) !== null && _a !== void 0 ? _a : []; if (path.length === 0) return true; if (!Array.isArray(itemPath) || itemPath.length < path.length) return false; for (let i = 0; i < path.length; i++) { if (itemPath[i] !== path[i]) return false; } return true; }); // DrillDown by filtering the data and scale. return (0, util_1.deepMix)({}, mark, { data: newData, }); }); return Object.assign(Object.assign({}, viewOptions), { marks: newMarks }); }); yield update(); }); const createDrillClick = (e) => { var _a, _b; const item = e.target; // Get properties directly from DOM element attributes instead of using get function. const markType = item.markType; const itemStyle = item.style || {}; const partitionType = itemStyle[partition_1.PARTITION_TYPE_FIELD]; const childNodeCount = itemStyle[partition_1.CHILD_NODE_COUNT]; const itemData = item.__data__; if (markType !== 'rect' || partitionType !== partition_1.PARTITION_TYPE || !childNodeCount) { return; } const path = (_b = (_a = itemData === null || itemData === void 0 ? void 0 : itemData.data) === null || _a === void 0 ? void 0 : _a.path) !== null && _b !== void 0 ? _b : []; drillDownClick(path); }; // Add click drill interaction. plotArea.addEventListener('click', createDrillClick); // Change attribute keys. const changeStyleKey = (0, util_1.keys)(Object.assign(Object.assign({}, state.active), state.inactive)); const createActive = () => { const elements = getElementsPartition(plotArea); elements.forEach((element) => { const childNodeCount = (0, util_1.get)(element, ['style', partition_1.CHILD_NODE_COUNT]); const cursor = (0, util_1.get)(element, ['style', 'cursor']); if (cursor !== 'pointer' && childNodeCount) { element.style.cursor = 'pointer'; const originalAttrs = (0, util_1.pick)(element.attributes, changeStyleKey); element.addEventListener('mouseenter', () => { element.attr(state.active); }); element.addEventListener('mouseleave', () => { element.attr((0, util_1.deepMix)(originalAttrs, state.inactive)); }); } }); }; // Animate elements update and add active state. plotArea.addEventListener('mousemove', createActive); return () => { textGroup.remove(); plotArea.removeEventListener('click', createDrillClick); plotArea.removeEventListener('mousemove', createActive); }; }; } //# sourceMappingURL=drillDown.js.map