UNPKG

victory-chart

Version:
393 lines (356 loc) 14 kB
import { includes, defaults, defaultsDeep, isFunction, range, without } from "lodash"; import { Helpers, Scale, Domain } from "victory-core"; const orientationSign = { top: -1, left: -1, right: 1, bottom: 1 }; export default { // exposed for use by VictoryChart getDomain(props, axis) { const inherentAxis = this.getAxis(props); if (axis && axis !== inherentAxis) { return undefined; } let domain; if (Array.isArray(props.domain)) { domain = props.domain; } else if (props.domain && props.domain[inherentAxis]) { domain = props.domain[inherentAxis]; } else if (Array.isArray(props.tickValues) && props.tickValues.length > 1) { domain = Domain.getDomainFromTickValues(props); } const paddedDomain = Domain.padDomain(domain, props, inherentAxis); return domain ? Domain.cleanDomain(paddedDomain, props, inherentAxis) : undefined; }, // exposed for use by VictoryChart getAxis(props, flipped) { if (props.orientation) { const vertical = {top: "x", bottom: "x", left: "y", right: "y"}; return vertical[props.orientation]; } const axisType = props.dependentAxis ? "dependent" : "independent"; const flippedAxis = { dependent: "x", independent: "y"}; const normalAxis = { independent: "x", dependent: "y"}; return flipped ? flippedAxis[axisType] : normalAxis[axisType]; }, // exposed for use by VictoryChart getScale(props) { const axis = this.getAxis(props); const scale = Scale.getBaseScale(props, axis); const domain = this.getDomain(props) || scale.domain(); scale.range(Helpers.getRange(props, axis)); scale.domain(domain); return scale; }, getStyleObject(props) { const { theme, dependentAxis } = props; const generalAxisStyle = theme && theme.axis && theme.axis.style; const axisType = dependentAxis ? "dependentAxis" : "independentAxis"; const specificAxisStyle = theme && theme[axisType] && theme[axisType].style; return generalAxisStyle && specificAxisStyle ? defaultsDeep({}, specificAxisStyle, generalAxisStyle ) : specificAxisStyle || generalAxisStyle; }, getStyles(props, styleObject) { const style = props.style || {}; styleObject = styleObject || {}; const parentStyleProps = { height: "auto", width: "100%" }; return { parent: defaults(parentStyleProps, style.parent, styleObject.parent), axis: defaults({}, style.axis, styleObject.axis), axisLabel: defaults({}, style.axisLabel, styleObject.axisLabel), grid: defaults({}, style.grid, styleObject.grid), ticks: defaults({}, style.ticks, styleObject.ticks), tickLabels: defaults({}, style.tickLabels, styleObject.tickLabels) }; }, getTickProps(layout, style, datum) { const { position, transform } = layout; return { x1: transform.x, y1: transform.y, x2: transform.x + position.x2, y2: transform.y + position.y2, style, datum }; }, getTickLabelProps(layout, style, anchors, datum, text) { // eslint-disable-line max-params const { position, transform } = layout; return { style, x: transform.x + position.x, y: transform.y + position.y, verticalAnchor: anchors.verticalAnchor, textAnchor: anchors.textAnchor, angle: style.angle, text, datum }; }, getGridProps(layout, style, datum) { const {edge, transform} = layout; return { x1: transform.x, y1: transform.y, x2: edge.x + transform.x, y2: edge.y + transform.y, style, datum }; }, getAxisProps(modifiedProps, calculatedValues, globalTransform) { const { style, padding, isVertical } = calculatedValues; const { width, height } = modifiedProps; return { style: style.axis, x1: isVertical ? globalTransform.x : padding.left + globalTransform.x, x2: isVertical ? globalTransform.x : width - padding.right + globalTransform.x, y1: isVertical ? padding.top + globalTransform.y : globalTransform.y, y2: isVertical ? height - padding.bottom + globalTransform.y : globalTransform.y }; }, getLayoutProps(modifiedProps, calculatedValues) { const offset = this.getOffset(modifiedProps, calculatedValues); return { globalTransform: this.getTransform(modifiedProps, calculatedValues, offset), gridOffset: this.getGridOffset(modifiedProps, calculatedValues, offset), gridEdge: this.getGridEdge(modifiedProps, calculatedValues) }; }, getEvaluatedStyles(style, tick, index) { return { tickStyle: Helpers.evaluateStyle(style.ticks, tick, index), labelStyle: Helpers.evaluateStyle(style.tickLabels, tick, index), gridStyle: Helpers.evaluateStyle(style.grid, tick, index) }; }, getRole(props) { if (props.dependentAxis) { return props.theme && props.theme.dependentAxis ? "dependentAxis" : "axis"; } return props.theme && props.theme.independentAxis ? "independentAxis" : "axis"; }, getShallowMergedThemeProps(props, role) { const axisTheme = props.theme.axis || {}; return defaults({}, props.theme[role], axisTheme); }, modifyProps(props, fallbackProps, role) { if (role !== "axis") { props.theme[role] = this.getShallowMergedThemeProps(props, role); } return Helpers.modifyProps(props, fallbackProps, role); }, getBaseProps(props, fallbackProps) { const role = this.getRole(props); props = this.modifyProps(props, fallbackProps, role); const calculatedValues = this.getCalculatedValues(props); const { style, orientation, isVertical, scale, ticks, tickFormat, stringTicks, anchors } = calculatedValues; const { globalTransform, gridOffset, gridEdge } = this.getLayoutProps(props, calculatedValues); const axisProps = this.getAxisProps(props, calculatedValues, globalTransform); const axisLabelProps = this.getAxisLabelProps(props, calculatedValues, globalTransform); const childProps = { parent: { style: style.parent, ticks, scale, width: props.width, height: props.height }}; for (let index = 0, len = ticks.length; index < len; index++) { const tick = stringTicks ? props.tickValues[(ticks[index]) - 1] : ticks[index]; const styles = this.getEvaluatedStyles(style, tick, index); const tickLayout = { position: this.getTickPosition(styles, orientation, isVertical), transform: this.getTickTransform(scale(ticks[index]), globalTransform, isVertical) }; const gridLayout = { edge: gridEdge, transform: { x: isVertical ? -gridOffset.x + globalTransform.x : scale(ticks[index]) + globalTransform.x, y: isVertical ? scale(ticks[index]) + globalTransform.y : gridOffset.y + globalTransform.y } }; childProps[index] = { axis: axisProps, axisLabel: axisLabelProps, ticks: this.getTickProps(tickLayout, styles.tickStyle, tick), tickLabels: this.getTickLabelProps( tickLayout, styles.labelStyle, anchors, tick, tickFormat(tick, index) ), grid: this.getGridProps(gridLayout, styles.gridStyle, tick) }; } return childProps; }, getCalculatedValues(props) { const defaultStyles = this.getStyleObject(props); const style = this.getStyles(props, defaultStyles); const padding = Helpers.getPadding(props); const orientation = props.orientation || (props.dependentAxis ? "left" : "bottom"); const isVertical = Helpers.isVertical(props); const labelPadding = this.getLabelPadding(props, style); const stringTicks = Helpers.stringTicks(props); const scale = this.getScale(props); const ticks = this.getTicks(props, scale); const tickFormat = this.getTickFormat(props, scale, ticks); const anchors = this.getAnchors(orientation, isVertical); return { style, padding, orientation, isVertical, labelPadding, stringTicks, anchors, scale, ticks, tickFormat }; }, getAxisLabelProps(props, calculatedValues, globalTransform) { const {style, orientation, padding, labelPadding, isVertical} = calculatedValues; const sign = orientationSign[orientation]; const hPadding = padding.left + padding.right; const vPadding = padding.top + padding.bottom; const verticalAnchor = sign < 0 ? "end" : "start"; const labelStyle = style.axisLabel; const angle = isVertical ? -90 : 0; const x = isVertical ? globalTransform.x + (sign * labelPadding) : ((props.width - hPadding) / 2) + padding.left + globalTransform.x; const y = isVertical ? ((props.height - vPadding) / 2) + padding.bottom + globalTransform.y : (sign * labelPadding) + globalTransform.y; return { x, y, verticalAnchor: labelStyle.verticalAnchor || verticalAnchor, textAnchor: labelStyle.textAnchor || "middle", angle: labelStyle.angle || angle, style: labelStyle, text: props.label }; }, getTicks(props, scale) { if (props.tickValues) { if (Helpers.stringTicks(props)) { return range(1, props.tickValues.length + 1); } return props.tickValues.length ? props.tickValues : scale.domain(); } else if (scale.ticks && isFunction(scale.ticks)) { const scaleTicks = scale.ticks(props.tickCount); const ticks = Array.isArray(scaleTicks) && scaleTicks.length ? scaleTicks : scale.domain(); if (props.crossAxis) { const filteredTicks = includes(ticks, 0) ? without(ticks, 0) : ticks; return filteredTicks.length ? filteredTicks : ticks; } return ticks; } return scale.domain(); }, getAnchors(orientation, isVertical) { const anchorOrientation = { top: "end", left: "end", right: "start", bottom: "start" }; const anchor = anchorOrientation[orientation]; return { textAnchor: isVertical ? anchor : "middle", verticalAnchor: isVertical ? "middle" : anchor }; }, getTickFormat(props, scale) { if (props.tickFormat && isFunction(props.tickFormat)) { return props.tickFormat; } else if (props.tickFormat && Array.isArray(props.tickFormat)) { return (x, index) => props.tickFormat[index]; } else if (Helpers.stringTicks(props)) { return (x, index) => props.tickValues[index]; } else if (scale.tickFormat && isFunction(scale.tickFormat)) { return scale.tickFormat(); } else { return (x) => x; } }, getLabelPadding(props, style) { const labelStyle = style.axisLabel || {}; if (typeof labelStyle.padding !== "undefined" && labelStyle.padding !== null) { return labelStyle.padding; } const isVertical = Helpers.isVertical(props); // TODO: magic numbers const fontSize = labelStyle.fontSize || 14; return props.label ? (fontSize * (isVertical ? 2.3 : 1.6)) : 0; }, getOffset(props, calculatedValues) { const { style, padding, isVertical, orientation, labelPadding, stringTicks, ticks } = calculatedValues; const xPadding = orientation === "right" ? padding.right : padding.left; const yPadding = orientation === "top" ? padding.top : padding.bottom; const fontSize = style.axisLabel.fontSize || 14; const offsetX = (props.offsetX !== null) && (props.offsetX !== undefined) ? props.offsetX : xPadding; const offsetY = (props.offsetY !== null) && (props.offsetY !== undefined) ? props.offsetY : yPadding; const tickSizes = ticks.map((data) => { const tick = stringTicks ? props.tickValues[data - 1] : data; const tickStyle = Helpers.evaluateStyle(style.ticks, tick); return tickStyle.size || 0; }); const totalPadding = fontSize + (2 * Math.max(...tickSizes)) + labelPadding; const minimumPadding = 1.2 * fontSize; // TODO: magic numbers const x = isVertical ? totalPadding : minimumPadding; const y = isVertical ? minimumPadding : totalPadding; return { x: (offsetX !== null) && (offsetX !== undefined) ? offsetX : x, y: (offsetY !== null) && (offsetY !== undefined) ? offsetY : y }; }, getTransform(props, calculatedValues, offset) { const {orientation} = calculatedValues; return { top: {x: 0, y: offset.y}, bottom: {x: 0, y: props.height - offset.y}, left: {x: offset.x, y: 0}, right: {x: props.width - offset.x, y: 0} }[orientation]; }, getTickPosition(style, orientation, isVertical) { const {tickStyle, labelStyle} = style; const size = tickStyle.size || 0; const tickPadding = tickStyle.padding || 0; const labelPadding = labelStyle.padding || 0; const tickSpacing = size + tickPadding + labelPadding; const sign = orientationSign[orientation]; return { x: isVertical ? sign * tickSpacing : 0, x2: isVertical ? sign * size : 0, y: isVertical ? 0 : sign * tickSpacing, y2: isVertical ? 0 : sign * size }; }, getTickTransform(tick, globalTransform, isVertical) { return { x: isVertical ? globalTransform.x : tick + globalTransform.x, y: isVertical ? tick + globalTransform.y : globalTransform.y }; }, getGridEdge(props, calculatedValues) { const {orientation, padding, isVertical} = calculatedValues; const sign = -orientationSign[orientation]; const x = isVertical ? sign * (props.width - (padding.left + padding.right)) : 0; const y = isVertical ? 0 : sign * (props.height - (padding.top + padding.bottom)); return {x, y}; }, getGridOffset(props, calculatedValues, offset) { const {padding, orientation } = calculatedValues; const xPadding = orientation === "right" ? padding.right : padding.left; const yPadding = orientation === "top" ? padding.top : padding.bottom; return { x: props.crossAxis ? offset.x - xPadding : 0, y: props.crossAxis ? offset.y - yPadding : 0 }; } };