Skip to content

Commit

Permalink
Better labels for graphs (#1348)
Browse files Browse the repository at this point in the history
* demo for etc instance

* manage different ticks with the same label

* refactoring

* update screenshots
  • Loading branch information
tom2drum authored Nov 14, 2023
1 parent ec765ed commit b5363b2
Show file tree
Hide file tree
Showing 24 changed files with 231 additions and 139 deletions.
4 changes: 2 additions & 2 deletions deploy/values/review/values.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ frontend:
NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json
NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/goerli.svg
NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/goerli.svg
NEXT_PUBLIC_API_HOST: blockscout-main.k8s-dev.blockscout.com
NEXT_PUBLIC_STATS_API_HOST: https://stats-test.k8s-dev.blockscout.com/
NEXT_PUBLIC_API_HOST: etc.blockscout.com
NEXT_PUBLIC_STATS_API_HOST: https://stats-etc.k8s.blockscout.com/
NEXT_PUBLIC_VISUALIZE_API_HOST: http://visualizer-svc.visualizer-testing.svc.cluster.local/
NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 16 additions & 13 deletions ui/home/indicators/ChainIndicatorChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import React from 'react';

import type { TimeChartData } from 'ui/shared/chart/types';

import useClientRect from 'lib/hooks/useClientRect';
import ChartArea from 'ui/shared/chart/ChartArea';
import ChartLine from 'ui/shared/chart/ChartLine';
import ChartOverlay from 'ui/shared/chart/ChartOverlay';
import ChartTooltip from 'ui/shared/chart/ChartTooltip';
import useTimeChartController from 'ui/shared/chart/useTimeChartController';
import calculateInnerSize from 'ui/shared/chart/utils/calculateInnerSize';

interface Props {
data: TimeChartData;
Expand All @@ -22,26 +20,31 @@ const ChainIndicatorChart = ({ data }: Props) => {
const overlayRef = React.useRef<SVGRectElement>(null);
const lineColor = useToken('colors', 'blue.500');

const [ rect, ref ] = useClientRect<SVGSVGElement>();
const { innerWidth, innerHeight } = calculateInnerSize(rect, CHART_MARGIN);
const { xScale, yScale } = useTimeChartController({
const axesConfig = React.useMemo(() => {
return {
x: { ticks: 4 },
y: { ticks: 3, nice: true },
};
}, [ ]);

const { rect, ref, axis, innerWidth, innerHeight } = useTimeChartController({
data,
width: innerWidth,
height: innerHeight,
margin: CHART_MARGIN,
axesConfig,
});

return (
<svg width="100%" height="100%" ref={ ref } cursor="pointer">
<g transform={ `translate(${ CHART_MARGIN?.left || 0 },${ CHART_MARGIN?.top || 0 })` } opacity={ rect ? 1 : 0 }>
<ChartArea
data={ data[0].items }
xScale={ xScale }
yScale={ yScale }
xScale={ axis.x.scale }
yScale={ axis.y.scale }
/>
<ChartLine
data={ data[0].items }
xScale={ xScale }
yScale={ yScale }
xScale={ axis.x.scale }
yScale={ axis.y.scale }
stroke={ lineColor }
animation="left"
strokeWidth={ 3 }
Expand All @@ -51,8 +54,8 @@ const ChainIndicatorChart = ({ data }: Props) => {
anchorEl={ overlayRef.current }
width={ innerWidth }
height={ innerHeight }
xScale={ xScale }
yScale={ yScale }
xScale={ axis.x.scale }
yScale={ axis.y.scale }
data={ data }
/>
</ChartOverlay>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 47 additions & 31 deletions ui/shared/chart/ChartWidgetGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { useToken } from '@chakra-ui/react';
import * as d3 from 'd3';
import React, { useEffect, useMemo } from 'react';
import React from 'react';

import type { ChartMargin, TimeChartItem } from 'ui/shared/chart/types';

import dayjs from 'lib/date/dayjs';
import useClientRect from 'lib/hooks/useClientRect';
import useIsMobile from 'lib/hooks/useIsMobile';
import ChartArea from 'ui/shared/chart/ChartArea';
import ChartAxis from 'ui/shared/chart/ChartAxis';
Expand All @@ -15,7 +14,6 @@ import ChartOverlay from 'ui/shared/chart/ChartOverlay';
import ChartSelectionX from 'ui/shared/chart/ChartSelectionX';
import ChartTooltip from 'ui/shared/chart/ChartTooltip';
import useTimeChartController from 'ui/shared/chart/useTimeChartController';
import calculateInnerSize from 'ui/shared/chart/utils/calculateInnerSize';

interface Props {
isEnlarged?: boolean;
Expand All @@ -31,44 +29,62 @@ interface Props {
const MAX_SHOW_ITEMS = 100_000_000_000;
const DEFAULT_CHART_MARGIN = { bottom: 20, left: 40, right: 20, top: 10 };

const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title, margin, units }: Props) => {
const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title, margin: marginProps, units }: Props) => {
const isMobile = useIsMobile();
const color = useToken('colors', 'blue.200');
const overlayRef = React.useRef<SVGRectElement>(null);
const chartId = `chart-${ title.split(' ').join('') }-${ isEnlarged ? 'fullscreen' : 'small' }`;

const [ rect, ref ] = useClientRect<SVGSVGElement>();
const chartMargin = { ...DEFAULT_CHART_MARGIN, ...margin };
const { innerWidth, innerHeight } = calculateInnerSize(rect, chartMargin);
const overlayRef = React.useRef<SVGRectElement>(null);

const chartId = `chart-${ title.split(' ').join('') }-${ isEnlarged ? 'fullscreen' : 'small' }`;
const [ range, setRange ] = React.useState<[ Date, Date ]>([ items[0].date, items[items.length - 1].date ]);

const rangedItems = useMemo(() =>
const rangedItems = React.useMemo(() =>
items.filter((item) => item.date >= range[0] && item.date <= range[1]),
[ items, range ]);
const isGroupedValues = rangedItems.length > MAX_SHOW_ITEMS;

const displayedData = useMemo(() => {
const displayedData = React.useMemo(() => {
if (isGroupedValues) {
return groupChartItemsByWeekNumber(rangedItems);
} else {
return rangedItems;
}
}, [ isGroupedValues, rangedItems ]);

const chartData = React.useMemo(() => ([ { items: displayedData, name: 'Value', color, units } ]), [ color, displayedData, units ]);

const { xTickFormat, yTickFormat, xScale, yScale } = useTimeChartController({
data: [ { items: displayedData, name: title, color } ],
width: innerWidth,
height: innerHeight,
const margin: ChartMargin = React.useMemo(() => ({ ...DEFAULT_CHART_MARGIN, ...marginProps }), [ marginProps ]);
const axesConfig = React.useMemo(() => {
return {
x: {
ticks: isEnlarged ? 8 : 4,
},
y: {
ticks: isEnlarged ? 6 : 3,
nice: true,
},
};
}, [ isEnlarged ]);

const {
ref,
rect,
innerWidth,
innerHeight,
chartMargin,
axis,
} = useTimeChartController({
data: chartData,
margin,
axesConfig,
});

const handleRangeSelect = React.useCallback((nextRange: [ Date, Date ]) => {
setRange([ nextRange[0], nextRange[1] ]);
onZoom();
}, [ onZoom ]);

useEffect(() => {
React.useEffect(() => {
if (isZoomResetInitial) {
setRange([ items[0].date, items[items.length - 1].date ]);
}
Expand All @@ -80,8 +96,8 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
<g transform={ `translate(${ chartMargin?.left || 0 },${ chartMargin?.top || 0 })` }>
<ChartGridLine
type="horizontal"
scale={ yScale }
ticks={ isEnlarged ? 6 : 3 }
scale={ axis.y.scale }
ticks={ axesConfig.y.ticks }
size={ innerWidth }
disableAnimation
/>
Expand All @@ -90,34 +106,34 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
id={ chartId }
data={ displayedData }
color={ color }
xScale={ xScale }
yScale={ yScale }
xScale={ axis.x.scale }
yScale={ axis.y.scale }
/>

<ChartLine
data={ displayedData }
xScale={ xScale }
yScale={ yScale }
xScale={ axis.x.scale }
yScale={ axis.y.scale }
stroke={ color }
animation="none"
strokeWidth={ isMobile ? 1 : 2 }
/>

<ChartAxis
type="left"
scale={ yScale }
ticks={ isEnlarged ? 6 : 3 }
tickFormatGenerator={ yTickFormat }
scale={ axis.y.scale }
ticks={ axesConfig.y.ticks }
tickFormatGenerator={ axis.y.tickFormatter }
disableAnimation
/>

<ChartAxis
type="bottom"
scale={ xScale }
scale={ axis.x.scale }
transform={ `translate(0, ${ innerHeight })` }
ticks={ isMobile ? 1 : 4 }
ticks={ axesConfig.x.ticks }
anchorEl={ overlayRef.current }
tickFormatGenerator={ xTickFormat }
tickFormatGenerator={ axis.x.tickFormatter }
disableAnimation
/>

Expand All @@ -127,15 +143,15 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title
width={ innerWidth }
tooltipWidth={ isGroupedValues ? 280 : 200 }
height={ innerHeight }
xScale={ xScale }
yScale={ yScale }
xScale={ axis.x.scale }
yScale={ axis.y.scale }
data={ chartData }
/>

<ChartSelectionX
anchorEl={ overlayRef.current }
height={ innerHeight }
scale={ xScale }
scale={ axis.x.scale }
data={ chartData }
onSelect={ handleRangeSelect }
/>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions ui/shared/chart/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,13 @@ export interface TimeChartDataItem {
}

export type TimeChartData = Array<TimeChartDataItem>;

export interface AxisConfig {
ticks?: number;
nice?: boolean;
}

export interface AxesConfig {
x?: AxisConfig;
y?: AxisConfig;
}
Loading

0 comments on commit b5363b2

Please sign in to comment.