ScalarChart.tsx 9.6 KB
Newer Older
P
Peter Pan 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/**
 * Copyright 2020 Baidu Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

17 18 19
import type {Dataset, Range, ScalarDataset} from '~/resource/scalar';
import LineChart, {LineChartRef, XAxisType, YAxisType} from '~/components/LineChart';
import React, {FunctionComponent, useCallback, useMemo, useRef, useState} from 'react';
20
import {
P
Peter Pan 已提交
21 22
    SortingMethod,
    XAxis,
23
    axisRange,
24
    chartData,
P
Peter Pan 已提交
25
    options as chartOptions,
26
    nearestPoint,
27
    range,
28
    singlePointRange,
29 30 31 32
    sortingMethodMap,
    tooltip,
    transform,
    xAxisMap
P
Peter Pan 已提交
33
} from '~/resource/scalar';
34
import {rem, size} from '~/utils/style';
35
import type {scalar_axis_range, scalar_range, scalar_transform} from '@visualdl/wasm'; // eslint-disable-line @typescript-eslint/no-unused-vars
36

37
import ChartToolbox from '~/components/ChartToolbox';
38 39
import type {EChartOption} from 'echarts';
import type {Run} from '~/types';
P
Peter Pan 已提交
40
import TooltipTable from '~/components/TooltipTable';
41
import {cycleFetcher} from '~/utils/fetch';
42
import ee from '~/utils/event';
P
Peter Pan 已提交
43
import {format} from 'd3-format';
44
import queryString from 'query-string';
P
Peter Pan 已提交
45
import {renderToStaticMarkup} from 'react-dom/server';
46 47 48
import styled from 'styled-components';
import useHeavyWork from '~/hooks/useHeavyWork';
import {useRunningRequest} from '~/hooks/useRequest';
49 50
import {useTranslation} from 'react-i18next';
import wasm from '~/utils/wasm';
51

P
Peter Pan 已提交
52 53
const labelFormatter = format('.8');

54
const smoothWasm = () =>
55
    wasm<typeof scalar_transform>('scalar_transform').then((scalar_transform): typeof transform => params =>
P
Peter Pan 已提交
56
        scalar_transform(params.datasets, params.smoothing)
57
    );
58 59 60 61 62 63

const axisRangeWasm = () =>
    wasm<typeof scalar_axis_range>('scalar_axis_range').then((scalar_axis_range): typeof axisRange => params =>
        scalar_axis_range(params.datasets, params.outlier)
    );

64
const rangeWasm = () =>
65 66
    wasm<typeof scalar_range>('scalar_range').then((scalar_range): typeof range => params =>
        scalar_range(params.datasets)
67 68
    );

69 70
// const smoothWorker = () => new Worker('~/worker/scalar/smooth.worker.ts', {type: 'module'});
// const rangeWorker = () => new Worker('~/worker/scalar/range.worker.ts', {type: 'module'});
71

72 73 74 75 76 77 78 79
const Wrapper = styled.div`
    ${size('100%', '100%')}
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: space-between;
`;

80
const StyledLineChart = styled(LineChart)`
81 82 83 84 85 86
    flex-grow: 1;
`;

const Toolbox = styled(ChartToolbox)`
    margin-left: ${rem(20)};
    margin-right: ${rem(20)};
P
Peter Pan 已提交
87
    margin-bottom: ${rem(18)};
88 89
`;

90
const Error = styled.div`
91
    ${size('100%', '100%')}
92 93 94 95 96
    display: flex;
    justify-content: center;
    align-items: center;
`;

97
type ScalarChartProps = {
98 99
    cid: symbol;
    runs: Run[];
100 101
    tag: string;
    smoothing: number;
P
Peter Pan 已提交
102 103
    xAxis: XAxis;
    sortingMethod: SortingMethod;
104
    outlier?: boolean;
105
    smoothedOnly?: boolean;
106
    showMostValue?: boolean;
107 108 109 110
    running?: boolean;
};

const ScalarChart: FunctionComponent<ScalarChartProps> = ({
111
    cid,
112 113 114 115 116 117
    runs,
    tag,
    smoothing,
    xAxis,
    sortingMethod,
    outlier,
118
    smoothedOnly,
119
    showMostValue,
120 121
    running
}) => {
P
Peter Pan 已提交
122
    const {t, i18n} = useTranslation(['scalar', 'common']);
123

124 125
    const echart = useRef<LineChartRef>(null);

P
Peter Pan 已提交
126
    const {data: datasets, error, loading} = useRunningRequest<(ScalarDataset | null)[]>(
P
Peter Pan 已提交
127
        runs.map(run => `/scalar/list?${queryString.stringify({run: run.label, tag})}`),
128 129
        !!running,
        (...urls) => cycleFetcher(urls)
130 131
    );

132 133 134 135 136 137
    const [maximized, setMaximized] = useState<boolean>(false);
    const toggleMaximized = useCallback(() => {
        ee.emit('toggle-chart-size', cid, !maximized);
        setMaximized(m => !m);
    }, [cid, maximized]);

P
Peter Pan 已提交
138
    const xAxisType = useMemo(() => (xAxis === XAxis.WallTime ? XAxisType.time : XAxisType.value), [xAxis]);
139 140 141 142 143

    const [yAxisType, setYAxisType] = useState<YAxisType>(YAxisType.value);
    const toggleYAxisType = useCallback(() => {
        setYAxisType(t => (t === YAxisType.log ? YAxisType.value : YAxisType.log));
    }, [setYAxisType]);
144

145 146
    const transformParams = useMemo(
        () => ({
147
            datasets: datasets?.map(data => data ?? []) ?? [],
148 149 150 151
            smoothing
        }),
        [datasets, smoothing]
    );
152
    const smoothedDatasetsOrUndefined = useHeavyWork(smoothWasm, null, transform, transformParams);
153 154 155 156
    const smoothedDatasets = useMemo<NonNullable<typeof smoothedDatasetsOrUndefined>>(
        () => smoothedDatasetsOrUndefined ?? [],
        [smoothedDatasetsOrUndefined]
    );
157

158
    const axisRangeParams = useMemo(
159 160 161 162 163 164
        () => ({
            datasets: smoothedDatasets,
            outlier: !!outlier
        }),
        [smoothedDatasets, outlier]
    );
165 166 167 168
    const yRange = useHeavyWork(axisRangeWasm, null, axisRange, axisRangeParams);

    const datasetRangesParams = useMemo(() => ({datasets: smoothedDatasets}), [smoothedDatasets]);
    const datasetRanges = useHeavyWork(rangeWasm, null, range, datasetRangesParams);
169

170 171 172 173 174 175
    const ranges: Record<'x' | 'y', Range | undefined> = useMemo(() => {
        let x: Range | undefined = undefined;
        let y: Range | undefined = yRange;

        // if there is only one point, place it in the middle
        if (smoothedDatasets.length === 1 && smoothedDatasets[0].length === 1) {
176
            if ([XAxisType.value, XAxisType.log].includes(xAxisType)) {
177 178 179 180 181
                x = singlePointRange(smoothedDatasets[0][0][xAxisMap[xAxis]]);
            }
            y = singlePointRange(smoothedDatasets[0][0][2]);
        }
        return {x, y};
182
    }, [smoothedDatasets, yRange, xAxisType, xAxis]);
183

184 185
    const data = useMemo(
        () =>
186
            chartData({
187
                data: smoothedDatasets.slice(0, runs.length),
188
                ranges: showMostValue ? datasetRanges ?? [] : [],
189
                runs,
190 191
                xAxis,
                smoothedOnly
192
            }),
193
        [smoothedDatasets, datasetRanges, runs, xAxis, smoothedOnly, showMostValue]
194 195
    );

P
Peter Pan 已提交
196 197 198 199 200
    const maxStepLength = useMemo(
        () => String(Math.max(...smoothedDatasets.map(i => Math.max(...i.map(j => j[1]))))).length,
        [smoothedDatasets]
    );

201 202
    const formatter = useCallback(
        (params: EChartOption.Tooltip.Format | EChartOption.Tooltip.Format[]) => {
P
Peter Pan 已提交
203
            const series: Dataset[number] = Array.isArray(params) ? params[0].data : params.data;
P
Peter Pan 已提交
204 205
            const idx = xAxisMap[xAxis];
            const points = nearestPoint(smoothedDatasets ?? [], runs, idx, series[idx]).map((point, index) => ({
206 207 208
                ...point,
                ...datasetRanges?.[index]
            }));
209
            const sort = sortingMethodMap[sortingMethod];
P
Peter Pan 已提交
210
            const sorted = sort(points, series);
211
            const {columns, data} = tooltip(sorted, maxStepLength, i18n);
P
Peter Pan 已提交
212 213 214
            return renderToStaticMarkup(
                <TooltipTable run={t('common:runs')} runs={sorted.map(i => i.run)} columns={columns} data={data} />
            );
215
        },
P
Peter Pan 已提交
216
        [smoothedDatasets, datasetRanges, runs, sortingMethod, xAxis, maxStepLength, t, i18n]
217 218
    );

P
Peter Pan 已提交
219 220 221 222 223
    const options = useMemo(
        () => ({
            ...chartOptions,
            tooltip: {
                ...chartOptions.tooltip,
224 225 226
                formatter,
                hideDelay: 300,
                enterable: true
P
Peter Pan 已提交
227 228 229
            },
            xAxis: {
                type: xAxisType,
P
Peter Pan 已提交
230 231 232 233
                ...ranges.x,
                axisPointer: {
                    label: {
                        formatter:
P
Peter Pan 已提交
234 235 236
                            xAxisType === XAxisType.time
                                ? undefined
                                : ({value}: {value: number}) => labelFormatter(value)
P
Peter Pan 已提交
237 238
                    }
                }
P
Peter Pan 已提交
239 240 241 242 243 244 245 246 247
            },
            yAxis: {
                type: yAxisType,
                ...ranges.y
            }
        }),
        [formatter, ranges, xAxisType, yAxisType]
    );

248 249 250
    // display error only on first fetch
    if (!data && error) {
        return <Error>{t('common:error')}</Error>;
251 252
    }

253
    return (
254
        <Wrapper>
P
Peter Pan 已提交
255
            <StyledLineChart ref={echart} title={tag} options={options} data={data} loading={loading} zoom />
256 257 258 259 260
            <Toolbox
                items={[
                    {
                        icon: 'maximize',
                        activeIcon: 'minimize',
P
Peter Pan 已提交
261 262
                        tooltip: t('scalar:maximize'),
                        activeTooltip: t('scalar:minimize'),
263 264 265 266 267
                        toggle: true,
                        onClick: toggleMaximized
                    },
                    {
                        icon: 'restore-size',
P
Peter Pan 已提交
268
                        tooltip: t('scalar:restore'),
269 270 271 272
                        onClick: () => echart.current?.restore()
                    },
                    {
                        icon: 'log-axis',
P
Peter Pan 已提交
273
                        tooltip: t('scalar:toggle-log-axis'),
274 275 276 277 278
                        toggle: true,
                        onClick: toggleYAxisType
                    },
                    {
                        icon: 'download',
P
Peter Pan 已提交
279
                        tooltip: t('scalar:download-image'),
280 281 282 283 284
                        onClick: () => echart.current?.saveAsImage()
                    }
                ]}
            />
        </Wrapper>
285 286 287 288
    );
};

export default ScalarChart;