ScalarChart.tsx 6.5 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
import type {Dataset, Range, ScalarDataset} from '~/resource/scalar';
P
Peter Pan 已提交
18 19
import React, {FunctionComponent, useCallback, useMemo} from 'react';
import SChart, {DownloadDataTypes, chartSize, chartSizeInRem} from '~/components/ScalarChart';
20
import {
P
Peter Pan 已提交
21 22
    SortingMethod,
    XAxis,
23
    chartData,
24
    nearestPoint,
25
    singlePointRange,
26 27 28
    sortingMethodMap,
    tooltip,
    xAxisMap
P
Peter Pan 已提交
29
} from '~/resource/scalar';
30

P
Peter Pan 已提交
31 32
import Chart from '~/components/Chart';
import {Chart as ChartLoader} from '~/components/Loader/ChartPage';
33
import type {Run} from '~/types';
P
Peter Pan 已提交
34
import {XAxisType} from '~/components/LineChart';
35 36
import {cycleFetcher} from '~/utils/fetch';
import queryString from 'query-string';
P
Peter Pan 已提交
37
import saveFile from '~/utils/saveFile';
38 39
import styled from 'styled-components';
import {useRunningRequest} from '~/hooks/useRequest';
40
import {useTranslation} from 'react-i18next';
P
Peter Pan 已提交
41
import useWebAssembly from '~/hooks/useWebAssembly';
42

43
const Error = styled.div`
P
Peter Pan 已提交
44 45
    width: 100%;
    height: 100%;
46 47 48 49 50
    display: flex;
    justify-content: center;
    align-items: center;
`;

51
type ScalarChartProps = {
52
    runs: Run[];
53 54
    tag: string;
    smoothing: number;
P
Peter Pan 已提交
55 56
    xAxis: XAxis;
    sortingMethod: SortingMethod;
57
    outlier?: boolean;
58
    smoothedOnly?: boolean;
59
    showMostValue?: boolean;
60 61 62 63 64 65 66 67 68 69
    running?: boolean;
};

const ScalarChart: FunctionComponent<ScalarChartProps> = ({
    runs,
    tag,
    smoothing,
    xAxis,
    sortingMethod,
    outlier,
70
    smoothedOnly,
71
    showMostValue,
72 73
    running
}) => {
P
Peter Pan 已提交
74
    const {t, i18n} = useTranslation(['scalar', 'common']);
75

76 77 78 79 80
    const {
        data: datasets,
        error,
        loading
    } = useRunningRequest<(ScalarDataset | null)[]>(
P
Peter Pan 已提交
81
        runs.map(run => `/scalar/list?${queryString.stringify({run: run.label, tag})}`),
82 83
        !!running,
        (...urls) => cycleFetcher(urls)
84 85
    );

P
Peter Pan 已提交
86
    const xAxisType = useMemo(() => (xAxis === XAxis.WallTime ? XAxisType.time : XAxisType.value), [xAxis]);
87

P
Peter Pan 已提交
88 89
    const transformParams = useMemo(() => [datasets?.map(data => data ?? []) ?? [], smoothing], [datasets, smoothing]);
    const {data: smoothedDatasetsOrUndefined} = useWebAssembly<Dataset[]>('scalar_transform', transformParams);
90 91 92 93
    const smoothedDatasets = useMemo<NonNullable<typeof smoothedDatasetsOrUndefined>>(
        () => smoothedDatasetsOrUndefined ?? [],
        [smoothedDatasetsOrUndefined]
    );
94

P
Peter Pan 已提交
95 96
    const axisRangeParams = useMemo(() => [smoothedDatasets, !!outlier], [smoothedDatasets, outlier]);
    const {data: yRange} = useWebAssembly<Range>('scalar_axis_range', axisRangeParams);
97

P
Peter Pan 已提交
98 99
    const datasetRangesParams = useMemo(() => [smoothedDatasets], [smoothedDatasets]);
    const {data: datasetRanges} = useWebAssembly<Range[]>('scalar_range', datasetRangesParams);
100

101 102 103 104 105 106
    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) {
107
            if ([XAxisType.value, XAxisType.log].includes(xAxisType)) {
108 109 110 111 112
                x = singlePointRange(smoothedDatasets[0][0][xAxisMap[xAxis]]);
            }
            y = singlePointRange(smoothedDatasets[0][0][2]);
        }
        return {x, y};
113
    }, [smoothedDatasets, yRange, xAxisType, xAxis]);
114

115 116
    const data = useMemo(
        () =>
117
            chartData({
118
                data: smoothedDatasets.slice(0, runs.length),
119
                ranges: showMostValue ? datasetRanges ?? [] : [],
120
                runs,
121 122
                xAxis,
                smoothedOnly
123
            }),
124
        [smoothedDatasets, datasetRanges, runs, xAxis, smoothedOnly, showMostValue]
125 126
    );

P
Peter Pan 已提交
127 128 129 130
    const maxStepLength = useMemo(
        () => String(Math.max(...smoothedDatasets.map(i => Math.max(...i.map(j => j[1]))))).length,
        [smoothedDatasets]
    );
P
Peter Pan 已提交
131 132
    const getTooltipTableData = useCallback(
        (series: number[]) => {
P
Peter Pan 已提交
133
            const idx = xAxisMap[xAxis];
134
            const points = nearestPoint(smoothedDatasets ?? [], runs, idx, series[idx]).map(point => ({
135
                ...point,
136
                ...datasetRanges?.[runs.findIndex(run => run.label === point.run.label)]
137
            }));
138
            const sort = sortingMethodMap[sortingMethod];
P
Peter Pan 已提交
139
            const sorted = sort(points, series);
140
            const {columns, data} = tooltip(sorted, maxStepLength, i18n);
P
Peter Pan 已提交
141 142 143 144 145
            return {
                runs: sorted.map(i => i.run),
                columns,
                data
            };
146
        },
P
Peter Pan 已提交
147
        [smoothedDatasets, datasetRanges, runs, sortingMethod, xAxis, maxStepLength, i18n]
P
Peter Pan 已提交
148 149
    );

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
    const downloadData = useCallback(
        (type: keyof typeof DownloadDataTypes) => {
            saveFile(
                runs.map(
                    run =>
                        `/scalar/data?${queryString.stringify({
                            run: run.label,
                            tag,
                            type
                        })}`
                ),
                runs.map(run => `visualdl-scalar-${run.label}-${tag}.${DownloadDataTypes[type]}`),
                `visualdl-scalar-${tag}.zip`
            );
        },
        [runs, tag]
    );

168 169 170
    // display error only on first fetch
    if (!data && error) {
        return <Error>{t('common:error')}</Error>;
171 172
    }

173
    return (
P
Peter Pan 已提交
174 175 176 177 178 179 180 181 182 183
        <SChart
            title={tag}
            data={data}
            loading={loading}
            xAxisType={xAxisType}
            xRange={ranges.x}
            yRange={ranges.y}
            getTooltipTableData={getTooltipTableData}
            downloadData={downloadData}
        />
184 185 186 187
    );
};

export default ScalarChart;
P
Peter Pan 已提交
188 189 190 191 192 193 194 195 196 197 198

export const Loader: FunctionComponent = () => (
    <>
        <Chart {...chartSizeInRem}>
            <ChartLoader width={chartSize.width - 2} height={chartSize.height - 2} />
        </Chart>
        <Chart {...chartSizeInRem}>
            <ChartLoader width={chartSize.width - 2} height={chartSize.height - 2} />
        </Chart>
    </>
);