Commit 8d6429de authored by tom goriunov's avatar tom goriunov Committed by GitHub

Fix RSK stats - transaction fees precision (#1602)

* Fix RSK stats - transaction fees precision

Fixes #1473

* update screenshot
parent b2432b38
...@@ -38,7 +38,7 @@ NEXT_PUBLIC_HAS_BEACON_CHAIN=true ...@@ -38,7 +38,7 @@ NEXT_PUBLIC_HAS_BEACON_CHAIN=true
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s.blockscout.com NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s-prod-1.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
......
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.98 5.36c-.387 0-.645.258-.645.645a4.485 4.485 0 0 1-4.512 4.512h-.451c-.13 0-.194 0-.322-.064-.13 0-.194-.065-.323-.065-.064 0-.129-.064-.193-.064-.451-.129-.903-.387-1.29-.645-.064-.064-.128-.064-.128-.129-.065-.064-.13-.129-.194-.129-.129-.064-.258-.193-.322-.257l-.129-.13 1.16-.193a.669.669 0 0 0 .516-.773.669.669 0 0 0-.773-.516l-2.128.387-.515.129a.435.435 0 0 0-.387.258 1.195 1.195 0 0 0-.13.45l.517 2.708c.064.323.322.516.644.516h.13a.669.669 0 0 0 .515-.774l-.194-1.095c.13.128.323.257.452.386h.064c.065.065.129.13.193.13.13.128.258.193.452.257.129.258.322.322.515.451h.065c.064.065.193.065.322.13.258.064.516.128.71.193.128 0 .257.064.386.064h.774a5.778 5.778 0 0 0 5.801-5.801c.065-.323-.258-.58-.58-.58ZM.73 6.65c.387 0 .645-.258.645-.645a4.485 4.485 0 0 1 4.513-4.512h.45c.13 0 .194 0 .323.064.13 0 .194.065.323.065.064 0 .128.064.193.064.451.129.902.322 1.29.645.064.064.128.064.193.129.064.064.129.064.193.128.064.065.129.194.258.258l.129.13-1.16.193a.669.669 0 0 0-.516.773c.064.322.322.516.644.516h.13l2.707-.516a.669.669 0 0 0 .515-.773L11.045.526A.669.669 0 0 0 10.27.01a.669.669 0 0 0-.516.773L9.95 1.88c-.13-.129-.322-.322-.451-.386h-.065c-.064-.065-.129-.13-.193-.13-.13-.128-.258-.193-.451-.322-.194-.129-.387-.193-.58-.322h-.065C8.079.655 7.95.655 7.822.59a10.574 10.574 0 0 1-.71-.193c-.128 0-.257-.065-.386-.065h-.773A5.778 5.778 0 0 0 .15 6.134c-.064.258.194.516.58.516Z" fill="currentColor"/> <path d="M10.98 5.36c-.387 0-.645.258-.645.645a4.485 4.485 0 0 1-4.512 4.512h-.451c-.13 0-.194 0-.322-.064-.13 0-.194-.065-.323-.065-.064 0-.129-.064-.193-.064-.451-.129-.903-.387-1.29-.645-.064-.064-.128-.064-.128-.129-.065-.064-.13-.129-.194-.129-.129-.064-.258-.193-.322-.257l-.129-.13 1.16-.193a.669.669 0 0 0 .516-.773.669.669 0 0 0-.773-.516l-2.128.387-.515.129a.435.435 0 0 0-.387.258 1.195 1.195 0 0 0-.13.45l.517 2.708c.064.323.322.516.644.516h.13a.669.669 0 0 0 .515-.774l-.194-1.095c.13.128.323.257.452.386h.064c.065.065.129.13.193.13.13.128.258.193.452.257.129.258.322.322.515.451h.065c.064.065.193.065.322.13.258.064.516.128.71.193.128 0 .257.064.386.064h.774a5.778 5.778 0 0 0 5.801-5.801c.065-.323-.258-.58-.58-.58ZM.73 6.65c.387 0 .645-.258.645-.645a4.485 4.485 0 0 1 4.513-4.512h.45c.13 0 .194 0 .323.064.13 0 .194.065.323.065.064 0 .128.064.193.064.451.129.902.322 1.29.645.064.064.128.064.193.129.064.064.129.064.193.128.064.065.129.194.258.258l.129.13-1.16.193a.669.669 0 0 0-.516.773c.064.322.322.516.644.516h.13l2.707-.516a.669.669 0 0 0 .515-.773L11.045.526A.669.669 0 0 0 10.27.01a.669.669 0 0 0-.516.773L9.95 1.88c-.13-.129-.322-.322-.451-.386h-.065c-.064-.065-.129-.13-.193-.13-.13-.128-.258-.193-.451-.322C8.596.913 8.403.849 8.21.72h-.065C8.079.655 7.95.655 7.822.59a10.574 10.574 0 0 1-.71-.193c-.128 0-.257-.065-.386-.065h-.773A5.778 5.778 0 0 0 .15 6.134c-.064.258.194.516.58.516Z" fill="currentColor"/>
</svg> </svg>
...@@ -23,11 +23,11 @@ const ChainIndicatorChart = ({ data }: Props) => { ...@@ -23,11 +23,11 @@ const ChainIndicatorChart = ({ data }: Props) => {
const axesConfig = React.useMemo(() => { const axesConfig = React.useMemo(() => {
return { return {
x: { ticks: 4 }, x: { ticks: 4 },
y: { ticks: 3, nice: true }, y: { ticks: 3, nice: true, noLabel: true },
}; };
}, [ ]); }, [ ]);
const { rect, ref, axis, innerWidth, innerHeight } = useTimeChartController({ const { rect, ref, axes, innerWidth, innerHeight, chartMargin } = useTimeChartController({
data, data,
margin: CHART_MARGIN, margin: CHART_MARGIN,
axesConfig, axesConfig,
...@@ -35,16 +35,16 @@ const ChainIndicatorChart = ({ data }: Props) => { ...@@ -35,16 +35,16 @@ const ChainIndicatorChart = ({ data }: Props) => {
return ( return (
<svg width="100%" height="100%" ref={ ref } cursor="pointer"> <svg width="100%" height="100%" ref={ ref } cursor="pointer">
<g transform={ `translate(${ CHART_MARGIN?.left || 0 },${ CHART_MARGIN?.top || 0 })` } opacity={ rect ? 1 : 0 }> <g transform={ `translate(${ chartMargin.left || 0 },${ chartMargin.top || 0 })` } opacity={ rect ? 1 : 0 }>
<ChartArea <ChartArea
data={ data[0].items } data={ data[0].items }
xScale={ axis.x.scale } xScale={ axes.x.scale }
yScale={ axis.y.scale } yScale={ axes.y.scale }
/> />
<ChartLine <ChartLine
data={ data[0].items } data={ data[0].items }
xScale={ axis.x.scale } xScale={ axes.x.scale }
yScale={ axis.y.scale } yScale={ axes.y.scale }
stroke={ lineColor } stroke={ lineColor }
animation="left" animation="left"
strokeWidth={ 3 } strokeWidth={ 3 }
...@@ -54,8 +54,8 @@ const ChainIndicatorChart = ({ data }: Props) => { ...@@ -54,8 +54,8 @@ const ChainIndicatorChart = ({ data }: Props) => {
anchorEl={ overlayRef.current } anchorEl={ overlayRef.current }
width={ innerWidth } width={ innerWidth }
height={ innerHeight } height={ innerHeight }
xScale={ axis.x.scale } xScale={ axes.x.scale }
yScale={ axis.y.scale } yScale={ axes.y.scale }
data={ data } data={ data }
/> />
</ChartOverlay> </ChartOverlay>
......
...@@ -82,7 +82,7 @@ const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data, ...@@ -82,7 +82,7 @@ const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data,
.selectAll<Element, TimeChartData>('.ChartTooltip__value') .selectAll<Element, TimeChartData>('.ChartTooltip__value')
.filter((td, tIndex) => tIndex === i) .filter((td, tIndex) => tIndex === i)
.text( .text(
(data[i].valueFormatter?.(d.value) || d.value.toLocaleString()) + (data[i].valueFormatter?.(d.value) || d.value.toLocaleString(undefined, { minimumSignificantDigits: 1 })) +
(data[i].units ? ` ${ data[i].units }` : ''), (data[i].units ? ` ${ data[i].units }` : ''),
) )
.nodes(); .nodes();
......
...@@ -73,3 +73,61 @@ test('error', async({ mount }) => { ...@@ -73,3 +73,61 @@ test('error', async({ mount }) => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('small values', async({ mount, page }) => {
const modifiedProps = {
...props,
items: [
{ date: new Date('2023-02-13'), value: 0.000005041012112611958 },
{ date: new Date('2023-02-14'), value: 0.000004781545670577531 },
{ date: new Date('2023-02-15'), value: 0.00000520510604212437 },
{ date: new Date('2023-02-16'), value: 0.000005274901030625893 },
{ date: new Date('2023-02-17'), value: 0.00000534325322320271 },
{ date: new Date('2023-02-18'), value: 0.00000579140116207668 },
{ date: new Date('2023-02-19'), value: 0.000004878307079043056 },
{ date: new Date('2023-02-20'), value: 0.0000053454186920910215 },
{ date: new Date('2023-02-21'), value: 0.000005770588532081243 },
{ date: new Date('2023-02-22'), value: 0.00000589334810122426 },
{ date: new Date('2023-02-23'), value: 0.00000547040196358741 },
],
};
const component = await mount(
<TestApp>
<ChartWidget { ...modifiedProps }/>
</TestApp>,
);
await page.waitForFunction(() => {
return document.querySelector('path[data-name="chart-Nativecoincirculatingsupply-small"]')?.getAttribute('opacity') === '1';
});
await expect(component).toHaveScreenshot();
});
test('small variations in big values', async({ mount, page }) => {
const modifiedProps = {
...props,
items: [
{ date: new Date('2023-02-13'), value: 8886203 },
{ date: new Date('2023-02-14'), value: 8890184 },
{ date: new Date('2023-02-15'), value: 8893483 },
{ date: new Date('2023-02-16'), value: 8897924 },
{ date: new Date('2023-02-17'), value: 8902268 },
{ date: new Date('2023-02-18'), value: 8906320 },
{ date: new Date('2023-02-19'), value: 8910264 },
{ date: new Date('2023-02-20'), value: 8914827 },
{ date: new Date('2023-02-21'), value: 8918592 },
{ date: new Date('2023-02-22'), value: 8921988 },
{ date: new Date('2023-02-23'), value: 8922206 },
],
};
const component = await mount(
<TestApp>
<ChartWidget { ...modifiedProps }/>
</TestApp>,
);
await page.waitForFunction(() => {
return document.querySelector('path[data-name="chart-Nativecoincirculatingsupply-small"]')?.getAttribute('opacity') === '1';
});
await expect(component).toHaveScreenshot();
});
...@@ -2,7 +2,7 @@ import { useToken } from '@chakra-ui/react'; ...@@ -2,7 +2,7 @@ import { useToken } from '@chakra-ui/react';
import * as d3 from 'd3'; import * as d3 from 'd3';
import React from 'react'; import React from 'react';
import type { ChartMargin, TimeChartItem } from 'ui/shared/chart/types'; import type { ChartMargin, TimeChartData, TimeChartItem } from 'ui/shared/chart/types';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
...@@ -27,7 +27,7 @@ interface Props { ...@@ -27,7 +27,7 @@ interface Props {
// temporarily turn off the data aggregation, we need a better algorithm for that // temporarily turn off the data aggregation, we need a better algorithm for that
const MAX_SHOW_ITEMS = 100_000_000_000; const MAX_SHOW_ITEMS = 100_000_000_000;
const DEFAULT_CHART_MARGIN = { bottom: 20, left: 40, right: 20, top: 10 }; const DEFAULT_CHART_MARGIN = { bottom: 20, left: 10, right: 20, top: 10 };
const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title, margin: marginProps, units }: Props) => { const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title, margin: marginProps, units }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
...@@ -51,7 +51,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -51,7 +51,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
} }
}, [ isGroupedValues, rangedItems ]); }, [ isGroupedValues, rangedItems ]);
const chartData = React.useMemo(() => ([ { items: displayedData, name: 'Value', color, units } ]), [ color, displayedData, units ]); const chartData: TimeChartData = React.useMemo(() => ([ { items: displayedData, name: 'Value', color, units } ]), [ color, displayedData, units ]);
const margin: ChartMargin = React.useMemo(() => ({ ...DEFAULT_CHART_MARGIN, ...marginProps }), [ marginProps ]); const margin: ChartMargin = React.useMemo(() => ({ ...DEFAULT_CHART_MARGIN, ...marginProps }), [ marginProps ]);
const axesConfig = React.useMemo(() => { const axesConfig = React.useMemo(() => {
...@@ -72,7 +72,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -72,7 +72,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
innerWidth, innerWidth,
innerHeight, innerHeight,
chartMargin, chartMargin,
axis, axes,
} = useTimeChartController({ } = useTimeChartController({
data: chartData, data: chartData,
margin, margin,
...@@ -96,7 +96,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -96,7 +96,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
<g transform={ `translate(${ chartMargin?.left || 0 },${ chartMargin?.top || 0 })` }> <g transform={ `translate(${ chartMargin?.left || 0 },${ chartMargin?.top || 0 })` }>
<ChartGridLine <ChartGridLine
type="horizontal" type="horizontal"
scale={ axis.y.scale } scale={ axes.y.scale }
ticks={ axesConfig.y.ticks } ticks={ axesConfig.y.ticks }
size={ innerWidth } size={ innerWidth }
disableAnimation disableAnimation
...@@ -106,14 +106,14 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -106,14 +106,14 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
id={ chartId } id={ chartId }
data={ displayedData } data={ displayedData }
color={ color } color={ color }
xScale={ axis.x.scale } xScale={ axes.x.scale }
yScale={ axis.y.scale } yScale={ axes.y.scale }
/> />
<ChartLine <ChartLine
data={ displayedData } data={ displayedData }
xScale={ axis.x.scale } xScale={ axes.x.scale }
yScale={ axis.y.scale } yScale={ axes.y.scale }
stroke={ color } stroke={ color }
animation="none" animation="none"
strokeWidth={ isMobile ? 1 : 2 } strokeWidth={ isMobile ? 1 : 2 }
...@@ -121,19 +121,19 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -121,19 +121,19 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
<ChartAxis <ChartAxis
type="left" type="left"
scale={ axis.y.scale } scale={ axes.y.scale }
ticks={ axesConfig.y.ticks } ticks={ axesConfig.y.ticks }
tickFormatGenerator={ axis.y.tickFormatter } tickFormatGenerator={ axes.y.tickFormatter }
disableAnimation disableAnimation
/> />
<ChartAxis <ChartAxis
type="bottom" type="bottom"
scale={ axis.x.scale } scale={ axes.x.scale }
transform={ `translate(0, ${ innerHeight })` } transform={ `translate(0, ${ innerHeight })` }
ticks={ axesConfig.x.ticks } ticks={ axesConfig.x.ticks }
anchorEl={ overlayRef.current } anchorEl={ overlayRef.current }
tickFormatGenerator={ axis.x.tickFormatter } tickFormatGenerator={ axes.x.tickFormatter }
disableAnimation disableAnimation
/> />
...@@ -143,15 +143,15 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title ...@@ -143,15 +143,15 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
width={ innerWidth } width={ innerWidth }
tooltipWidth={ isGroupedValues ? 280 : 200 } tooltipWidth={ isGroupedValues ? 280 : 200 }
height={ innerHeight } height={ innerHeight }
xScale={ axis.x.scale } xScale={ axes.x.scale }
yScale={ axis.y.scale } yScale={ axes.y.scale }
data={ chartData } data={ chartData }
/> />
<ChartSelectionX <ChartSelectionX
anchorEl={ overlayRef.current } anchorEl={ overlayRef.current }
height={ innerHeight } height={ innerHeight }
scale={ axis.x.scale } scale={ axes.x.scale }
data={ chartData } data={ chartData }
onSelect={ handleRangeSelect } onSelect={ handleRangeSelect }
/> />
......
...@@ -29,6 +29,7 @@ export type TimeChartData = Array<TimeChartDataItem>; ...@@ -29,6 +29,7 @@ export type TimeChartData = Array<TimeChartDataItem>;
export interface AxisConfig { export interface AxisConfig {
ticks?: number; ticks?: number;
nice?: boolean; nice?: boolean;
noLabel?: boolean;
} }
export interface AxesConfig { export interface AxesConfig {
......
...@@ -5,7 +5,7 @@ import type { AxesConfig, ChartMargin, TimeChartData } from 'ui/shared/chart/typ ...@@ -5,7 +5,7 @@ import type { AxesConfig, ChartMargin, TimeChartData } from 'ui/shared/chart/typ
import useClientRect from 'lib/hooks/useClientRect'; import useClientRect from 'lib/hooks/useClientRect';
import calculateInnerSize from './utils/calculateInnerSize'; import calculateInnerSize from './utils/calculateInnerSize';
import { getAxisParams, DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS } from './utils/timeChartAxis'; import { getAxesParams } from './utils/timeChartAxis';
interface Props { interface Props {
data: TimeChartData; data: TimeChartData;
...@@ -19,29 +19,27 @@ export default function useTimeChartController({ data, margin, axesConfig }: Pro ...@@ -19,29 +19,27 @@ export default function useTimeChartController({ data, margin, axesConfig }: Pro
// we need to recalculate the axis scale whenever the rect width changes // we need to recalculate the axis scale whenever the rect width changes
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const axisParams = React.useMemo(() => getAxisParams(data, axesConfig), [ data, axesConfig, rect?.width ]); const axesParams = React.useMemo(() => getAxesParams(data, axesConfig), [ data, axesConfig, rect?.width ]);
const chartMargin = React.useMemo(() => { const chartMargin = React.useMemo(() => {
const exceedingDigits = (axisParams.y.labelFormatParams.maximumSignificantDigits ?? DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS) - const PIXELS_PER_DIGIT = 8;
DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS; const leftShift = axesConfig?.y?.noLabel ? 0 : PIXELS_PER_DIGIT * axesParams.y.labelFormatParams.maxLabelLength;
const PIXELS_PER_DIGIT = 7;
const leftShift = PIXELS_PER_DIGIT * exceedingDigits;
return { return {
...margin, ...margin,
left: (margin?.left ?? 0) + leftShift, left: (margin?.left ?? 0) + leftShift,
}; };
}, [ axisParams.y.labelFormatParams.maximumSignificantDigits, margin ]); }, [ axesParams.y.labelFormatParams.maxLabelLength, margin, axesConfig?.y?.noLabel ]);
const { innerWidth, innerHeight } = calculateInnerSize(rect, chartMargin); const { innerWidth, innerHeight } = calculateInnerSize(rect, chartMargin);
const xScale = React.useMemo(() => { const xScale = React.useMemo(() => {
return axisParams.x.scale.range([ 0, innerWidth ]); return axesParams.x.scale.range([ 0, innerWidth ]);
}, [ axisParams.x.scale, innerWidth ]); }, [ axesParams.x.scale, innerWidth ]);
const yScale = React.useMemo(() => { const yScale = React.useMemo(() => {
return axisParams.y.scale.range([ innerHeight, 0 ]); return axesParams.y.scale.range([ innerHeight, 0 ]);
}, [ axisParams.y.scale, innerHeight ]); }, [ axesParams.y.scale, innerHeight ]);
return React.useMemo(() => { return React.useMemo(() => {
return { return {
...@@ -50,16 +48,16 @@ export default function useTimeChartController({ data, margin, axesConfig }: Pro ...@@ -50,16 +48,16 @@ export default function useTimeChartController({ data, margin, axesConfig }: Pro
chartMargin, chartMargin,
innerWidth, innerWidth,
innerHeight, innerHeight,
axis: { axes: {
x: { x: {
tickFormatter: axisParams.x.tickFormatter, tickFormatter: axesParams.x.tickFormatter,
scale: xScale, scale: xScale,
}, },
y: { y: {
tickFormatter: axisParams.y.tickFormatter, tickFormatter: axesParams.y.tickFormatter,
scale: yScale, scale: yScale,
}, },
}, },
}; };
}, [ axisParams.x.tickFormatter, axisParams.y.tickFormatter, chartMargin, innerHeight, innerWidth, rect, ref, xScale, yScale ]); }, [ axesParams.x.tickFormatter, axesParams.y.tickFormatter, chartMargin, innerHeight, innerWidth, rect, ref, xScale, yScale ]);
} }
import * as d3 from 'd3'; import * as d3 from 'd3';
import _maxBy from 'lodash/maxBy';
import _unique from 'lodash/uniq'; import _unique from 'lodash/uniq';
import type { AxesConfig, AxisConfig, TimeChartData } from '../types'; import type { AxesConfig, AxisConfig, TimeChartData } from '../types';
...@@ -8,8 +9,15 @@ import { WEEK, MONTH, YEAR } from 'lib/consts'; ...@@ -8,8 +9,15 @@ import { WEEK, MONTH, YEAR } from 'lib/consts';
export const DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS = 2; export const DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS = 2;
export const DEFAULT_MAXIMUM_FRACTION_DIGITS = 3; export const DEFAULT_MAXIMUM_FRACTION_DIGITS = 3;
export const MAXIMUM_SIGNIFICANT_DIGITS_LIMIT = 8; export const MAXIMUM_SIGNIFICANT_DIGITS_LIMIT = 8;
export const DEFAULT_LABEL_LENGTH = 5;
export function getAxisParams(data: TimeChartData, axesConfig?: AxesConfig) { export interface LabelFormatParams extends Intl.NumberFormatOptions {
maxLabelLength: number;
}
type Data = TimeChartData;
export function getAxesParams(data: Data, axesConfig?: AxesConfig) {
const { labelFormatParams: labelFormatParamsY, scale: yScale } = getAxisParamsY(data, axesConfig?.y); const { labelFormatParams: labelFormatParamsY, scale: yScale } = getAxisParamsY(data, axesConfig?.y);
return { return {
...@@ -25,7 +33,7 @@ export function getAxisParams(data: TimeChartData, axesConfig?: AxesConfig) { ...@@ -25,7 +33,7 @@ export function getAxisParams(data: TimeChartData, axesConfig?: AxesConfig) {
}; };
} }
function getAxisParamsX(data: TimeChartData) { function getAxisParamsX(data: Data) {
const min = d3.min(data, ({ items }) => d3.min(items, ({ date }) => date)) ?? new Date(); const min = d3.min(data, ({ items }) => d3.min(items, ({ date }) => date)) ?? new Date();
const max = d3.max(data, ({ items }) => d3.max(items, ({ date }) => date)) ?? new Date(); const max = d3.max(data, ({ items }) => d3.max(items, ({ date }) => date)) ?? new Date();
const scale = d3.scaleTime().domain([ min, max ]); const scale = d3.scaleTime().domain([ min, max ]);
...@@ -53,7 +61,7 @@ const tickFormatterX = (axis: d3.Axis<d3.NumberValue>) => (d: d3.AxisDomain) => ...@@ -53,7 +61,7 @@ const tickFormatterX = (axis: d3.Axis<d3.NumberValue>) => (d: d3.AxisDomain) =>
return format(d as Date); return format(d as Date);
}; };
function getAxisParamsY(data: TimeChartData, config?: AxisConfig) { function getAxisParamsY(data: Data, config?: AxisConfig) {
const DEFAULT_TICKS_NUM = 3; const DEFAULT_TICKS_NUM = 3;
const min = d3.min(data, ({ items }) => d3.min(items, ({ value }) => value)) ?? 0; const min = d3.min(data, ({ items }) => d3.min(items, ({ value }) => value)) ?? 0;
const max = d3.max(data, ({ items }) => d3.max(items, ({ value }) => value)) ?? 0; const max = d3.max(data, ({ items }) => d3.max(items, ({ value }) => value)) ?? 0;
...@@ -72,27 +80,21 @@ function getAxisParamsY(data: TimeChartData, config?: AxisConfig) { ...@@ -72,27 +80,21 @@ function getAxisParamsY(data: TimeChartData, config?: AxisConfig) {
const getTickFormatterY = (params: Intl.NumberFormatOptions) => () => (d: d3.AxisDomain) => { const getTickFormatterY = (params: Intl.NumberFormatOptions) => () => (d: d3.AxisDomain) => {
const num = Number(d); const num = Number(d);
if (num < 1) {
// for small number there are no algorithm to format label right now
// so we set it to 3 digits after dot maximum
return num.toLocaleString(undefined, { maximumFractionDigits: 3 });
}
return num.toLocaleString(undefined, params); return num.toLocaleString(undefined, params);
}; };
function getYLabelFormatParams(ticks: Array<number>, maximumSignificantDigits = DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS): Intl.NumberFormatOptions { function getYLabelFormatParams(ticks: Array<number>, maximumSignificantDigits = DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS): LabelFormatParams {
const params = { const params = {
maximumFractionDigits: 3, maximumFractionDigits: DEFAULT_MAXIMUM_FRACTION_DIGITS,
maximumSignificantDigits, maximumSignificantDigits,
notation: 'compact' as const, notation: 'compact' as const,
}; };
const uniqTicksStr = _unique(ticks.map((tick) => tick.toLocaleString(undefined, params))); const uniqTicksStr = _unique(ticks.map((tick) => tick.toLocaleString(undefined, params)));
const maxLabelLength = _maxBy(uniqTicksStr, (items) => items.length)?.length ?? DEFAULT_LABEL_LENGTH;
if (uniqTicksStr.length === ticks.length || maximumSignificantDigits === MAXIMUM_SIGNIFICANT_DIGITS_LIMIT) { if (uniqTicksStr.length === ticks.length || maximumSignificantDigits === MAXIMUM_SIGNIFICANT_DIGITS_LIMIT) {
return params; return { ...params, maxLabelLength };
} }
return getYLabelFormatParams(ticks, maximumSignificantDigits + 1); return getYLabelFormatParams(ticks, maximumSignificantDigits + 1);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment