UNPKG

@antv/g2

Version:

the Grammar of Graphics in Javascript

784 lines 33.8 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.styleOf = exports.computeTitleBBox = exports.computeLabelsBBox = exports.createScale = exports.computeComponentSize = exports.groupComponents = exports.flatComponents = exports.normalizeComponents = exports.renderComponent = exports.inferComponent = void 0; const util_1 = require("@antv/util"); const d3_array_1 = require("d3-array"); const d3_format_1 = require("d3-format"); const g_1 = require("@antv/g"); const coordinate_1 = require("../coordinate"); const array_1 = require("../utils/array"); const number_1 = require("../utils/number"); const helper_1 = require("../utils/helper"); const constant_1 = require("../component/constant"); const coordinate_2 = require("./coordinate"); const library_1 = require("./library"); const scale_1 = require("./scale"); const scale_2 = require("./types/scale"); function inferComponent(scales, partialOptions, library) { const { coordinates = [], title } = partialOptions; const [, createGuideComponent] = (0, library_1.useLibrary)('component', library); const displayedScales = scales.filter(({ guide }) => { if (guide === null) return false; return true; }); const components = []; // Sliders and scrollbar component. const sliders = inferScrollableComponents(partialOptions, scales, library); components.push(...sliders); // Title components. if (title) { const { props } = createGuideComponent('title'); const { defaultPosition, defaultOrientation, defaultOrder, defaultSize, defaultCrossPadding, } = props; const titleOptions = typeof title === 'string' ? { title } : title; components.push(Object.assign({ type: 'title', position: defaultPosition, orientation: defaultOrientation, order: defaultOrder, crossPadding: defaultCrossPadding[0], defaultSize }, titleOptions)); } // Axis and legends. const inferredComponents = inferComponentsType(displayedScales, coordinates); inferredComponents.forEach(([type, relativeScales]) => { const { props } = createGuideComponent(type); const { defaultPosition, defaultPlane = 'xy', defaultOrientation, defaultSize, defaultOrder, defaultLength, defaultPadding: DP = [0, 0], defaultCrossPadding: DCP = [0, 0], } = props; // @todo to be confirm if the scale can be merged. // const scale: G2ScaleOptions = Object.assign({}, ...relativeScales); const scale = (0, util_1.deepMix)({}, ...relativeScales); const { guide: guideOptions, field } = scale; // A scale may have multiple guides. const guides = Array.isArray(guideOptions) ? guideOptions : [guideOptions]; for (const partialGuide of guides) { const [position, orientation] = inferComponentPositionAndOrientation(type, defaultPosition, defaultOrientation, partialGuide, relativeScales, displayedScales, coordinates); // Skip if position and orientation are not specified. // @example the last axis of radar chart if (!position && !orientation) continue; const isVertical = position === 'left' || position === 'right'; const defaultPadding = isVertical ? DP[1] : DP[0]; const defaultCrossPadding = isVertical ? DCP[1] : DCP[0]; const { size, order = defaultOrder, length = defaultLength, padding = defaultPadding, crossPadding = defaultCrossPadding, } = partialGuide; components.push(Object.assign(Object.assign({ title: field }, partialGuide), { defaultSize, length, position, plane: defaultPlane, orientation, padding, order, crossPadding, size, type, scales: relativeScales })); } }); return components; } exports.inferComponent = inferComponent; function renderComponent(component, coordinate, theme, library, markState) { const [useGuideComponent] = (0, library_1.useLibrary)('component', library); const { scaleInstances: scales, scale, bbox } = component, options = __rest(component, ["scaleInstances", "scale", "bbox"]); const value = { bbox, library }; const render = useGuideComponent(options); return render({ coordinate, library, markState, scales, theme, value, scale, }); } exports.renderComponent = renderComponent; function normalizeComponents(components) { return components.map((d) => { const component = (0, util_1.deepMix)(d, d.style); delete component.style; return component; }); } exports.normalizeComponents = normalizeComponents; function flatComponents(components) { return components.flatMap((d) => (d.type == 'group' ? d.children : d)); } exports.flatComponents = flatComponents; // Wrap legends into a group component. function groupComponents(components, crossSize) { // Group components by key. const P = ['left', 'right', 'bottom', 'top']; const key = ({ type, position, group }) => { if (!P.includes(position)) return Symbol('independent'); if (group === undefined) { if (type.startsWith('legend')) return `legend-${position}`; return Symbol('independent'); } if (group === 'independent') return Symbol('independent'); return group; }; const grouped = (0, d3_array_1.groups)(components, key); // Update attributes of group components, // and maybe flatten group components without enough room. return grouped.flatMap(([, components]) => { if (components.length === 1) return components[0]; // If crossSize defined, group components only when has // enough room. if (crossSize !== undefined) { // Compute total length. const DL = components .filter((d) => d.length !== undefined) .map((d) => d.length); const totalLength = (0, d3_array_1.sum)(DL); // If there is no enough room for components, // do not group. if (totalLength > crossSize) { components.forEach((d) => (d.group = Symbol('independent'))); return components; } // Group legends and update legend length. const emptyLength = crossSize - totalLength; const emptyCount = components.length - DL.length; const length = emptyLength / emptyCount; components.forEach((d) => { if (d.length !== undefined) return; d.length = length; }); } // Create a group component. const size = (0, d3_array_1.max)(components, (d) => d.size); const order = (0, d3_array_1.max)(components, (d) => d.order); const crossPadding = (0, d3_array_1.max)(components, (d) => d.crossPadding); const position = components[0].position; return { type: 'group', size, order, position, children: components, crossPadding, }; }); } exports.groupComponents = groupComponents; function inferLegendComponentType(scales, coordinates) { // Filter accepts scales. const channels = ['shape', 'size', 'color', 'opacity']; const isConstantSize = (type, name) => type === 'constant' && name === 'size'; const accepts = scales.filter(({ type, name }) => typeof type === 'string' && channels.includes(name) && !isConstantSize(type, name)); // Group scales by fields. const constants = accepts.filter(({ type }) => type === 'constant'); const nonConstants = accepts.filter(({ type }) => type !== 'constant'); const groupKey = (d) => (d.field ? d.field : Symbol('independent')); const fieldScales = (0, d3_array_1.groups)(nonConstants, groupKey) .map(([key, scales]) => [key, [...scales, ...constants]]) .filter(([, scales]) => scales.some((scale) => scale.type !== 'constant')); const scalesByField = new Map(fieldScales); // Skip empty scales. if (scalesByField.size === 0) return []; // Infer components. const sort = (arr) => arr.sort(([a], [b]) => a.localeCompare(b)); const components = Array.from(scalesByField) .map(([, scs]) => { const combinations = (0, array_1.combine)(scs).sort((a, b) => b.length - a.length); const options = combinations.map((combination) => ({ combination, option: combination.map((scale) => [scale.name, getScaleType(scale)]), })); // For category legend. for (const { option, combination } of options) { // If every scale is constant, do not display legend. if (option.every((d) => d[1] === 'constant')) continue; if (option.every((d) => d[1] === 'discrete' || d[1] === 'constant')) { return ['legendCategory', combination]; } } // For reset legend. // @todo Remove this. for (const [componentType, accords] of constant_1.LEGEND_INFER_STRATEGIES) { for (const { option, combination } of options) { if (accords.some((accord) => (0, util_1.isEqual)(sort(accord), sort(option)))) { return [componentType, combination]; } } } return null; }) .filter(helper_1.defined); return components; } function getScaleType(scale) { const { type } = scale; if (typeof type !== 'string') return null; if (type in scale_2.ContinuousScale) return 'continuous'; if (type in scale_2.DiscreteScale) return 'discrete'; if (type in scale_2.DistributionScale) return 'distribution'; if (type in scale_2.ConstantScale) return 'constant'; return null; } function inferAxisComponentType(scales, coordinates) { return scales .map((scale) => { const { name } = scale; // todo wait for gui provide helix axis if ((0, coordinate_2.isHelix)(coordinates) || (0, coordinate_2.isTheta)(coordinates)) return null; if ((0, coordinate_2.isTranspose)(coordinates) && ((0, coordinate_2.isPolar)(coordinates) || (0, coordinate_2.isRadial)(coordinates))) return null; // infer axis if (name.startsWith('x')) { if ((0, coordinate_2.isPolar)(coordinates)) return ['axisArc', [scale]]; if ((0, coordinate_2.isRadial)(coordinates)) return ['axisLinear', [scale]]; return [(0, coordinate_2.isTranspose)(coordinates) ? 'axisY' : 'axisX', [scale]]; } if (name.startsWith('y')) { if ((0, coordinate_2.isPolar)(coordinates)) return ['axisLinear', [scale]]; if ((0, coordinate_2.isRadial)(coordinates)) return ['axisArc', [scale]]; return [(0, coordinate_2.isTranspose)(coordinates) ? 'axisX' : 'axisY', [scale]]; } // Only support linear axis for z. if (name.startsWith('z')) { return ['axisZ', [scale]]; } if (name.startsWith('position')) { if ((0, coordinate_2.isRadar)(coordinates)) return ['axisRadar', [scale]]; if (!(0, coordinate_2.isPolar)(coordinates)) return ['axisY', [scale]]; } return null; }) .filter(helper_1.defined); } function inferComponentsType(scales, coordinates) { const availableScales = scales.filter((scale) => (0, scale_1.isValidScale)(scale)); return [ ...inferLegendComponentType(availableScales, coordinates), ...inferAxisComponentType(availableScales, coordinates), ]; } function angleOf(coordinates) { const polar = (0, coordinate_2.coordOf)(coordinates, 'polar'); if (polar.length) { const lastPolar = polar[polar.length - 1]; const { startAngle, endAngle } = (0, coordinate_1.getPolarOptions)(lastPolar); return [startAngle, endAngle]; } const radial = (0, coordinate_2.coordOf)(coordinates, 'radial'); if (radial.length) { const lastRadial = radial[radial.length - 1]; const { startAngle, endAngle } = (0, coordinate_1.getRadialOptions)(lastRadial); return [startAngle, endAngle]; } return [-Math.PI / 2, (Math.PI / 2) * 3]; } /** * match index of position */ function matchPosition(name) { const match = /position(\d*)/g.exec(name); if (!match) return null; return +match[1]; } function inferAxisPositionAndOrientation(type, ordinalPosition, relativeScales, scales, coordinates) { // a axis only has one scale const { name } = relativeScales[0]; // todo, in current resolution, the radar chart is implement by parallel + polar coordinate. // implementation plan to be confirmed. // in current implementation, it must to add the first position encode to it's last. // so we won't render the last axis repeatably. if (type === 'axisRadar') { const positions = scales.filter((scale) => scale.name.startsWith('position')); const index = matchPosition(name); if (name === positions.slice(-1)[0].name || index === null) return [null, null]; // infer radar axis orientation const [startAngle, endAngle] = angleOf(coordinates); const angle = ((endAngle - startAngle) / (positions.length - 1)) * index + startAngle; return ['center', angle]; } if (type === 'axisY' && (0, coordinate_2.isParallel)(coordinates)) { return (0, coordinate_2.isTranspose)(coordinates) ? ['center', 'horizontal'] : ['center', 'vertical']; } // in non-cartesian coordinate systems, infer the arc axis angle if (type === 'axisLinear') { const [startAngle] = angleOf(coordinates); return ['center', startAngle]; } if (type === 'axisArc') { if (ordinalPosition[0] === 'inner') return ['inner', null]; return ['outer', null]; } if ((0, coordinate_2.isPolar)(coordinates)) return ['center', null]; if ((0, coordinate_2.isRadial)(coordinates)) return ['center', null]; if ((type === 'axisX' && (0, coordinate_2.isReflect)(coordinates)) || (type === 'axisX' && (0, coordinate_2.isReflectY)(coordinates))) { return ['top', null]; } // if (type === 'axisX') return ['bottom', null]; return ordinalPosition; } // @todo Infer position by coordinates. function inferComponentPositionAndOrientation(type, defaultPosition, defaultOrientation, guide, relativeScales, scales, coordinates) { const [startAngle] = angleOf(coordinates); const ordinalPositionAndOrientation = [ guide.position || defaultPosition, startAngle !== null && startAngle !== void 0 ? startAngle : defaultOrientation, ]; if (typeof type === 'string' && type.startsWith('axis')) { return inferAxisPositionAndOrientation(type, ordinalPositionAndOrientation, relativeScales, scales, coordinates); } if (typeof type === 'string' && type.startsWith('legend') && (0, coordinate_2.isPolar)(coordinates)) { if (guide.position === 'center') return ['center', 'vertical']; } // for general component, use default position return ordinalPositionAndOrientation; } function inferScrollableType(name, type, coordinates = []) { if (name === 'x') return (0, coordinate_2.isTranspose)(coordinates) ? `${type}Y` : `${type}X`; if (name === 'y') return (0, coordinate_2.isTranspose)(coordinates) ? `${type}X` : `${type}Y`; return null; } /** * Infer scrollable components, such as slider and scrollbar. */ function inferScrollableComponents(partialOptions, scales, library) { const [, createGuideComponent] = (0, library_1.useLibrary)('component', library); const { coordinates } = partialOptions; function normalized(type, channelName, scale, options) { const componentType = inferScrollableType(channelName, type, coordinates); if (!options || !componentType) return; const { props } = createGuideComponent(componentType); const { defaultPosition, defaultSize, defaultOrder, defaultCrossPadding: [crossPadding], } = props; return Object.assign(Object.assign({ position: defaultPosition, defaultSize, order: defaultOrder, type: componentType, crossPadding }, options), { scales: [scale] }); } return scales .filter((d) => d.slider || d.scrollbar) .flatMap((scale) => { const { slider, scrollbar, name: channelName } = scale; return [ normalized('slider', channelName, scale, slider), normalized('scrollbar', channelName, scale, scrollbar), ]; }) .filter((d) => !!d); } // !!! Note Mutate component.size and component. function computeComponentSize(component, crossSize, crossPadding, position, theme, library) { // Only compute and update size of components in padding area. const { type } = component; const paddingAreas = ['left', 'right', 'bottom', 'top']; if (!paddingAreas.includes(position)) return; if (typeof type !== 'string') return; const t = type; const createCompute = () => { if (t.startsWith('axis')) return computeAxisSize; if (t.startsWith('group')) return computeGroupSize; if (t.startsWith('legendContinuous')) return computeContinuousLegendSize; if (t === 'legendCategory') return computeCategoryLegendSize; if (t.startsWith('slider')) return computeSliderSize; if (t === 'title') return computeTitleSize; if (t.startsWith('scrollbar')) return computeScrollbarSize; return () => { }; }; return createCompute()(component, crossSize, crossPadding, position, theme, library); } exports.computeComponentSize = computeComponentSize; function computeGroupSize(component, crossSize, crossPadding, position, theme, library) { const { children } = component; const maxCrossPadding = (0, d3_array_1.max)(children, (d) => d.crossPadding); children.forEach((d) => (d.crossPadding = maxCrossPadding)); children.forEach((child) => computeComponentSize(child, crossSize, crossPadding, position, theme, library)); const maxSize = (0, d3_array_1.max)(children, (d) => d.size); component.size = maxSize; children.forEach((d) => (d.size = maxSize)); } function computeScrollbarSize(component, crossSize, crossPadding, position, theme, library) { const { trackSize = 6 } = (0, util_1.deepMix)({}, theme.scrollbar, component); component.size = trackSize; } function computeTitleSize(component, crossSize, crossPadding, position, theme, library) { const _a = (0, util_1.deepMix)({}, theme.title, component), { title, subtitle, spacing = 0 } = _a, style = __rest(_a, ["title", "subtitle", "spacing"]); if (title) { const titleStyle = (0, helper_1.subObject)(style, 'title'); const titleBBox = computeLabelSize(title, titleStyle); component.size = titleBBox.height; } if (subtitle) { const subtitleStyle = (0, helper_1.subObject)(style, 'subtitle'); const subtitleBBox = computeLabelSize(subtitle, subtitleStyle); component.size += spacing + subtitleBBox.height; } } function computeSliderSize(component, crossSize, crossPadding, position, theme, library) { const styleOf = () => { const { slider } = theme; return (0, util_1.deepMix)({}, slider, component); }; const { trackSize, handleIconSize } = styleOf(); const size = Math.max(trackSize, handleIconSize * 2.4); component.size = size; } function computeAxisSize(component, crossSize, crossPadding, position, theme, library) { var _a; // If padding is auto, use hide as the labelTransform by default // to avoid overlap between labels. component.transform = component.transform || [{ type: 'hide' }]; // Vertical or horizontal. const isVertical = position === 'left' || position === 'right'; // Get styles to be applied. const style = styleOf(component, position, theme); const { tickLength = 0, labelSpacing = 0, titleSpacing = 0, labelAutoRotate } = style, rest = __rest(style, ["tickLength", "labelSpacing", "titleSpacing", "labelAutoRotate"]); // Compute Labels. const scale = createScale(component, library); const labelBBoxes = computeLabelsBBox(rest, scale); const paddingTick = tickLength + labelSpacing; if (labelBBoxes && labelBBoxes.length) { const maxLabelWidth = (0, d3_array_1.max)(labelBBoxes, (d) => d.width); const maxLabelHeight = (0, d3_array_1.max)(labelBBoxes, (d) => d.height); if (isVertical) { component.size = maxLabelWidth + paddingTick; } else { const { tickFilter, labelTransform } = component; // If the labels can't be placed horizontally, and labelTransform is unset, // rotate 90 deg to display them. if (overflowX(scale, labelBBoxes, crossSize, crossPadding, tickFilter) && !labelTransform && labelAutoRotate !== false && labelAutoRotate !== null) { component.labelTransform = 'rotate(90)'; component.size = maxLabelWidth + paddingTick; } else { component.labelTransform = (_a = component.labelTransform) !== null && _a !== void 0 ? _a : 'rotate(0)'; component.size = maxLabelHeight + paddingTick; } } } else { component.size = tickLength; } // Compute title. const titleBBox = computeTitleBBox(rest); if (titleBBox) { if (isVertical) { component.size += titleSpacing + titleBBox.width; } else { component.size += titleSpacing + titleBBox.height; } } } function computeContinuousLegendSize(component, crossSize, crossPadding, position, theme, library) { // Get styles. const styleOf = () => { const { legendContinuous } = theme; return (0, util_1.deepMix)({}, legendContinuous, component); }; const _a = styleOf(), { labelSpacing = 0, titleSpacing = 0 } = _a, rest = __rest(_a, ["labelSpacing", "titleSpacing"]); // Vertical or horizontal. const isVertical = position === 'left' || position === 'right'; // Ribbon styles. const ribbonStyles = (0, helper_1.subObject)(rest, 'ribbon'); const { size: ribbonSize } = ribbonStyles; const handleIconStyles = (0, helper_1.subObject)(rest, 'handleIcon'); const { size: handleIconSize } = handleIconStyles; const mainSize = Math.max(ribbonSize, handleIconSize * 2.4); component.size = mainSize; // Compute labels. const scale = createScale(component, library); const labelBBoxes = computeLabelsBBox(rest, scale); if (labelBBoxes) { const key = isVertical ? 'width' : 'height'; const size = (0, d3_array_1.max)(labelBBoxes, (d) => d[key]); component.size += size + labelSpacing; } // Compute title. const titleBBox = computeTitleBBox(rest); if (titleBBox) { if (isVertical) { component.size = Math.max(component.size, titleBBox.width); } else { component.size += titleSpacing + titleBBox.height; } } } function computeCategoryLegendSize(component, crossSize0, crossPadding, position, theme, library) { const styleOf = () => { const { legendCategory } = theme; const { title } = component; const [defaultTitle, specifiedTitle] = Array.isArray(title) ? [title, undefined] : [undefined, title]; return (0, util_1.deepMix)({ title: defaultTitle }, legendCategory, Object.assign(Object.assign({}, component), { title: specifiedTitle })); }; const _a = styleOf(), { itemSpacing, itemMarkerSize, titleSpacing, rowPadding, colPadding, maxCols = Infinity, maxRows = Infinity } = _a, rest = __rest(_a, ["itemSpacing", "itemMarkerSize", "titleSpacing", "rowPadding", "colPadding", "maxCols", "maxRows"]); const { cols, length } = component; const getRows = (rows) => Math.min(rows, maxRows); const getCols = (cols) => Math.min(cols, maxCols); // Vertical or horizontal. const isVertical = position === 'left' || position === 'right'; const crossSize = length === undefined ? crossSize0 + (isVertical ? 0 : crossPadding[0] + crossPadding[1]) : length; // Compute title. const titleBBox = computeTitleBBox(rest); const scale = createScale(component, library); const labelBBoxes = computeLabelsBBox(rest, scale, 'itemLabel'); const height = Math.max(labelBBoxes[0].height, itemMarkerSize) + rowPadding; const widthOf = (w, padding = 0) => itemMarkerSize + w + itemSpacing[0] + padding; // Only support grid layout for vertical area. const computeVerticalSize = () => { let maxSize = -Infinity; let pos = 0; let cols = 1; let rows = 0; let maxRows = -Infinity; let maxPos = -Infinity; const titleHeight = titleBBox ? titleBBox.height : 0; const maxHeight = crossSize - titleHeight; for (const { width } of labelBBoxes) { const w = widthOf(width, colPadding); maxSize = Math.max(maxSize, w); if (pos + height > maxHeight) { cols++; maxRows = Math.max(maxRows, rows); maxPos = Math.max(maxPos, pos); rows = 1; pos = height; } else { pos += height; rows++; } } if (cols <= 1) { maxRows = rows; maxPos = pos; } component.size = maxSize * getCols(cols); component.length = maxPos + titleHeight; (0, util_1.deepMix)(component, { cols: getCols(cols), gridRow: maxRows }); }; // Horizontal grid layout. const computeHorizontalGrid = () => { const rows = Math.ceil(labelBBoxes.length / cols); const maxWidth = (0, d3_array_1.max)(labelBBoxes, (d) => widthOf(d.width)) * cols; component.size = height * getRows(rows) - rowPadding; component.length = Math.min(maxWidth, crossSize); }; // Horizontal flex layout. const computeHorizontalFlex = () => { let rows = 1; let pos = 0; let maxPos = -Infinity; for (const { width } of labelBBoxes) { const w = widthOf(width, colPadding); if (pos + w > crossSize) { maxPos = Math.max(maxPos, pos); pos = w; rows++; } else { pos += w; } } if (rows === 1) maxPos = pos; component.size = height * getRows(rows) - rowPadding; component.length = maxPos; }; if (isVertical) computeVerticalSize(); else if (typeof cols === 'number') computeHorizontalGrid(); else computeHorizontalFlex(); // Compute titles. if (titleBBox) { if (isVertical) { component.size = Math.max(component.size, titleBBox.width); } else { component.size += titleSpacing + titleBBox.height; } } } function createScale(component, library) { const [useScale] = (0, library_1.useLibrary)('scale', library); // Init scale, the tickCount of axis has higher priority than scale. const { scales, tickCount, tickMethod } = component; const scaleOptions = scales.find((d) => d.type !== 'constant' && d.type !== 'identity'); if (tickCount !== undefined) scaleOptions.tickCount = tickCount; if (tickMethod !== undefined) scaleOptions.tickMethod = tickMethod; return useScale(scaleOptions); } exports.createScale = createScale; function computeLabelsBBox(component, scale, key = 'label') { const { labelFormatter, tickFilter, label = true } = component, style = __rest(component, ["labelFormatter", "tickFilter", "label"]); if (!label) return null; // Get labels to be rendered. const labels = labelsOf(scale, labelFormatter, tickFilter); const labelStyle = (0, helper_1.subObject)(style, key); const labelStyles = labels.map((d, i) => Object.fromEntries(Object.entries(labelStyle).map(([key, value]) => [ key, typeof value === 'function' ? value(d, i) : value, ]))); const labelBBoxes = labels.map((d, i) => { const normalizeStyle = labelStyles[i]; return computeLabelSize(d, normalizeStyle); }); // Cache boxes to avoid computed twice. // @todo GUI use untransformed bbox, so it can't cache if // label.style has transform attributes. const hasTransform = labelStyles.some((d) => d.transform); if (!hasTransform) { const I = labels.map((_, i) => i); component.indexBBox = new Map(I.map((i) => [i, [labels[i], labelBBoxes[i]]])); } return labelBBoxes; } exports.computeLabelsBBox = computeLabelsBBox; function computeTitleBBox(component) { const isFalsy = (x) => x === false || x === null; const { title } = component, style = __rest(component, ["title"]); if (isFalsy(title) || title === undefined) return null; const titleStyle = (0, helper_1.subObject)(style, 'title'); const { direction, transform } = titleStyle; const titleText = Array.isArray(title) ? title.join(',') : title; if (typeof titleText !== 'string') return null; const titleBBox = computeLabelSize(titleText, Object.assign(Object.assign({}, titleStyle), { transform: transform || (direction === 'vertical' ? 'rotate(-90)' : '') })); return titleBBox; } exports.computeTitleBBox = computeTitleBBox; function styleOf(axis, position, theme) { const { title } = axis; const [defaultTitle, specifiedTitle] = Array.isArray(title) ? [title, undefined] : [undefined, title]; const { axis: baseStyle, // @ts-ignore [`axis${(0, helper_1.capitalizeFirst)(position)}`]: positionStyle, } = theme; return (0, util_1.deepMix)({ title: defaultTitle }, baseStyle, positionStyle, Object.assign(Object.assign({}, axis), { title: specifiedTitle })); } exports.styleOf = styleOf; function ticksOf(scale, tickFilter) { const ticks = scale.getTicks ? scale.getTicks() : scale.getOptions().domain; if (!tickFilter) return ticks; return ticks.filter(tickFilter); } function labelsOf(scale, labelFormatter, tickFilter) { const T = ticksOf(scale, tickFilter); const ticks = T.map((d) => (typeof d === 'number' ? (0, number_1.prettyNumber)(d) : d)); const formatter = labelFormatter ? typeof labelFormatter === 'string' ? (0, d3_format_1.format)(labelFormatter) : labelFormatter : scale.getFormatter ? scale.getFormatter() : (d) => `${d}`; return ticks.map(formatter); } function offsetOf(scale, d) { if (!scale.getBandWidth) return 0; const offset = scale.getBandWidth(d) / 2; return offset; } function overflowX(scale, labelBBoxes, crossSize, crossPadding, tickFilter) { // If actual size bigger than container size, overflow. const totalSize = (0, d3_array_1.sum)(labelBBoxes, (d) => d.width); if (totalSize > crossSize) return true; // Clone scale to get visual position for labels. const scaleX = scale.clone(); scaleX.update({ range: [0, crossSize] }); const ticks = ticksOf(scale, tickFilter); const X = ticks.map((d) => scaleX.map(d) + offsetOf(scaleX, d)); const I = ticks.map((_, i) => i); const startX = -crossPadding[0]; const endX = crossSize + crossPadding[1]; const extent = (x, bbox) => { const { width } = bbox; return [x - width / 2, x + width / 2]; }; // Collision detection. for (let i = 0; i < I.length; i++) { const x = X[i]; const [x0, x1] = extent(x, labelBBoxes[i]); // If a label is out of plot area, overflow. if (x0 < startX || x1 > endX) return true; const y = X[i + 1]; if (y) { // If two labels intersect, overflow. const [y0] = extent(y, labelBBoxes[i + 1]); if (x1 > y0) return true; } } return false; } function computeLabelSize(d, style) { const shape = normalizeLabel(d); const { filter } = style, rest = __rest(style, ["filter"]); shape.attr(Object.assign(Object.assign({}, rest), { visibility: 'none' })); const bbox = shape.getBBox(); return bbox; } function normalizeLabel(d) { if (d instanceof g_1.DisplayObject) return d; return new g_1.Text({ style: { text: `${d}` } }); } //# sourceMappingURL=component.js.map