@antv/g2
Version:
the Grammar of Graphics in Javascript
408 lines • 18.8 kB
JavaScript
"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.ArcAxis = exports.LinearAxis = exports.rotateAxis = void 0;
const component_1 = require("@antv/component");
const scale_1 = require("@antv/scale");
const util_1 = require("@antv/util");
const d3_array_1 = require("d3-array");
const d3_format_1 = require("d3-format");
const coordinate_1 = require("../utils/coordinate");
const number_1 = require("../utils/number");
const helper_1 = require("../utils/helper");
const utils_1 = require("./utils");
function rotateAxis(axis, options) {
const { eulerAngles, origin } = options;
if (origin) {
axis.setOrigin(origin);
}
if (eulerAngles) {
axis.rotate(eulerAngles[0], eulerAngles[1], eulerAngles[2]);
}
}
exports.rotateAxis = rotateAxis;
function sizeOf(coordinate) {
// @ts-ignore
const { innerWidth, innerHeight, depth } = coordinate.getOptions();
return [innerWidth, innerHeight, depth];
}
function createFisheye(position, coordinate) {
const { width, height } = coordinate.getOptions();
return (tick) => {
if (!(0, coordinate_1.isFisheye)(coordinate))
return tick;
const tickPoint = position === 'bottom' ? [tick, 1] : [0, tick];
const vector = coordinate.map(tickPoint);
if (position === 'bottom') {
const v = vector[0];
const x = new scale_1.Linear({
domain: [0, width],
range: [0, 1],
});
return x.map(v);
}
else if (position === 'left') {
const v = vector[1];
const x = new scale_1.Linear({
domain: [0, height],
range: [0, 1],
});
return x.map(v);
}
return tick;
};
}
function ticksOf(scale, domain, tickMethod) {
if (scale.getTicks)
return scale.getTicks();
if (!tickMethod)
return domain;
const [min, max] = (0, d3_array_1.extent)(domain, (d) => +d);
const { tickCount } = scale.getOptions();
return tickMethod(min, max, tickCount);
}
// Set inset for axis.
function createInset(position, coordinate) {
if ((0, coordinate_1.isPolar)(coordinate))
return (d) => d;
const options = coordinate.getOptions();
const { innerWidth, innerHeight, insetTop, insetBottom, insetLeft, insetRight, } = options;
const [start, end, size] = position === 'left' || position === 'right'
? [insetTop, insetBottom, innerHeight]
: [insetLeft, insetRight, innerWidth];
const x = new scale_1.Linear({
domain: [0, 1],
range: [start / size, 1 - end / size],
});
return (i) => x.map(i);
}
/**
* Calc ticks based on scale and coordinate.
*/
function getData(scale, domain, tickCount, defaultTickFormatter, tickFilter, tickMethod, position, coordinate) {
var _a;
if (tickCount !== undefined || tickMethod !== undefined) {
scale.update(Object.assign(Object.assign({}, (tickCount && { tickCount })), (tickMethod && { tickMethod })));
}
const ticks = ticksOf(scale, domain, tickMethod);
const filteredTicks = tickFilter ? ticks.filter(tickFilter) : ticks;
const toString = (d) => d instanceof Date
? String(d)
: typeof d === 'object' && !!d
? d
: String(d);
const labelFormatter = defaultTickFormatter || ((_a = scale.getFormatter) === null || _a === void 0 ? void 0 : _a.call(scale)) || toString;
const applyInset = createInset(position, coordinate);
const applyFisheye = createFisheye(position, coordinate);
const isHorizontal = (position) => ['top', 'bottom', 'center', 'outer'].includes(position);
const isVertical = (position) => ['left', 'right'].includes(position);
// @todo GUI should consider the overlap problem for the first
// and label of arc axis.
if ((0, coordinate_1.isPolar)(coordinate) || (0, coordinate_1.isTranspose)(coordinate)) {
return filteredTicks.map((d, i, array) => {
var _a, _b;
const offset = ((_a = scale.getBandWidth) === null || _a === void 0 ? void 0 : _a.call(scale, d)) / 2 || 0;
const tick = applyInset(scale.map(d) + offset);
const shouldReverse = ((0, coordinate_1.isRadial)(coordinate) && position === 'center') ||
((0, coordinate_1.isTranspose)(coordinate) &&
((_b = scale.getTicks) === null || _b === void 0 ? void 0 : _b.call(scale)) &&
isHorizontal(position)) ||
((0, coordinate_1.isTranspose)(coordinate) && isVertical(position));
return {
value: shouldReverse ? 1 - tick : tick,
label: toString(labelFormatter((0, number_1.prettyNumber)(d), i, array)),
id: String(i),
};
});
}
return filteredTicks.map((d, i, array) => {
var _a;
const offset = ((_a = scale.getBandWidth) === null || _a === void 0 ? void 0 : _a.call(scale, d)) / 2 || 0;
const tick = applyFisheye(applyInset(scale.map(d) + offset));
const shouldReverse = isVertical(position);
return {
value: shouldReverse ? 1 - tick : tick,
label: toString(labelFormatter((0, number_1.prettyNumber)(d), i, array)),
id: String(i),
};
});
}
function inferGridLength(position, coordinate, plane = 'xy') {
const [width, height, depth] = sizeOf(coordinate);
if (plane === 'xy') {
if (position.includes('bottom') || position.includes('top'))
return height;
return width;
}
else if (plane === 'xz') {
if (position.includes('bottom') || position.includes('top'))
return depth;
return width;
}
else {
if (position.includes('bottom') || position.includes('top'))
return height;
return depth;
}
}
function inferLabelOverlap(transform = [], style) {
if (transform.length > 0)
return transform;
const { labelAutoRotate, labelAutoHide, labelAutoEllipsis, labelAutoWrap } = style;
const finalTransforms = [];
const addToTransforms = (overlap, state) => {
if (state) {
finalTransforms.push(Object.assign(Object.assign({}, overlap), state));
}
};
addToTransforms({
type: 'rotate',
optionalAngles: [0, 15, 30, 45, 60, 90],
}, labelAutoRotate);
addToTransforms({ type: 'ellipsis', minLength: 20 }, labelAutoEllipsis);
addToTransforms({ type: 'hide' }, labelAutoHide);
addToTransforms({ type: 'wrap', wordWrapWidth: 100, maxLines: 3, recoveryWhenFail: true }, labelAutoWrap);
return finalTransforms;
}
function inferArcStyle(position, bbox, innerRadius, outerRadius, coordinate) {
const { x, y, width, height } = bbox;
const center = [x + width / 2, y + height / 2];
const radius = Math.min(width, height) / 2;
const [startAngle, endAngle] = (0, coordinate_1.angleOf)(coordinate);
const [w, h] = sizeOf(coordinate);
const r = Math.min(w, h) / 2;
const common = {
center,
radius,
startAngle,
endAngle,
gridLength: (outerRadius - innerRadius) * r,
};
if (position === 'inner') {
// @ts-ignore
const { insetLeft, insetTop } = coordinate.getOptions();
return Object.assign(Object.assign({}, common), { center: [center[0] - insetLeft, center[1] - insetTop], labelAlign: 'perpendicular', labelDirection: 'positive', tickDirection: 'positive', gridDirection: 'negative' });
}
// arc outer
return Object.assign(Object.assign({}, common), { labelAlign: 'parallel', labelDirection: 'negative', tickDirection: 'negative', gridDirection: 'positive' });
}
function inferGrid(value, coordinate, scale) {
if ((0, coordinate_1.isTheta)(coordinate) || (0, coordinate_1.isParallel)(coordinate))
return false;
// Display axis grid for non-discrete values.
return value === undefined ? !!scale.getTicks : value;
}
function infer3DAxisLinearOverrideStyle(coordinate) {
// @ts-ignore
const { depth } = coordinate.getOptions();
return depth
? {
tickIsBillboard: true,
lineIsBillboard: true,
labelIsBillboard: true,
titleIsBillboard: true,
gridIsBillboard: true,
}
: {};
}
function inferAxisLinearOverrideStyle(position, orientation, bbox, coordinate, xScale) {
const { x, y, width, height } = bbox;
if (position === 'bottom') {
return { startPos: [x, y], endPos: [x + width, y] };
}
if (position === 'left') {
return { startPos: [x + width, y + height], endPos: [x + width, y] };
}
if (position === 'right') {
return { startPos: [x, y + height], endPos: [x, y] };
}
if (position === 'top') {
return { startPos: [x, y + height], endPos: [x + width, y + height] };
}
// linear axis, maybe in parallel, polar, radial or radar systems.
if (position === 'center') {
// axisY
if (orientation === 'vertical') {
return {
startPos: [x, y],
endPos: [x, y + height],
};
}
// axisX
else if (orientation === 'horizontal') {
return {
startPos: [x, y],
endPos: [x + width, y],
};
}
// axis with rotate
else if (typeof orientation === 'number') {
const [cx, cy] = coordinate.getCenter();
const [innerRadius, outerRadius] = (0, coordinate_1.radiusOf)(coordinate);
const [startAngle, endAngle] = (0, coordinate_1.angleOf)(coordinate);
const r = Math.min(width, height) / 2;
// @ts-ignore
const { insetLeft, insetTop } = coordinate.getOptions();
const innerR = innerRadius * r;
const outerR = outerRadius * r;
const [actualCx, actualCy] = [cx + x - insetLeft, cy + y - insetTop];
const [cos, sin] = [Math.cos(orientation), Math.sin(orientation)];
const startPos = [
actualCx + outerR * cos,
actualCy + outerR * sin,
];
const endPos = [
actualCx + innerR * cos,
actualCy + innerR * sin,
];
const getAxisXDomainLength = () => {
const { domain } = xScale.getOptions();
return domain.length;
};
const controllAngleCount = (0, coordinate_1.isPolar)(coordinate) && xScale ? getAxisXDomainLength() : 3;
return {
startPos,
endPos,
gridClosed: Math.abs(endAngle - startAngle - 360) < 1e-6,
gridCenter: [actualCx, actualCy],
gridControlAngles: new Array(controllAngleCount)
.fill(0)
.map((d, i, arr) => ((endAngle - startAngle) / controllAngleCount) * i),
};
}
}
// position is inner or outer for arc axis won't be here
return {};
}
const ArcAxisComponent = (options) => {
const { order, size, position, orientation, labelFormatter, tickFilter, tickCount, tickMethod, important = {}, style = {}, indexBBox, title, grid = false } = options, rest = __rest(options, ["order", "size", "position", "orientation", "labelFormatter", "tickFilter", "tickCount", "tickMethod", "important", "style", "indexBBox", "title", "grid"]);
return ({ scales: [scale], value, coordinate, theme }) => {
const { bbox } = value;
const { domain } = scale.getOptions();
const data = getData(scale, domain, tickCount, labelFormatter, tickFilter, tickMethod, position, coordinate);
// Bind computed bbox if exists.
const labels = indexBBox
? data.map((d, i) => {
const bbox = indexBBox.get(i);
if (!bbox)
return d;
// bbox: [label, bbox]
// Make than indexBBox can match current label.
if (bbox[0] !== d.label)
return d;
return Object.assign(Object.assign({}, d), { bbox: bbox[1] });
})
: data;
const [innerRadius, outerRadius] = (0, coordinate_1.radiusOf)(coordinate);
const defaultStyle = inferArcStyle(position, bbox, innerRadius, outerRadius, coordinate);
const { axis: axisTheme, axisArc = {} } = theme;
const finalStyle = (0, utils_1.adaptor)((0, util_1.deepMix)({}, axisTheme, axisArc, defaultStyle, Object.assign(Object.assign({ type: 'arc', data: labels, titleText: (0, utils_1.titleContent)(title), grid }, rest), important)));
return new component_1.Axis({
// @fixme transform is not valid for arcAxis.
// @ts-ignore
style: (0, util_1.omit)(finalStyle, ['transform']),
});
};
};
function inferThemeStyle(scale, coordinate, theme, direction, position, orientation) {
const baseStyle = theme.axis;
const positionStyle = ['top', 'right', 'bottom', 'left'].includes(position)
? theme[`axis${(0, helper_1.capitalizeFirst)(position)}`]
: theme.axisLinear;
const channel = scale.getOptions().name;
const channelStyle = theme[`axis${(0, util_1.upperFirst)(channel)}`] || {};
return Object.assign({}, baseStyle, positionStyle, channelStyle);
}
function inferDefaultStyle(scale, coordinate, theme, direction, position, orientation) {
const themeStyle = inferThemeStyle(scale, coordinate, theme, direction, position, orientation);
if (position === 'center') {
return Object.assign(Object.assign(Object.assign(Object.assign({}, themeStyle), { labelDirection: direction === 'right' ? 'negative' : 'positive' }), (direction === 'center'
? { labelTransform: 'translate(50%,0)' }
: null)), { tickDirection: direction === 'right' ? 'negative' : 'positive', labelSpacing: direction === 'center' ? 0 : 4, titleSpacing: (0, utils_1.isVertical)(orientation) ? 10 : 0, tick: direction === 'center' ? false : undefined });
}
return themeStyle;
}
const LinearAxisComponent = (options) => {
const { direction = 'left', important = {}, labelFormatter, order, orientation, actualPosition, position, size, style = {}, title, tickCount, tickFilter, tickMethod, transform, indexBBox } = options, userDefinitions = __rest(options, ["direction", "important", "labelFormatter", "order", "orientation", "actualPosition", "position", "size", "style", "title", "tickCount", "tickFilter", "tickMethod", "transform", "indexBBox"]);
return ({ scales, value, coordinate, theme }) => {
const { bbox } = value;
const [scale] = scales;
const { domain, xScale } = scale.getOptions();
const defaultStyle = inferDefaultStyle(scale, coordinate, theme, direction, position, orientation);
const internalAxisStyle = Object.assign(Object.assign(Object.assign({}, defaultStyle), style), userDefinitions);
const gridLength = inferGridLength(actualPosition || position, coordinate, options.plane);
const overrideStyle = inferAxisLinearOverrideStyle(position, orientation, bbox, coordinate, xScale);
const threeDOverrideStyle = infer3DAxisLinearOverrideStyle(coordinate);
const data = getData(scale, domain, tickCount, labelFormatter, tickFilter, tickMethod, position, coordinate);
// Bind computed bbox if exists.
const labels = indexBBox
? data.map((d, i) => {
const bbox = indexBBox.get(i);
if (!bbox)
return d;
// bbox: [label, bbox]
// Make than indexBBox can match current label.
if (bbox[0] !== d.label)
return d;
return Object.assign(Object.assign({}, d), { bbox: bbox[1] });
})
: data;
const finalAxisStyle = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, internalAxisStyle), { type: 'linear', data: labels, crossSize: size, titleText: (0, utils_1.titleContent)(title), labelOverlap: inferLabelOverlap(transform, internalAxisStyle), grid: inferGrid(internalAxisStyle.grid, coordinate, scale), gridLength,
// Always showLine, make title could align the end of axis.
line: true, indexBBox }), (!internalAxisStyle.line ? { lineOpacity: 0 } : null)), overrideStyle), threeDOverrideStyle), important);
// For hide overlap, do not set crossSize.
const hasHide = finalAxisStyle.labelOverlap.find((d) => d.type === 'hide');
if (hasHide)
finalAxisStyle.crossSize = false;
return new component_1.Axis({
className: 'axis',
style: (0, utils_1.adaptor)(finalAxisStyle),
});
};
};
const axisFactor = (axis) => {
return (options) => {
const { labelFormatter: useDefinedLabelFormatter, labelFilter: userDefinedLabelFilter = () => true, } = options;
return (context) => {
var _a;
const { scales: [scale], } = context;
const ticks = ((_a = scale.getTicks) === null || _a === void 0 ? void 0 : _a.call(scale)) || scale.getOptions().domain;
const labelFormatter = typeof useDefinedLabelFormatter === 'string'
? (0, d3_format_1.format)(useDefinedLabelFormatter)
: useDefinedLabelFormatter;
const labelFilter = (datum, index, array) => userDefinedLabelFilter(ticks[index], index, ticks);
const normalizedOptions = Object.assign(Object.assign({}, options), { labelFormatter,
labelFilter,
scale });
return axis(normalizedOptions)(context);
};
};
};
exports.LinearAxis = axisFactor(LinearAxisComponent);
exports.ArcAxis = axisFactor(ArcAxisComponent);
exports.LinearAxis.props = {
defaultPosition: 'center',
defaultSize: 45,
defaultOrder: 0,
defaultCrossPadding: [12, 12],
defaultPadding: [12, 12],
};
exports.ArcAxis.props = {
defaultPosition: 'outer',
defaultOrientation: 'vertical',
defaultSize: 45,
defaultOrder: 0,
defaultCrossPadding: [12, 12],
defaultPadding: [12, 12],
};
//# sourceMappingURL=axis.js.map