UNPKG

reaviz

Version:

Data Visualization using React

1 lines 863 kB
{"version":3,"file":"index.umd.cjs","sources":["../src/common/utils/bigint.ts","../src/common/utils/dimensions.ts","../src/common/utils/domains.ts","../src/common/utils/formatting.ts","../src/common/utils/interpolation.ts","../src/common/utils/position.ts","../src/common/utils/selection.ts","../src/common/utils/stroke.ts","../src/common/utils/ticks.ts","../src/common/utils/math.ts","../src/common/utils/functions.ts","../src/common/utils/array.ts","../src/common/utils/size.ts","../src/common/utils/wrapText.tsx","../src/common/utils/useHoverIntent.ts","../src/common/utils/useResizeObserver.ts","../src/common/utils/merge.ts","../src/common/Axis/LinearAxis/LinearAxisLine.tsx","../src/common/Axis/LinearAxis/LinearAxisTickLine.tsx","../src/common/Axis/LinearAxis/LinearAxisTickLabel.tsx","../src/common/Axis/LinearAxis/LinearAxisTickSeries.tsx","../src/common/Axis/LinearAxis/LinearAxis.tsx","../src/common/Axis/LinearAxis/LinearXAxis.tsx","../src/common/Axis/LinearAxis/LinearYAxis.tsx","../src/common/Axis/LinearAxis/helpers.ts","../src/common/Axis/RadialAxis/RadialAxisTickSeries/RadialAxisTickLine.tsx","../src/common/Axis/RadialAxis/RadialAxisTickSeries/RadialAxisTickLabel.tsx","../src/common/Axis/RadialAxis/RadialAxisTickSeries/RadialAxisTick.tsx","../src/common/Axis/RadialAxis/RadialAxisTickSeries/RadialAxisTickSeries.tsx","../src/common/Axis/RadialAxis/RadialAxisArcSeries/RadialAxisArc.tsx","../src/common/Axis/RadialAxis/RadialAxisArcSeries/utils.ts","../src/common/Axis/RadialAxis/RadialAxisArcSeries/RadialAxisArcSeries.tsx","../src/common/Axis/RadialAxis/RadialAxisArcSeries/RadialAxisArcLine.tsx","../src/common/Axis/RadialAxis/RadialAxis.tsx","../src/common/Gestures/Move.tsx","../src/common/Brush/BrushHandle.tsx","../src/common/Brush/BrushSlice.tsx","../src/common/Brush/Brush.tsx","../src/common/Brush/ChartBrush.tsx","../src/common/containers/ChartContext.ts","../src/common/containers/ChartContainer.tsx","../src/common/data/bigInteger.ts","../src/common/data/builder.ts","../src/common/data/histogram.ts","../src/common/data/barStack.ts","../src/common/data/marimekko.ts","../src/common/data/areaStack.ts","../src/common/data/waterfall.ts","../src/common/Gestures/Pan.tsx","../src/common/Gestures/pinchUtils.ts","../src/common/Gestures/Zoom.tsx","../src/common/legends/DiscreteLegend/DiscreteLegend.tsx","../src/common/legends/DiscreteLegend/DiscreteLegendSymbol.tsx","../src/common/legends/DiscreteLegend/DiscreteLegendEntry.tsx","../src/common/legends/SequentialLegend/SequentialLegend.tsx","../src/common/Gridline/Gridline.tsx","../src/common/Gridline/GridStripe.tsx","../src/common/Gridline/GridlineSeries.tsx","../src/common/MarkLine/MarkLine.tsx","../src/common/Gradient/GradientStop.tsx","../src/common/Gradient/Gradient.tsx","../src/common/Gradient/RadialGradient.tsx","../src/common/Mask/Mask.tsx","../src/common/Mask/Stripes.tsx","../src/common/Tooltip/TooltipTemplate.tsx","../src/common/Tooltip/TooltipTheme.ts","../src/common/Tooltip/ChartTooltip.tsx","../src/common/Tooltip/TooltipArea.tsx","../src/common/ZoomPan/ZoomPan.tsx","../src/common/scales/basic.ts","../src/common/scales/marimekko.ts","../src/common/scales/multiSeries.ts","../src/common/scales/radial.ts","../src/common/ZoomPan/ChartZoomPan.tsx","../src/common/Motion/config.ts","../src/common/Motion/MotionPath.tsx","../src/common/color/schemes.ts","../src/common/color/helper.ts","../src/common/Count/useCount.tsx","../src/common/Count/Count.tsx","../src/common/ValueMarker/LinearValueMarker.tsx","../src/common/ValueMarker/RadialValueMarker.tsx","../src/common/Glow/utils.ts","../src/ScatterPlot/ScatterSeries/ScatterPoint.tsx","../src/ScatterPlot/ScatterSeries/ScatterSeries.tsx","../src/ScatterPlot/ScatterPlot.tsx","../src/AreaChart/AreaSeries/PointSeries.tsx","../src/AreaChart/AreaSeries/Area.tsx","../src/AreaChart/AreaSeries/Line.tsx","../src/AreaChart/AreaSeries/AreaSeries.tsx","../src/AreaChart/AreaSeries/StackedNormalizedAreaSeries.tsx","../src/AreaChart/AreaSeries/StackedAreaSeries.tsx","../src/AreaChart/AreaChart.tsx","../src/AreaChart/StackedAreaChart.tsx","../src/AreaChart/StackedNormalizedAreaChart.tsx","../src/BarChart/BarSeries/BarLabel.tsx","../src/BarChart/BarSeries/Bar.tsx","../src/BarChart/BarSeries/BarSeries.tsx","../src/BarChart/BarSeries/RangeLines.tsx","../src/BarChart/BarSeries/StackedBarSeries.tsx","../src/BarChart/BarSeries/StackedNormalizedBarSeries.tsx","../src/BarChart/BarSeries/MarimekkoBarSeries.tsx","../src/BarChart/BarSeries/HistogramBarSeries.tsx","../src/BarChart/BarSeries/GuideBar.tsx","../src/BarChart/BarChart.tsx","../src/BarChart/MarimekkoChart.tsx","../src/BarChart/StackedBarChart.tsx","../src/BarChart/StackedNormalizedBarChart.tsx","../src/BarChart/HistogramBarChart.tsx","../src/LineChart/LineSeries.tsx","../src/LineChart/LineChart.tsx","../src/Map/Map.tsx","../src/Map/MapMarker.tsx","../src/PieChart/PieArcSeries/useInterpolate.ts","../src/PieChart/PieArcSeries/PieArc.tsx","../src/PieChart/PieArcSeries/findBreakPoint.ts","../src/PieChart/PieArcSeries/PieArcLabel.tsx","../src/PieChart/PieArcSeries/radiusUtils.ts","../src/PieChart/PieArcSeries/PieArcSeries.tsx","../src/PieChart/PieChart.tsx","../src/Sankey/utils.ts","../src/Sankey/Sankey.tsx","../src/Sankey/SankeyLabel/SankeyLabel.tsx","../src/Sankey/SankeyLink/SankeyLink.tsx","../src/Sankey/SankeyNode/SankeyNode.tsx","../src/Sparkline/SparklineChart.tsx","../src/Sparkline/AreaSparklineChart.tsx","../src/Sparkline/BarSparklineChart.tsx","../src/Sparkline/SonarChart.tsx","../src/RadialAreaChart/RadialAreaSeries/RadialArea.tsx","../src/RadialAreaChart/RadialAreaSeries/RadialLine.tsx","../src/RadialScatterPlot/RadialScatterPlot.tsx","../src/RadialScatterPlot/RadialScatterSeries/RadialScatterPoint.tsx","../src/RadialScatterPlot/RadialScatterSeries/RadialScatterSeries.tsx","../src/RadialAreaChart/RadialAreaSeries/RadialPointSeries.tsx","../src/RadialAreaChart/RadialAreaSeries/RadialAreaSeries.tsx","../src/RadialAreaChart/RadialAreaChart.tsx","../node_modules/d3-path/src/path.js","../src/RadialBarChart/RadialBarSeries/MotionBar.tsx","../src/RadialBarChart/RadialBarSeries/RadialGuideBar.tsx","../src/RadialBarChart/RadialBarSeries/RadialBar.tsx","../src/RadialBarChart/RadialBarSeries/RadialBarSeries.tsx","../src/RadialBarChart/RadialBarChart.tsx","../src/RadialGauge/RadialGaugeSeries/RadialGaugeArc.tsx","../src/RadialGauge/RadialGaugeSeries/RadialGaugeLabel/RadialGaugeLabel.tsx","../src/RadialGauge/RadialGaugeSeries/RadialGaugeValueLabel/RadialGaugeValueLabel.tsx","../src/RadialGauge/RadialGaugeSeries/RadialGaugeOuterArc.tsx","../src/RadialGauge/RadialGaugeSeries/RadialGaugeSeries.tsx","../src/RadialGauge/RadialGaugeSeries/RadialGaugeStackedArc.tsx","../src/RadialGauge/RadialGaugeSeries/StackedRadialGaugeValueLabel/StackedRadialGaugeValueLabel.tsx","../src/RadialGauge/RadialGaugeSeries/StackedRadialGaugeSeries.tsx","../src/RadialGauge/RadialGaugeSeries/StackedRadialGaugeDescriptionLabel/StackedRadialGaugeDescriptionLabel.tsx","../src/RadialGauge/RadialGauge.tsx","../src/Heatmap/HeatmapSeries/HeatmapCell.tsx","../src/Heatmap/HeatmapSeries/HeatmapSeries.tsx","../src/Heatmap/Heatmap.tsx","../node_modules/date-fns/toDate.mjs","../node_modules/date-fns/constructFrom.mjs","../node_modules/date-fns/addDays.mjs","../node_modules/date-fns/subDays.mjs","../src/Heatmap/calendarUtils.ts","../src/Heatmap/CalendarHeatmap.tsx","../src/LinearGauge/LinearGaugeBar.tsx","../src/LinearGauge/LinearGaugeOuterBar.tsx","../src/LinearGauge/LinearGaugeSeries.tsx","../src/LinearGauge/LinearGauge.tsx","../src/VennDiagram/useInterpolate.ts","../src/VennDiagram/VennArc.tsx","../src/VennDiagram/VennLabel.tsx","../src/VennDiagram/VennOuterLabel.tsx","../src/VennDiagram/VennSeries.tsx","../src/VennDiagram/starEuler/starEuler.ts","../src/VennDiagram/VennDiagram.tsx","../src/BubbleChart/Bubble.tsx","../src/BubbleChart/BubbleLabel.tsx","../src/BubbleChart/BubbleSeries.tsx","../src/BubbleChart/BubbleChart.tsx","../src/TreeMap/TreeMapLabel.tsx","../src/TreeMap/TreeMapRect.tsx","../src/TreeMap/TreeMapSeries.tsx","../src/TreeMap/TreeMap.tsx","../src/BarList/BarListSeries.tsx","../src/BarList/BarList.tsx","../src/Meter/MeterColumn.tsx","../src/Meter/Meter.tsx","../src/RadarChart/RadarChartSeries.tsx","../src/RadarChart/RadarChart.tsx","../src/FunnelChart/FunnelSeries/FunnelArc.tsx","../src/FunnelChart/FunnelSeries/FunnelAxis/FunnelAxisLabel.tsx","../src/FunnelChart/FunnelSeries/FunnelAxis/FunnelAxisLine.tsx","../src/FunnelChart/FunnelSeries/FunnelAxis/FunnelAxis.tsx","../src/FunnelChart/FunnelSeries/FunnelSeries.tsx","../src/FunnelChart/FunnelChart.tsx","../src/SunburstChart/useInterpolate.ts","../src/SunburstChart/SunburstArc.tsx","../src/SunburstChart/SunburstArcLabel.tsx","../src/SunburstChart/SunburstSeries.tsx","../src/SunburstChart/SunburstChart.tsx","../src/WordCloud/WordCloudLabel.tsx","../src/WordCloud/WordCloud.tsx"],"sourcesContent":["import humanFormat from 'human-format';\n\nconst humanFormatScale = new humanFormat.Scale({\n k: 1000,\n M: 1000000,\n B: 1000000000\n});\n\nconst humanFormatMillionScale = new humanFormat.Scale({\n M: 1,\n B: 1000,\n T: 1000000\n});\n\nconst ONE_MILLION = 1000000;\nconst ONE_BILLION = 1000000000;\n\nexport const humanFormatBigInteger = (bigInteger) => {\n if (bigInteger.greater(ONE_BILLION)) {\n return humanFormat(bigInteger.divide(ONE_MILLION).toJSNumber(), {\n scale: humanFormatMillionScale\n });\n }\n return humanFormat(bigInteger.toJSNumber(), { scale: humanFormatScale });\n};\n\nexport const bigIntegerToLocaleString = (bigInteger) => {\n let i = 0;\n let formattedString = '';\n for (const c of bigInteger.toString().split('').reverse()) {\n if (i > 0 && i % 3 === 0) {\n formattedString = ',' + formattedString;\n }\n formattedString = c + formattedString;\n i++;\n }\n return formattedString;\n};\n","export interface Dimensions {\n xOffset: number;\n yOffset: number;\n height: number;\n width: number;\n chartWidth: number;\n chartHeight: number;\n xMargin: number;\n yMargin: number;\n}\n\nexport interface DimensionParameter {\n xOffset: number;\n yOffset: number;\n yAxis: any;\n xAxis: any;\n height: number;\n width: number;\n margins: Margins;\n}\n\nexport type Margins =\n | [number, number]\n | [number, number, number, number]\n | number;\n\n/**\n * Given a margins object, returns the top/left/right/bottom positions.\n */\nfunction parseMargins(margins?: Margins) {\n let top = 0;\n let right = 0;\n let bottom = 0;\n let left = 0;\n\n if (Array.isArray(margins)) {\n if (margins.length === 2) {\n top = margins[0];\n bottom = margins[0];\n left = margins[1];\n right = margins[1];\n } else if (margins.length === 4) {\n top = margins[0];\n right = margins[1];\n bottom = margins[2];\n left = margins[3];\n }\n } else if (margins !== undefined) {\n top = margins;\n right = margins;\n bottom = margins;\n left = margins;\n }\n\n return {\n top,\n right,\n bottom,\n left\n };\n}\n\n/**\n * Calculates the margins for the chart.\n */\nfunction calculateMarginOffsets(\n height: number,\n width: number,\n margins: { left: number; right: number; bottom: number; top: number }\n) {\n const { left, right, bottom, top } = margins;\n const newHeight = height - top - bottom;\n const newWidth = width - left - right;\n\n return {\n height: newHeight,\n width: newWidth\n };\n}\n\n/**\n * Calculates the dimensions for the chart.\n */\nexport function getDimension({\n xOffset,\n yOffset,\n height,\n width,\n margins\n}: DimensionParameter | any): Dimensions {\n const parsedMargins = parseMargins(margins);\n const marginDims = calculateMarginOffsets(height, width, parsedMargins);\n const chartWidth = marginDims.width - xOffset;\n const chartHeight = marginDims.height - yOffset;\n\n return {\n xOffset,\n yOffset,\n height,\n width,\n chartWidth,\n chartHeight,\n xMargin: xOffset + parsedMargins.left,\n yMargin: parsedMargins.top\n };\n}\n","import { min, max } from 'd3-array';\n\n/**\n * Gets the min/max values handling nested arrays.\n */\nexport function extent(data: any[], attr: string): number[] {\n const accessor = (val, fn) => {\n if (Array.isArray(val.data)) {\n return fn(val.data, (vv) => vv[attr]);\n }\n return val[attr];\n };\n\n const minVal = min(data, (d) => accessor(d, min));\n const maxVal = max(data, (d) => accessor(d, max));\n\n return [minVal, maxVal];\n}\n\n/**\n * Get the domain for the Y Axis.\n */\nexport function getYDomain({\n data,\n scaled = false,\n isDiverging = false\n}): number[] {\n const [startY, endY] = extent(data, 'y');\n const [startY1, endY1] = extent(data, 'y1');\n\n // If dealing w/ negative numbers, we should\n // normalize the top and bottom values\n if (startY < 0 || isDiverging) {\n const posStart = -startY;\n const maxNum = Math.max(posStart, endY);\n\n return [-maxNum, maxNum];\n }\n\n // Scaled start scale at non-zero\n if (scaled) {\n return [startY1, endY1];\n }\n\n // Start at 0 based\n return [0, endY1];\n}\n\n/**\n * Get the domain for the X Axis.\n */\nexport function getXDomain({\n data,\n scaled = false,\n isDiverging = false\n}): number[] {\n const startX0 = extent(data, 'x0')[0];\n const endX1 = extent(data, 'x1')[1];\n\n // Histograms use dates for start/end\n if (typeof startX0 === 'number' && typeof endX1 === 'number') {\n // If dealing w/ negative numbers, we should\n // normalize the top and bottom values\n if (startX0 < 0 || isDiverging) {\n const posStart = -startX0;\n const maxNum = Math.max(posStart, endX1);\n\n return [-maxNum, maxNum];\n }\n\n // If not scaled, return 0/max domains\n if (!scaled) {\n return [0, endX1];\n }\n }\n\n // Scaled start scale at non-zero\n return [startX0, endX1];\n}\n","import { ChartInternalDataTypes } from '@/common/data';\n\n// https://stackoverflow.com/questions/673905/best-way-to-determine-users-locale-within-browser\nconst getNavigatorLanguage = () => {\n if (typeof window === 'undefined') {\n return 'en';\n }\n\n if (navigator.languages && navigator.languages.length) {\n return navigator.languages[0];\n }\n\n if (\n (navigator as any).userLanguage ||\n navigator.language ||\n (navigator as any).browserLanguage\n ) {\n return 'en';\n }\n};\n\nconst locale = getNavigatorLanguage();\n\nconst options = {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour12: true,\n formatMatcher: 'best fit'\n};\n\n/**\n * Format a value based on type.\n */\nexport function formatValue(value: ChartInternalDataTypes): string {\n if (value !== undefined) {\n if (value instanceof Date) {\n return (value as Date).toLocaleDateString(locale, options as any);\n } else if (typeof value === 'number') {\n return value.toLocaleString();\n }\n\n return value as string;\n }\n\n return 'No value';\n}\n\n/**\n * Generate aria label text for the given data point(s)\n * @param datapoint\n * @returns Aria Label\n */\nexport function getAriaLabel(datapoint) {\n const isArray = Array.isArray(datapoint);\n if (isArray) {\n return datapoint?.map((row) => getAriaLabel(row)).join(', ');\n } else {\n const key = datapoint?.key || datapoint?.x;\n // 'data' or 'y' will not be an array as the label is unique for each element\n const value = datapoint?.data || datapoint?.y;\n return `${key}: ${formatValue(value)}`;\n }\n}\n","import { curveLinear, curveMonotoneX, curveStep } from 'd3-shape';\n\nexport type InterpolationTypes = 'linear' | 'smooth' | 'step';\nexport type RadialInterpolationTypes = 'linear' | 'smooth';\n\n/**\n * Helper function for interpolation.\n */\nexport function interpolate(\n type: InterpolationTypes | RadialInterpolationTypes\n) {\n if (type === 'smooth') {\n return curveMonotoneX;\n } else if (type === 'step') {\n return curveStep;\n } else {\n return curveLinear;\n }\n}\n","import { bisector } from 'd3-array';\nimport { applyToPoint, applyToPoints, inverse } from 'transformation-matrix';\n\ntype PointObjectNotation = { x: number; y: number };\n\n/**\n * Add ability to calculate scale band position.\n * Reference: https://stackoverflow.com/questions/38633082/d3-getting-invert-value-of-band-scales\n */\nconst scaleBandInvert = (scale, round = false) => {\n const domain = scale.domain();\n const paddingOuter = scale(domain[0]);\n const eachBand = scale.step();\n const [, end] = scale.range();\n\n return (offset) => {\n // Keep the band from going outside the domain length\n let band = Math.min(\n (offset - paddingOuter) / eachBand,\n domain.length - 0.01\n );\n\n // Catch negative band values from horizontal charts exceeding domain length\n if (band < 0 && Math.abs(band) > domain.length - 1) {\n band = Math.floor(Math.abs(band)) * -1;\n }\n\n // Round to the closest index OR take the floor value\n let index = round\n ? Math.round(band) % domain.length\n : Math.floor(band) % domain.length;\n\n // Handle horizontal charts...\n if (end === 0) {\n index = index * -1;\n }\n\n return domain[Math.max(0, Math.min(index, domain.length - 1))];\n };\n};\n\n/**\n * Get the data point closest to a given position on a continuous scale.\n *\n * @param {Object} params - The parameters for the function.\n * @param {number} params.pos - The position to find the closest point to.\n * @param {Object} params.scale - The scale object.\n * @param {Array} params.data - The data array.\n * @param {string} [params.attr='x'] - The attribute to use for comparison.\n * @param {boolean} [params.roundDown=false] - Whether to round down to the nearest point.\n *\n * @returns {Object} The closest point to the specified position.\n */\nexport const getClosestContinousScalePoint = ({\n pos,\n scale,\n data,\n attr = 'x',\n roundDown = false\n}: {\n pos: number;\n scale: any;\n data: any[];\n attr?: string;\n roundDown?: boolean;\n}) => {\n const domain = scale.invert(pos);\n\n // Select the index\n const bisect = bisector((d: any) => {\n // add 1 to an index so it's the upper limit of a domain\n return attr === 'i' ? d[attr] + 1 : d[attr];\n }).right;\n const index = bisect(data, domain);\n\n // Determine min index\n const minIndex = Math.max(0, index - 1);\n const before = data[minIndex];\n\n if (roundDown) {\n return before;\n }\n\n // Determine max index\n const maxIndex = Math.min(data.length - 1, index);\n const after = data[maxIndex];\n\n // Determine which is closest to the point\n let beforeVal = before[attr];\n let afterVal = after[attr];\n beforeVal = domain - beforeVal;\n afterVal = afterVal - domain;\n\n return beforeVal < afterVal ? before : after;\n};\n\n/**\n * Get the data point closest to a given position on a band scale. This rounds down by default.\n *\n * @param {Object} params - The parameters for the function.\n * @param {number} params.pos - The position to find the closest point to.\n * @param {Object} params.scale - The scale object.\n * @param {Array} params.data - The data array.\n * @param {boolean} [params.roundClosest=false] - Whether to round to the closest point instead of down.\n *\n * @returns {Object} The closest point to the specified position.\n */\nexport const getClosestBandScalePoint = ({\n pos,\n scale,\n data,\n roundClosest = false\n}: {\n pos: number;\n scale: any;\n data: any[];\n roundClosest?: boolean;\n}) => {\n const domain = scale.domain();\n let prop;\n\n // Of course the Marimekko is a pain...\n if (scale.mariemkoInvert) {\n prop = scale.mariemkoInvert(pos);\n } else {\n prop = scaleBandInvert(scale, roundClosest)(pos);\n }\n\n const idx = domain.indexOf(prop);\n return data[idx];\n};\n\n/**\n * Given an event, get the parent svg element;\n */\nexport const getParentSVG = (event) => {\n // set node to targets owner svg\n let node = event.target.ownerSVGElement;\n\n // find the outermost svg\n if (node) {\n while (node.ownerSVGElement) {\n node = node.ownerSVGElement;\n }\n }\n\n return node;\n};\n\n/**\n * Given an event, get the relative X/Y position for a target.\n */\nexport const getPositionForTarget = ({ target, clientX, clientY }) => {\n const rect = target.getBoundingClientRect();\n return {\n x: clientX - (rect?.left || 0) - target.clientLeft,\n y: clientY - (rect?.top || 0) - target.clientTop\n };\n};\n\n/**\n * Gets the point from q given matrix.\n */\nexport const getPointFromMatrix = (event, matrix): PointObjectNotation => {\n const parent = getParentSVG(event);\n\n if (!parent) {\n return null;\n }\n\n // Determines client coordinates relative to the editor component\n const { top, left } = parent.getBoundingClientRect();\n const x = event.clientX - left;\n const y = event.clientY - top;\n\n // Transforms the coordinate to world coordinate (in the SVG/DIV world)\n return applyToPoint(inverse(matrix), { x, y });\n};\n\n/**\n * Get the start/end matrix.\n */\nexport const getLimitMatrix = (\n height: number,\n width: number,\n matrix\n): PointObjectNotation[] =>\n applyToPoints(matrix, [\n { x: 0, y: 0 },\n { x: width, y: height }\n ]);\n\n/**\n * Constrain the matrix.\n */\nexport const constrainMatrix = (height: number, width: number, matrix) => {\n const [min, max] = getLimitMatrix(height, width, matrix) as {\n x: number;\n y: number;\n }[];\n\n if (max.x < width || max.y < height) {\n return true;\n }\n\n if (min.x > 0 || min.y > 0) {\n return true;\n }\n\n return false;\n};\n\n/**\n * Determine if scale factor is less than allowed.\n */\nconst lessThanScaleFactorMin = (value, scaleFactor: number) =>\n value.scaleFactorMin && value.d * scaleFactor <= value.scaleFactorMin;\n\n/**\n * Determine if scale factor is larger than allowed.\n */\nconst moreThanScaleFactorMax = (value, scaleFactor: number) =>\n value.scaleFactorMax && value.d * scaleFactor >= value.scaleFactorMax;\n\n/**\n * Determine if both min and max scale fctors are going out of bounds.\n */\nexport const isZoomLevelGoingOutOfBounds = (value, scaleFactor: number) => {\n const a = lessThanScaleFactorMin(value, scaleFactor) && scaleFactor < 1;\n const b = moreThanScaleFactorMax(value, scaleFactor) && scaleFactor > 1;\n return a || b;\n};\n","/**\n * Toggle the text selection of the body.\n */\nexport function toggleTextSelection(allowSelection: boolean) {\n const style = allowSelection ? '' : 'none';\n [\n '-webkit-touch-callout',\n '-webkit-user-select',\n '-khtml-user-select',\n '-moz-user-select',\n '-ms-user-select',\n 'user-select'\n ].forEach((prop) => (document.body.style[prop] = style));\n}\n","import { ChartInternalShallowDataShape } from '@/common/data';\n\n/**\n * Calculates whether the stroke should be shown.\n */\nexport function calculateShowStroke(\n current: ChartInternalShallowDataShape,\n data: ChartInternalShallowDataShape[]\n) {\n const i = data.indexOf(current);\n let showLine = false;\n\n const prev = data[i - 1];\n if (i > 0 && prev.y) {\n showLine = true;\n }\n\n const cur = data[i];\n if (cur.y) {\n showLine = true;\n }\n\n const next = data[i + 1];\n if (i < data.length - 1 && next.y) {\n showLine = true;\n }\n\n return showLine;\n}\n","import { TimeInterval } from 'd3-time';\n\nconst ONE_DAY = 60 * 60 * 24;\nconst DURATION_TICK_STEPS = [\n 0.001, // 1 ms\n 0.005, // 5 ms\n 0.01, // 10 ms\n 0.05, // 50 ms\n 0.1, // 100 ms\n 0.5, // 500 ms\n 1, // 1 s\n 5, // 5 s\n 10, // 10 s\n 15, // 15 s\n 60, // 1 m\n 60 * 15, // 15 m\n 60 * 30, // 30 m\n 60 * 60, // 1 h\n 60 * 60 * 2, // 2 h\n 60 * 60 * 4, // 4 h\n 60 * 60 * 6, // 6 h\n 60 * 60 * 8, // 8 h\n 60 * 60 * 12, // 12 h\n ONE_DAY // 24 h\n];\n\n/**\n * Reduce the ticks to the max number of ticks.\n */\nexport function reduceTicks<T>(ticks: T[], maxTicks: number) {\n if (ticks.length > maxTicks) {\n const reduced: T[] = [];\n const modulus = Math.floor(ticks.length / maxTicks);\n\n for (let i = 0; i < ticks.length; i++) {\n if (i % modulus === 0) {\n reduced.push(ticks[i]);\n }\n }\n ticks = reduced;\n }\n\n return ticks;\n}\n\n/**\n * Determine the max ticks for the available width.\n */\nexport function getMaxTicks(size: number, dimension: number) {\n const tickWidth = Math.max(size, 0);\n return Math.floor(dimension / tickWidth);\n}\n\n/**\n * Formats the ticks in a duration format.\n */\nexport function getDurationTicks(domain, maxTicks) {\n const domainWidth = domain[1] - domain[0];\n let tickStep: number | null = null;\n for (const s of DURATION_TICK_STEPS) {\n if (domainWidth / s < maxTicks) {\n tickStep = s;\n break;\n }\n }\n\n if (tickStep === null) {\n const numDayTicks = domainWidth / ONE_DAY;\n const dayStep = Math.ceil(numDayTicks / maxTicks);\n tickStep = ONE_DAY * dayStep;\n }\n\n const ticks = [domain[0]];\n while (ticks[ticks.length - 1] + tickStep <= domain[1]) {\n ticks.push(ticks[ticks.length - 1] + tickStep);\n }\n\n return ticks;\n}\n\n/**\n * Get the tick values from the scale.\n */\nexport function getTicks(\n scale: any,\n tickValues: any[],\n type: 'value' | 'category' | 'time' | 'duration',\n maxTicks = 100,\n interval?: number | TimeInterval\n) {\n let result;\n\n if (tickValues) {\n result = tickValues;\n } else {\n if (scale.ticks) {\n if (type === 'duration') {\n result = getDurationTicks(scale.domain(), maxTicks);\n } else if (interval) {\n result = scale.ticks(interval);\n } else {\n if (type === 'time') {\n // If its time, we need to handle the time count\n // manually because d3 does this odd rounding\n result = scale.ticks();\n result = reduceTicks(result, maxTicks);\n } else {\n result = scale.ticks(maxTicks);\n }\n }\n } else {\n tickValues = scale.domain();\n result = reduceTicks(tickValues, maxTicks);\n }\n }\n\n return result;\n}\n","/**\n * Get the angle from a radian.\n */\nexport const getDegrees = (radians: number) => (radians / Math.PI) * 180 - 90;\n\nexport const roundDecimals = (value: number, decimals: number = 5): number =>\n parseFloat(value.toFixed(decimals));\n","import classNames from 'classnames';\n\nexport interface PropFunctionTypes {\n /**\n * Classnames to apply to the element.\n */\n className?: any;\n\n /**\n * CSS styles to apply to the element.\n */\n style?: any;\n}\n\nexport const functionProps = (prop: string, val: any, data: any) => {\n if (typeof val === 'function') {\n return val(data);\n } else if (prop === 'className') {\n return classNames(val);\n } else if (val !== undefined || val !== null) {\n return val;\n }\n\n return {};\n};\n\nexport const constructFunctionProps = (\n props: PropFunctionTypes,\n data: any\n) => ({\n className: functionProps('className', props.className, data),\n style: functionProps('style', props.style, data)\n});\n","type AccessorCallback = (data: any) => any;\n\n/**\n * Given a dataset and a list of accessors, returns a unique collection.\n */\nexport function uniqueBy<T = any>(data: T[], ...accessors: AccessorCallback[]) {\n const result: any[] = [];\n\n const ittr = (arr: T[], depth: number) => {\n for (const a of arr) {\n const acc = accessors[depth];\n if (acc === undefined) {\n throw new Error(`Accessor not found for depth: ${depth}`);\n }\n\n const val = acc(a);\n if (Array.isArray(val)) {\n ittr(val, depth + 1);\n } else if (!result.includes(val)) {\n result.push(val);\n }\n }\n };\n\n ittr(data, 0);\n\n return result;\n}\n","export interface TextDimensions {\n height: number;\n width: number;\n}\n\nconst cache: { [key: string]: TextDimensions } = {};\n\nexport const calculateDimensions = (\n text: string,\n fontFamily: string,\n fontSize: string | number\n): TextDimensions => {\n const key = `${text}_${fontFamily}_${fontSize}`;\n\n // Check if we have a cache hit\n if (cache[key]) {\n return cache[key];\n }\n\n // If we are in a Node.js environment\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n const height = parseInt(typeof fontSize === 'string' ? fontSize : fontSize.toString(), 10);\n const dimensions = {\n height,\n // 8 is an approximation of the width of a character\n width: text.length * 8\n };\n\n cache[key] = dimensions;\n\n return dimensions;\n }\n\n // Create a temporary div element\n const element = document.createElement('div');\n\n // Set up the style so the size can be measured\n element.style.fontFamily = fontFamily;\n element.style.fontSize = typeof fontSize === 'string' ? fontSize : `${fontSize}px`;\n element.style.position = 'absolute';\n element.style.left = '-9999px';\n element.style.whiteSpace = 'nowrap';\n element.style.height = 'auto';\n element.style.fontWeight = 'normal';\n element.style.lineHeight = 'normal';\n element.style.width = 'auto';\n element.style.wordBreak = 'normal';\n\n // Add the text to the div\n element.textContent = text;\n\n // Add the div to the body\n document.body.appendChild(element);\n\n // Measure the div\n const dimensions = {\n height: element.offsetHeight,\n width: element.offsetWidth\n };\n\n // Remove the div from the body\n document.body.removeChild(element);\n\n // Store the result in the cache for future calls\n cache[key] = dimensions;\n\n return dimensions;\n};\n","import React, { ReactElement } from 'react';\nimport { calculateDimensions } from './size';\n\nexport interface WrapTextInputs {\n key: string;\n x?: any;\n paddingY?: number;\n paddingX?: number;\n width: number;\n height?: number;\n fontFamily: string;\n fontSize: number;\n wrap?: boolean;\n size?: {\n width: number;\n height: number;\n };\n visibility?: 'auto' | 'always';\n}\n\nexport function wrapText({\n key,\n x = 0,\n size,\n paddingY,\n wrap = true,\n paddingX,\n width,\n height,\n fontFamily,\n fontSize,\n visibility = 'auto'\n}: WrapTextInputs): ReactElement | ReactElement[] | null {\n size = size || calculateDimensions(key, fontFamily, fontSize);\n const words = key.toString().split(/\\s+/);\n\n if (words.length > 1 && size.width > width) {\n let rows = [];\n let maxWidth = 0;\n let maxHeight = 0;\n let curText = '';\n let currWidth = 0;\n let nextText = '';\n let nextWidth = 0;\n\n for (const word of words) {\n nextText = curText === '' ? word : `${curText} ${word}`;\n nextWidth = calculateDimensions(nextText, fontFamily, fontSize).width;\n\n if (nextWidth <= width - (paddingX ? 2 * paddingX : 0)) {\n curText = nextText;\n currWidth = nextWidth;\n } else {\n rows.push(curText);\n maxWidth = Math.max(maxWidth, currWidth);\n curText = word;\n currWidth = calculateDimensions(curText, fontFamily, fontSize).width;\n }\n }\n rows.push(curText);\n maxHeight = rows.length * size.height;\n\n if (visibility !== 'always') {\n if (height && maxHeight >= height - (paddingY ? 2 * paddingY : 0)) {\n return null;\n }\n\n if (width && maxWidth >= width - (paddingX ? 2 * paddingX : 0)) {\n return null;\n }\n }\n\n if (!wrap && rows.length > 1) {\n return rows[0];\n }\n\n return rows.map((r, i) => (\n <tspan\n key={i}\n dominantBaseline=\"alphabetic\"\n style={{ baselineShift: '0%' }}\n dy={\n i > 0\n ? size.height\n : height\n ? size.height / 2 - 5\n : -maxHeight / 2 + size.height\n }\n x={x}\n >\n {r}\n </tspan>\n ));\n }\n\n if (visibility !== 'always') {\n if (height && size.height + paddingY >= height) {\n return null;\n }\n\n if (width && size.width + paddingX >= width) {\n return null;\n }\n }\n\n // NOTE: 5px seems to magic number for making it center\n return (\n <tspan\n dominantBaseline=\"alphabetic\"\n style={{ baselineShift: '0%' }}\n dy={size.height / 2 - 5}\n x={x}\n >\n {key}\n </tspan>\n );\n}\n","import { PointerEvent, useCallback, useRef } from 'react';\n\nexport interface HoverIntentOptions {\n interval?: number;\n sensitivity?: number;\n timeout?: number;\n disabled?: boolean;\n onPointerOver: (event: PointerEvent<SVGElement>) => void;\n onPointerOut: (event: PointerEvent<SVGElement>) => void;\n}\n\nexport interface HoverIntentResult {\n pointerOut: (event: PointerEvent<SVGElement>) => void;\n pointerOver: (event: PointerEvent<SVGElement>) => void;\n}\n\n/**\n * Hover intent identifies if the user actually is\n * intending to over by measuring the position of the mouse\n * once a pointer enters and determining if in a duration if\n * the mouse moved inside a certain threshold and fires the events.\n */\nexport const useHoverIntent = ({\n sensitivity = 7,\n interval = 50,\n timeout = 10,\n disabled,\n onPointerOver,\n onPointerOut\n}: HoverIntentOptions | undefined): HoverIntentResult => {\n const mouseOver = useRef<boolean>(false);\n const timer = useRef<any | null>(null);\n const state = useRef<number>(0);\n const coords = useRef({\n x: null,\n y: null,\n px: null,\n py: null\n });\n\n const onMouseMove = useCallback((event: MouseEvent) => {\n coords.current.x = event.clientX;\n coords.current.y = event.clientY;\n }, []);\n\n const comparePosition = useCallback(\n (event: PointerEvent<SVGElement>) => {\n timer.current = clearTimeout(timer.current);\n const { px, x, py, y } = coords.current;\n\n if (Math.abs(px - x) + Math.abs(py - y) < sensitivity) {\n state.current = 1;\n onPointerOver(event);\n } else {\n coords.current.px = x;\n coords.current.py = y;\n timer.current = setTimeout(() => comparePosition(event), interval);\n }\n },\n [interval, onPointerOver, sensitivity]\n );\n\n const cleanup = useCallback(() => {\n clearTimeout(timer.current);\n document.removeEventListener('mousemove', onMouseMove, false);\n }, [onMouseMove]);\n\n const pointerOver = useCallback(\n (event: PointerEvent<SVGElement>) => {\n if (!disabled) {\n mouseOver.current = true;\n cleanup();\n\n if (state.current !== 1) {\n coords.current.px = event.nativeEvent.x;\n coords.current.py = event.nativeEvent.y;\n document.addEventListener('mousemove', onMouseMove, false);\n timer.current = setTimeout(() => comparePosition(event), timeout);\n }\n }\n },\n [cleanup, comparePosition, disabled, onMouseMove, timeout]\n );\n\n const delay = useCallback(\n (event: PointerEvent<SVGElement>) => {\n timer.current = clearTimeout(timer.current);\n state.current = 0;\n onPointerOut(event);\n },\n [onPointerOut]\n );\n\n const pointerOut = useCallback(\n (event: PointerEvent<SVGElement>) => {\n mouseOver.current = false;\n cleanup();\n\n if (state.current === 1) {\n timer.current = setTimeout(() => delay(event), timeout);\n }\n },\n [cleanup, delay, timeout]\n );\n\n return {\n pointerOver,\n pointerOut\n };\n};\n","import { useEffect, useRef, useState, RefObject } from 'react';\n\ntype Size = {\n width: number | undefined;\n height: number | undefined;\n};\n\nexport const useResizeObserver = <T extends HTMLElement>(): [\n RefObject<T>,\n Size\n] => {\n const ref = useRef<T>(null);\n const [size, setSize] = useState<Size>({\n width: undefined,\n height: undefined\n });\n\n useEffect(() => {\n const element = ref.current;\n if (!element) return;\n\n const resizeObserver = new ResizeObserver((entries) => {\n if (entries.length === 0) return;\n const entry = entries[0];\n setSize({\n width: entry.contentRect.width,\n height: entry.contentRect.height\n });\n });\n\n resizeObserver.observe(element);\n\n return () => {\n resizeObserver.unobserve(element);\n };\n }, []);\n\n return [ref, size];\n};\n","/**\n * Merges default props with props and returns a new object, filtering out undefined values to keep original behavior.\n * @param defaultProps - The default props to merge.\n * @param props - The props to merge.\n * @returns The merged props.\n */\n\nexport const mergeDefaultProps = <T>(\n defaultProps: Partial<T>,\n props: Partial<T> = {}\n): T => {\n const filteredProps = Object.fromEntries(\n Object.entries(props).filter(([_, value]) => value !== undefined)\n );\n return { ...defaultProps, ...filteredProps } as T;\n};\n","import React, { Fragment, FC, ReactElement } from 'react';\nimport { GradientProps, Gradient } from '@/common/Gradient';\nimport { CloneElement, useId } from 'reablocks';\nimport { mergeDefaultProps } from '@/common/utils';\n\nexport interface LinearAxisLineProps {\n height: number;\n width: number;\n strokeColor?: string;\n strokeWidth: number;\n strokeGradient: ReactElement<GradientProps, typeof Gradient> | null;\n scale: any;\n orientation: 'horizontal' | 'vertical';\n className?: string;\n}\n\nexport const LinearAxisLine: FC<Partial<LinearAxisLineProps>> = (props) => {\n const {\n strokeColor,\n strokeWidth,\n strokeGradient,\n scale,\n orientation,\n className\n } = mergeDefaultProps(linearAxisLineDefaultProps, props);\n const id = useId();\n const [range0, range1] = scale.range();\n\n return (\n <Fragment>\n <line\n className={className}\n x1={orientation === 'vertical' ? 0 : range0}\n // Workaround for a Chrome/Firefox bug where it won't render gradients for straight lines\n x2={orientation === 'vertical' ? 0.00001 : range1}\n y1={orientation === 'vertical' ? range0 : 0}\n y2={orientation === 'vertical' ? range1 : 0.00001}\n strokeWidth={strokeWidth}\n stroke={strokeGradient ? `url(#axis-gradient-${id})` : strokeColor}\n />\n {strokeGradient && (\n <CloneElement<GradientProps>\n element={strokeGradient}\n id={`axis-gradient-${id}`}\n />\n )}\n </Fragment>\n );\n};\n\nexport const linearAxisLineDefaultProps = {\n strokeColor: '#8F979F',\n strokeWidth: 1\n};\n","import React, { FC, useMemo } from 'react';\n\nexport interface LinearAxisTickLineProps {\n height: number;\n width: number;\n orientation: 'horizontal' | 'vertical';\n size: number;\n strokeColor?: string;\n strokeWidth: number;\n position: 'start' | 'end' | 'center';\n className?: string;\n}\n\nexport const LinearAxisTickLine: FC<Partial<LinearAxisTickLineProps>> = (\n props\n) => {\n const { size, position, orientation, strokeColor, strokeWidth, className } = {\n ...LINEAR_AXIS_TICK_LINE_DEFAULT_PROPS,\n ...props\n };\n\n const path = useMemo(() => {\n const isVertical = orientation === 'vertical';\n const tickSize = size || 0;\n const start =\n position === 'start'\n ? tickSize * -1\n : position === 'center'\n ? tickSize * -0.5\n : 0;\n const end = start + tickSize;\n\n return {\n x1: isVertical ? end : 0,\n x2: isVertical ? start : 0,\n y1: isVertical ? 0 : start,\n y2: isVertical ? 0 : end\n };\n }, [orientation, position, size]);\n\n return (\n <line\n className={className}\n strokeWidth={strokeWidth}\n stroke={strokeColor}\n {...path}\n />\n );\n};\n\nexport const LINEAR_AXIS_TICK_LINE_DEFAULT_PROPS = {\n strokeColor: '#8F979F',\n strokeWidth: 1,\n size: 5\n};\n","import React, { FC, ReactElement } from 'react';\nimport {\n LINEAR_AXIS_TICK_LINE_DEFAULT_PROPS,\n LinearAxisTickLine,\n LinearAxisTickLineProps\n} from './LinearAxisTickLine';\nimport { mergeDefaultProps } from '@/common/utils';\n\nexport interface LinearAxisTickLabelProps {\n text: string;\n fullText: string;\n angle: number;\n orientation: 'horizontal' | 'vertical';\n half: 'start' | 'end' | 'center';\n line: ReactElement<LinearAxisTickLineProps, typeof LinearAxisTickLine>;\n format?: (v) => any;\n /**\n * Format tooltip title on hover label.\n */\n formatTooltip?: (value: any) => any | string;\n fill: string;\n fontSize: number;\n fontFamily: string;\n rotation: boolean | number;\n padding: number | { fromAxis: number; alongAxis: number };\n textAnchor?: 'start' | 'end' | 'middle';\n position: 'start' | 'end' | 'center';\n align: 'start' | 'end' | 'center' | 'inside' | 'outside';\n className?: string;\n}\n\nexport const LinearAxisTickLabel: FC<Partial<LinearAxisTickLabelProps>> = (\n props\n) => {\n const {\n text,\n fullText,\n angle,\n orientation,\n half,\n line,\n textAnchor,\n position,\n className,\n fill,\n fontSize,\n fontFamily,\n rotation,\n padding,\n formatTooltip,\n align\n } = mergeDefaultProps(LINEAR_AXIS_TICK_LABEL_DEFAULT_PROPS, props);\n\n function getAlign() {\n if ((align === 'inside' || align === 'outside') && half === 'center') {\n return 'center';\n }\n\n if (align === 'inside') {\n return half === 'start' ? 'end' : 'start';\n }\n\n if (align === 'outside') {\n return half === 'start' ? 'start' : 'end';\n }\n\n return align;\n }\n\n // bug in this function - spacing is NA\n function getTickLineSpacing() {\n if (!line) {\n return [0, 0];\n }\n\n const lineProps = { ...LINEAR_AXIS_TICK_LINE_DEFAULT_PROPS, ...line.props };\n const size = lineProps.size ?? 3;\n const position = lineProps.position ?? 'center';\n\n if (position === 'start') {\n return [size * -1, 0];\n } else if (position === 'end') {\n return [0, size];\n } else {\n return [size * -0.5, size * 0.5];\n }\n }\n\n function getOffset() {\n const adjustedPadding =\n typeof padding === 'number'\n ? { fromAxis: padding, alongAxis: padding }\n : padding;\n\n const spacing = getTickLineSpacing();\n const offset1 =\n position === 'start'\n ? spacing[0] - adjustedPadding.fromAxis\n : position === 'end'\n ? spacing[1] + adjustedPadding.fromAxis\n : 0;\n\n const align = getAlign();\n let offset2 = 0;\n offset2 +=\n align === 'center'\n ? 0\n : align === 'start'\n ? -adjustedPadding.alongAxis\n : adjustedPadding.alongAxis;\n\n const horz = orientation === 'horizontal';\n\n return {\n [horz ? 'x' : 'y']: offset2,\n [horz ? 'y' : 'x']: offset1\n };\n }\n\n function getTextPosition() {\n let transform = '';\n let newtextAnchor = '';\n let alignmentBaseline = 'middle' as 'middle' | 'baseline' | 'hanging';\n\n if (angle !== 0) {\n transform = `rotate(${angle})`;\n newtextAnchor = 'end';\n } else {\n const align = getAlign();\n if (orientation === 'horizontal') {\n newtextAnchor =\n align === 'center' ? 'middle' : align === 'start' ? 'end' : 'start';\n if (position === 'start') {\n alignmentBaseline = 'baseline';\n } else if (position === 'end') {\n alignmentBaseline = 'hanging';\n }\n } else {\n alignmentBaseline =\n align === 'center'\n ? 'middle'\n : align === 'start'\n ? 'baseline'\n : 'hanging';\n if (position === 'start') {\n newtextAnchor = 'end';\n } else if (position === 'end') {\n newtextAnchor = 'start';\n } else {\n newtextAnchor = 'middle';\n }\n }\n }\n\n return {\n transform,\n textAnchor: (textAnchor || newtextAnchor) as\n | 'start'\n | 'end'\n | 'middle'\n | 'inherit',\n alignmentBaseline\n };\n }\n\n const { x, y } = getOffset();\n const textPosition = getTextPosition();\n const titleHover =\n typeof formatTooltip === 'function' ? formatTooltip(fullText) : fullText;\n\n return (\n <g\n transform={`translate(${x}, ${y})`}\n fontSize={fontSize}\n fontFamily={fontFamily}\n >\n <title>{titleHover}</title>\n <text {...textPosition} fill={fill} className={className}>\n {text}\n </text>\n </g>\n );\n};\n\nexport const LINEAR_AXIS_TICK_LABEL_DEFAULT_PROPS: Partial<LinearAxisTickLabelProps> =\n {\n fill: '#8F979F',\n fontSize: 11,\n fontFamily: 'sans-serif',\n rotation: true,\n padding: 5,\n align: 'center'\n };\n","import React, { FC, Fragment, ReactElement, useCallback, useMemo } from 'react';\nimport {\n LINEAR_AXIS_TICK_LABEL_DEFAULT_PROPS,\n LinearAxisTickLabel,\n LinearAxisTickLabelProps\n} from './LinearAxisTickLabel';\nimport {\n LINEAR_AXIS_TICK_LINE_DEFAULT_PROPS,\n LinearAxisTickLine,\n LinearAxisTickLineProps\n} from './LinearAxisTickLine';\nimport { formatValue } from '@/common/utils/formatting';\nimport { getTicks, getMaxTicks } from '@/common/utils/ticks';\nimport { TimeInterval } from 'd3-time';\nimport { CloneElement } from 'reablocks';\nimport { LinearAxisProps } from './LinearAxis';\nimport ellipsize from 'ellipsize';\nimport { max } from 'd3-array';\nimport { calculateDimensions } from '@/common/utils/size';\nimport { mergeDefaultProps } from '@/common/utils';\n\nexport interface LinearAxisTickSeriesProps {\n height: number;\n width: number;\n scale: any;\n interval?: number | TimeInterval;\n tickSize: number;\n tickValues: any[];\n orientation: 'horizontal' | 'vertical';\n label: ReactElement<\n LinearAxisTickLabelProps,\n typeof LinearAxisTickLabel\n > | null;\n line: ReactElement<LinearAxisTickLineProps, typeof LinearAxisTickLine> | null;\n axis: LinearAxisProps;\n}\n\ninterface ProcessedTick {\n text: string;\n fullText: string;\n x: number;\n y: number;\n height: number;\n width: number;\n half: 'start' | 'end' | 'center';\n}\n\nexport const LinearAxisTickSeries: FC<Partial<LinearAxisTickSeriesProps>> = (\n props\n) => {\n const {\n scale,\n orientation,\n height,\n width,\n label,\n tickSize,\n tickValues,\n interval,\n line,\n axis\n } = mergeDefaultProps(LINEAR_AXIS_TICK_SERIES_DEFAULT_PROPS, props);\n\n const labelProps = useMemo(\n () => ({\n ...LINEAR_AXIS_TICK_LABEL_DEFAULT_PROPS,\n ...(label?.props ?? {})\n }),\n [label?.props]\n );\n\n /**\n * Gets the adjusted scale given offsets.\n */\n const getAdjustedScale = useCallback(() => {\n if (scale.bandwidth) {\n let offset = scale.bandwidth() / 2;\n if (scale.round()) {\n offset = Math.round(offset);\n }\n\n return (d) => +scale(d) + offset;\n } else {\n return (d) => +scale(d);\n }\n }, [scale]);\n\n /**\n * Gets the x/y position for a given tick.\n */\n const getPosition = useCallback(\n (scaledTick: number) => {\n if (orientation === 'horizontal') {\n return { x: scaledTick, y: 0 };\n } else {\n return { x: 0, y: scaledTick };\n }\n },\n [orientation]\n );\n\n /**\n * Gets the dimension (height/width) this axis is calculating on.\n */\n const getDimension = useCallback(() => {\n return orientation === 'vertical' ? height : width;\n }, [height, orientation, width]);\n\n /**\n * Gets the formatted label of the tick.\n */\n const labelFormatFn = useMemo((): any => {\n if (labelProps.format) {\n return labelProps.format;\n } else if (scale.tickFormat) {\n return scale.tickFormat.apply(scale, [5]);\n } else {\n return (v) => formatValue(v);\n }\n }, [labelProps.format, scale]);\n\n /**\n * Gets the ticks given the dimensions and scales and returns\n * the text and position.\n */\n const ticks = useMemo((): ProcessedTick[] => {\n const dimension = getDimension();\n const maxTicks = getMaxTicks(tickSize, dimension);\n const ticks = getTicks(scale, tickValues, axis.type, maxTicks, interval);\n const adjustedScale = getAdjustedScale();\n const format = labelFormatFn;\n const midpoint = dimension / 2;\n\n return ticks.map((tick) => {\n const fullText = format(tick);\n const scaledTick = adjustedScale(tick);\n const position = getPosition(scaledTick);\n const text = ellipsize(fullText, 18);\n const size = label\n ? calculateDimensions(\n text,\n labelProps.fontFamily,\n labelProps.fontSize?.toString()\n )\n : {};\n\n return {\n ...position,\n ...size,\n text,\n fullText,\n half:\n scaledTick === midpoint\n ? 'center'\n : scaledTick < midpoint\n ? 'start'\n : 'end'\n };\n });\n }, [\n axis.type,\n getAdjustedScale,\n getDimension,\n getPosition,\n interval,\n label,\n labelProps,\n labelFormatFn,\n scale,\n tickSize,\n tickValues\n ]);\n\n /**\n * Calculates the rotation angle that the ticks need to be shifted to.\n * This equation will measure the length of the text in a external canvas\n * object and determine what the longest label is and rotate until they fit.\n */\n const angle = useMemo((): number => {\n if (!label) {\n return 0;\n }\n\n const dimension = getDimension();\n const maxTicksLength = max(ticks, (tick) => tick.width);\n let angle = 0;\n\n if (labelProps.rotation) {\n if (labelProps.rotation === true) {\n let baseWidth = maxTicksLength;\n const maxBaseWidth = Math.floor(dimension / ticks.length);\n\n while (baseWidth > maxBaseWidth && angle > -90) {\n angle -= 30;\n baseWidth = Math.cos(angle * (