UNPKG

@antv/g2

Version:

the Grammar of Graphics in Javascript

217 lines (199 loc) 6.18 kB
import { arc } from 'd3-shape'; import { Vector2, ShapeComponent as SC } from '../../runtime'; import { isPolar, isHelix, isTranspose } from '../../utils/coordinate'; import { select } from '../../utils/selection'; import { sub } from '../../utils/vector'; import { clamp } from '../../utils/number'; import { applyStyle, getArcObject, reorder, toOpacityKey } from '../utils'; export type ColorOptions = { colorAttribute: 'fill' | 'stroke'; /** * Minimum width of each interval. */ minWidth?: number; /** * Maximum width of each interval. */ maxWidth?: number; /** * Minimum height of each interval. */ minHeight?: number; [key: string]: any; }; // Render rect in different coordinate. export function rect( document, points, value, coordinate, style: Record<string, any> = {}, ) { const { inset = 0, radius = 0, insetLeft = inset, insetTop = inset, insetRight = inset, insetBottom = inset, radiusBottomLeft = radius, radiusBottomRight = radius, radiusTopLeft = radius, radiusTopRight = radius, minWidth = -Infinity, maxWidth = Infinity, minHeight = -Infinity, ...rest } = style; if (!isPolar(coordinate) && !isHelix(coordinate)) { const tpShape = !!isTranspose(coordinate); const [p0, , p2] = tpShape ? reorder(points) : points; const [x, y] = p0; const [width, height] = sub(p2, p0); // Deal with width or height is negative. const absX = width > 0 ? x : x + width; const absY = height > 0 ? y : y + height; const absWidth = Math.abs(width); const absHeight = Math.abs(height); const finalX = absX + insetLeft; const finalY = absY + insetTop; const finalWidth = absWidth - (insetLeft + insetRight); const finalHeight = absHeight - (insetTop + insetBottom); const clampWidth = tpShape ? clamp(finalWidth, minHeight, Infinity) : clamp(finalWidth, minWidth, maxWidth); const clampHeight = tpShape ? clamp(finalHeight, minWidth, maxWidth) : clamp(finalHeight, minHeight, Infinity); const clampX = tpShape ? finalX : finalX - (clampWidth - finalWidth) / 2; const clampY = tpShape ? finalY - (clampHeight - finalHeight) / 2 : finalY - (clampHeight - finalHeight); return select(document.createElement('rect', {})) .style('x', clampX) .style('y', clampY) .style('width', clampWidth) .style('height', clampHeight) .style('radius', [ radiusTopLeft, radiusTopRight, radiusBottomRight, radiusBottomLeft, ]) .call(applyStyle, rest) .node(); } // Render path in polar coordinate. const { y, y1 } = value; const center = coordinate.getCenter() as Vector2; const arcObject = getArcObject(coordinate, points, [y, y1]); const path = arc() .cornerRadius(radius as number) .padAngle((inset * Math.PI) / 180); return select(document.createElement('path', {})) .style('d', path(arcObject)) .style('transform', `translate(${center[0]}, ${center[1]})`) .style('radius', radius) .style('inset', inset) .call(applyStyle, rest) .node(); } /** * Render rect in different coordinate. * Calc arc path based on control points directly rather startAngle, endAngle, innerRadius, * outerRadius. This is not accurate and will cause bug when the range of y scale is [1, 0] * for cell geometry. */ export const Color: SC<ColorOptions> = (options, context) => { // Render border only when colorAttribute is stroke. const { colorAttribute, opacityAttribute = 'fill', first = true, last = true, ...style } = options; const { coordinate, document } = context; return (points, value, defaults) => { const { color: defaultColor, radius: defaultRadius = 0, ...restDefaults } = defaults; const defaultLineWidth = restDefaults.lineWidth || 1; const { stroke, radius = defaultRadius, radiusTopLeft = radius, radiusTopRight = radius, radiusBottomRight = radius, radiusBottomLeft = radius, innerRadius = 0, innerRadiusTopLeft = innerRadius, innerRadiusTopRight = innerRadius, innerRadiusBottomRight = innerRadius, innerRadiusBottomLeft = innerRadius, lineWidth = colorAttribute === 'stroke' || stroke ? defaultLineWidth : 0, inset = 0, insetLeft = inset, insetRight = inset, insetBottom = inset, insetTop = inset, minWidth, maxWidth, minHeight, ...rest } = style; const { color = defaultColor, opacity } = value; // Extended style, which is not supported by native g shape, // should apply at first. const standardDirRadius = [ first ? radiusTopLeft : innerRadiusTopLeft, first ? radiusTopRight : innerRadiusTopRight, last ? radiusBottomRight : innerRadiusBottomRight, last ? radiusBottomLeft : innerRadiusBottomLeft, ]; const standardDir = [ 'radiusTopLeft', 'radiusTopRight', 'radiusBottomRight', 'radiusBottomLeft', ]; // Transpose: rotate it clockwise by 90. if (isTranspose(coordinate)) { standardDir.push(standardDir.shift()); } const extendedStyle = { radius, ...Object.fromEntries( standardDir.map((d, i) => [d, standardDirRadius[i]]), ), inset, insetLeft, insetRight, insetBottom, insetTop, minWidth, maxWidth, minHeight, }; return ( select(rect(document, points, value, coordinate, extendedStyle)) .call(applyStyle, restDefaults) .style('fill', 'transparent') .style(colorAttribute, color) .style(toOpacityKey(options), opacity) .style('lineWidth', lineWidth) .style('stroke', stroke === undefined ? color : stroke) // shape.style has higher priority. .call(applyStyle, rest) .node() ); }; }; // @todo Should Shape have default animations using for ordinal scale? Color.props = { defaultEnterAnimation: 'scaleInY', defaultUpdateAnimation: 'morphing', defaultExitAnimation: 'fadeOut', };