UNPKG

victory-native

Version:

A charting library for React Native with a focus on performance and customization.

276 lines (275 loc) 12.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.transformInputData = void 0; const getOffsetFromAngle_1 = require("../../utils/getOffsetFromAngle"); const tickHelpers_1 = require("../../utils/tickHelpers"); const getAxisLabelLayout_1 = require("./getAxisLabelLayout"); const getAxisTitleLayout_1 = require("./getAxisTitleLayout"); const getXScaleInputBounds_1 = require("./getXScaleInputBounds"); const getXAxisTicks_1 = require("./getXAxisTicks"); const getYScaleInputBounds_1 = require("./getYScaleInputBounds"); const getYScaleDomain_1 = require("./getYScaleDomain"); const makeScale_1 = require("./makeScale"); const getYOutputValue = (value, yScale, yAxisScale) => { if (typeof value !== "number") return value; if (yAxisScale === "log" && value <= 0) return null; const output = yScale(value); return Number.isFinite(output) ? output : null; }; /** * This is a fatty. Takes raw user input data, and transforms it into a format * that's easier for us to consume. End result looks something like: * { * ix: [1, 2, 3], // input x values * ox: [10, 20, 30], // canvas x values * y: { * high: { i: [3, 4, 5], o: [30, 40, 50] }, * low: { ... } * } * } * This form allows us to easily e.g. do a binary search to find closest output x index * and then map that into each of the other value lists. */ const transformInputData = ({ data: _data, xKey, yKeys, outputWindow, domain, domainPadding, xAxis, yAxes, viewport, labelRotate, axisScales, }) => { var _a; const data = [..._data]; const { xAxisScale = "linear", yAxisScale = "linear" } = axisScales || {}; // Determine if xKey data is numerical const isNumericalData = data.every((datum) => typeof datum[xKey] === "number"); // and sort if it is if (isNumericalData) { data.sort((a, b) => +a[xKey] - +b[xKey]); } // // Set up our y-output data structure const y = yKeys.reduce((acc, k) => { acc[k] = { i: [], o: [] }; return acc; }, {}); const rawChartWidth = outputWindow.xMax - outputWindow.xMin; const xTickValues = xAxis === null || xAxis === void 0 ? void 0 : xAxis.tickValues; const xTicks = xAxis === null || xAxis === void 0 ? void 0 : xAxis.tickCount; const tickDomainsX = (0, tickHelpers_1.getDomainFromTicks)(xTickValues); const ix = data.map((datum) => datum[xKey]); const ixNum = ix.map((val, i) => (isNumericalData ? val : i)); const xInputBounds = (0, getXScaleInputBounds_1.getXScaleInputBounds)({ isNumericalData, ixNum, domain: domain === null || domain === void 0 ? void 0 : domain.x, tickDomain: tickDomainsX, }); const xTempScale = (0, makeScale_1.makeScale)({ inputBounds: xInputBounds, outputBounds: [0, rawChartWidth], axisScale: xAxisScale, }); const xTicksForLabelLayout = (0, getXAxisTicks_1.getXAxisTicks)({ isNumericalData, ix, tickCount: xTicks, tickValues: xTickValues, xScale: xTempScale, }); const xAxisLabelLayouts = xTicksForLabelLayout.map((xTick, index) => { const labelInput = (isNumericalData ? xTick : ix[xTick]); const labelValue = xAxis.formatXLabel ? xAxis.formatXLabel(labelInput) : String(labelInput !== null && labelInput !== void 0 ? labelInput : xTick); return (0, getAxisLabelLayout_1.getAxisLabelLayout)({ axis: "x", orientation: "vertical", value: labelInput, text: String(labelValue), index, font: xAxis.font, labelRenderer: xAxis.labelRenderer, }); }); const maxXLabelLayout = (0, getAxisLabelLayout_1.getMaxAxisLabelLayout)(xAxisLabelLayouts); const xAxisTitleLayout = (0, getAxisTitleLayout_1.getAxisTitleLayout)({ title: xAxis.title, font: xAxis.font, }); const xAxisTitleOutset = xAxisTitleLayout.hasContent ? xAxisTitleLayout.height + xAxisTitleLayout.offset : 0; // workt with adjustedoutputwindow isntead of directly // working with outpuwidnow const adjustedOutputWindow = Object.assign({}, outputWindow); if (labelRotate && xAxis.labelPosition === "outset") { const rotateOffset = Math.abs(maxXLabelLayout.width * (0, getOffsetFromAngle_1.getOffsetFromAngle)(labelRotate)); if (xAxis.axisSide === "bottom") { adjustedOutputWindow.yMax -= rotateOffset; } else if (xAxis.axisSide === "top") { adjustedOutputWindow.yMin += rotateOffset; } } // 1. Set up our y axes first... // Transform data for each y-axis configuration const yAxesTransformed = (_a = (yAxes !== null && yAxes !== void 0 ? yAxes : [{}])) === null || _a === void 0 ? void 0 : _a.map((yAxis) => { var _a; const yTickValues = yAxis.tickValues; const yTicks = yAxis.tickCount; const tickDomainsY = yAxis.domain ? yAxis.domain : (0, tickHelpers_1.getDomainFromTicks)(yAxis.tickValues); const yKeysForAxis = (_a = yAxis.yKeys) !== null && _a !== void 0 ? _a : yKeys; const { yMin, yMax } = (0, getYScaleInputBounds_1.getYScaleInputBounds)({ data, yKeys: yKeysForAxis, domain: domain === null || domain === void 0 ? void 0 : domain.y, tickDomain: tickDomainsY, }); const yScaleDomain = (0, getYScaleDomain_1.getYScaleDomain)({ yMin, yMax, yAxisScale }); const yScaleRange = (() => { var _a, _b; const xTickCount = (_a = xAxis === null || xAxis === void 0 ? void 0 : xAxis.tickCount) !== null && _a !== void 0 ? _a : 0; const xLabelOffset = (_b = xAxis === null || xAxis === void 0 ? void 0 : xAxis.labelOffset) !== null && _b !== void 0 ? _b : 0; const xAxisSide = xAxis === null || xAxis === void 0 ? void 0 : xAxis.axisSide; const xLabelPosition = xAxis === null || xAxis === void 0 ? void 0 : xAxis.labelPosition; const xLabelOutset = xTickCount > 0 && maxXLabelLayout.width > 0 ? maxXLabelLayout.height + xLabelOffset * 2 : 0; const xAxisOutset = xAxisTitleOutset + (xLabelPosition === "outset" ? xLabelOutset : 0); if (xAxisSide === "bottom") { return [ adjustedOutputWindow.yMin, adjustedOutputWindow.yMax - xAxisOutset, ]; } if (xAxisSide === "top") { return [ adjustedOutputWindow.yMin + xAxisOutset, adjustedOutputWindow.yMax, ]; } return [adjustedOutputWindow.yMin, adjustedOutputWindow.yMax]; })(); const yScale = (0, makeScale_1.makeScale)({ inputBounds: yScaleDomain, outputBounds: yScaleRange, // Reverse viewport y values since canvas coordinates increase downward viewport: (viewport === null || viewport === void 0 ? void 0 : viewport.y) ? [viewport.y[1], viewport.y[0]] : undefined, isNice: true, padEnd: typeof domainPadding === "number" ? domainPadding : domainPadding === null || domainPadding === void 0 ? void 0 : domainPadding.bottom, padStart: typeof domainPadding === "number" ? domainPadding : domainPadding === null || domainPadding === void 0 ? void 0 : domainPadding.top, axisScale: yAxisScale, }); const yData = yKeysForAxis.reduce((acc, key) => { acc[key] = { i: data.map((datum) => datum[key]), o: data.map((datum) => getYOutputValue(datum[key], yScale, yAxisScale)), }; return acc; }, {}); const yTicksNormalized = yTickValues ? (0, tickHelpers_1.downsampleTicks)(yTickValues, yTicks) : yScale.ticks(yTicks); yKeys.forEach((yKey) => { if (yKeysForAxis.includes(yKey)) { y[yKey].i = data.map((datum) => datum[yKey]); y[yKey].o = data.map((datum) => getYOutputValue(datum[yKey], yScale, yAxisScale)); } }); const yAxisLabelLayouts = yTicksNormalized.map((yTick, index) => { var _a, _b; const labelInput = yTick; const label = (_b = (_a = yAxis === null || yAxis === void 0 ? void 0 : yAxis.formatYLabel) === null || _a === void 0 ? void 0 : _a.call(yAxis, labelInput)) !== null && _b !== void 0 ? _b : String(yTick); return (0, getAxisLabelLayout_1.getAxisLabelLayout)({ axis: "y", orientation: "vertical", value: labelInput, text: String(label), index, font: yAxis.font, labelRenderer: yAxis.labelRenderer, }); }); const maxYLabel = (0, getAxisLabelLayout_1.getMaxAxisLabelLayout)(yAxisLabelLayouts).width; const yAxisTitleLayout = (0, getAxisTitleLayout_1.getAxisTitleLayout)({ title: yAxis.title, font: yAxis.font, }); const yAxisTitleOutset = yAxisTitleLayout.hasContent ? yAxisTitleLayout.height + yAxisTitleLayout.offset : 0; return { yScale, yTicksNormalized, yData, maxYLabel, yAxisTitleOutset, }; }); // 2. Then set up our x axis... // Determine the x-output range based on yAxes/label options const oRange = (() => { let xMinAdjustment = 0; let xMaxAdjustment = 0; yAxes === null || yAxes === void 0 ? void 0 : yAxes.forEach((axis, index) => { var _a, _b, _c, _d; const yTickCount = axis.tickCount; const yLabelPosition = axis.labelPosition; const yAxisSide = axis.axisSide; const yLabelOffset = axis.labelOffset; // Calculate label width for this axis const labelWidth = (_b = (_a = yAxesTransformed[index]) === null || _a === void 0 ? void 0 : _a.maxYLabel) !== null && _b !== void 0 ? _b : 0; const yAxisTitleOutset = (_d = (_c = yAxesTransformed[index]) === null || _c === void 0 ? void 0 : _c.yAxisTitleOutset) !== null && _d !== void 0 ? _d : 0; // Adjust xMin or xMax based on the axis side and label position // make ajdustments for label rotation here if (yAxisSide === "left") { xMinAdjustment += yAxisTitleOutset; if (yLabelPosition === "outset") { xMinAdjustment += yTickCount > 0 && labelWidth > 0 ? labelWidth + yLabelOffset : 0; } } else if (yAxisSide === "right") { xMaxAdjustment -= yAxisTitleOutset; if (yLabelPosition === "outset") { xMaxAdjustment += yTickCount > 0 && labelWidth > 0 ? -labelWidth - yLabelOffset : 0; } } }); // Return the adjusted output range return [ adjustedOutputWindow.xMin + xMinAdjustment, adjustedOutputWindow.xMax + xMaxAdjustment, ]; })(); const xScale = (0, makeScale_1.makeScale)({ // if single data point, manually add upper & lower bounds so chart renders properly inputBounds: xInputBounds, outputBounds: oRange, viewport: viewport === null || viewport === void 0 ? void 0 : viewport.x, padStart: typeof domainPadding === "number" ? domainPadding : domainPadding === null || domainPadding === void 0 ? void 0 : domainPadding.left, padEnd: typeof domainPadding === "number" ? domainPadding : domainPadding === null || domainPadding === void 0 ? void 0 : domainPadding.right, axisScale: xAxisScale, }); // Normalize xTicks values either via the d3 scaleLinear ticks() function or our custom downSample function // For consistency we do it here, so we have both y and x ticks to pass to the axis generator const finalXTicksNormalized = (0, getXAxisTicks_1.getXAxisTicks)({ isNumericalData, ix, tickCount: xTicks, tickValues: xTickValues, xScale, }); const ox = ixNum.map((x) => xScale(x)); return { ix, y, isNumericalData, ox, xScale, xTicksNormalized: finalXTicksNormalized, // conform to type NonEmptyArray<T> yAxes: [yAxesTransformed[0], ...yAxesTransformed.slice(1)], }; }; exports.transformInputData = transformInputData;