未验证 提交 e698eda6 编写于 作者: P Peter Pan 提交者: GitHub

feat: pr-curve (#681)

上级 dc3bebb8
......@@ -41,7 +41,7 @@
"yargs": "15.3.1"
},
"devDependencies": {
"@types/node": "14.0.13",
"@types/node": "14.0.14",
"@types/yargs": "15.0.5",
"cross-env": "7.0.2",
"ts-node": "8.10.2",
......
......@@ -14,7 +14,7 @@ const Label = styled.div`
`;
type FieldProps = {
label?: string;
label?: string | JSX.Element;
};
const Field: FunctionComponent<FieldProps & WithStyled> = ({label, children, className}) => (
......
......@@ -55,7 +55,6 @@ type HistogramChartProps = {
tag: string;
mode: Modes;
running?: boolean;
onToggleMaximized?: (maximized: boolean) => void;
};
const HistogramChart: FunctionComponent<HistogramChartProps> = ({cid, run, tag, mode, running}) => {
......
......@@ -18,6 +18,17 @@ type LineChartProps = {
zoom?: boolean;
};
export enum XAxisType {
value = 'value',
log = 'log',
time = 'time'
}
export enum YAxisType {
value = 'value',
log = 'log'
}
export type LineChartRef = {
restore(): void;
saveAsImage(): void;
......
import LineChart, {LineChartRef} from '~/components/LineChart';
import {PRCurveData, Run, options as chartOptions, nearestPoint} from '~/resource/pr-curve';
import React, {FunctionComponent, useCallback, useMemo, useRef, useState} from 'react';
import {rem, size} from '~/utils/style';
import ChartToolbox from '~/components/ChartToolbox';
import {EChartOption} from 'echarts';
import TooltipTable from '~/components/TooltipTable';
import {cycleFetcher} from '~/utils/fetch';
import ee from '~/utils/event';
import {format} from 'd3-format';
import queryString from 'query-string';
import {renderToStaticMarkup} from 'react-dom/server';
import styled from 'styled-components';
import {useRunningRequest} from '~/hooks/useRequest';
import {useTranslation} from '~/utils/i18n';
import zip from 'lodash/zip';
const axisFormatter = format('.4f');
const valueFormatter = format('.2f');
const Wrapper = styled.div`
${size('100%', '100%')}
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: space-between;
`;
const StyledLineChart = styled(LineChart)`
flex-grow: 1;
`;
const Toolbox = styled(ChartToolbox)`
margin-left: ${rem(20)};
margin-right: ${rem(20)};
margin-bottom: ${rem(18)};
`;
const Error = styled.div`
${size('100%', '100%')}
display: flex;
justify-content: center;
align-items: center;
`;
type PRCurveChartProps = {
cid: symbol;
runs: Run[];
tag: string;
running?: boolean;
};
const PRCurveChart: FunctionComponent<PRCurveChartProps> = ({cid, runs, tag, running}) => {
const {t} = useTranslation(['pr-curve', 'common']);
const echart = useRef<LineChartRef>(null);
const {data: dataset, error, loading} = useRunningRequest<PRCurveData[]>(
runs.map(run => `/pr-curve/list?${queryString.stringify({run: run.label, tag})}`),
!!running,
(...urls) => cycleFetcher(urls)
);
const [maximized, setMaximized] = useState<boolean>(false);
const toggleMaximized = useCallback(() => {
ee.emit('toggle-chart-size', cid, !maximized);
setMaximized(m => !m);
}, [cid, maximized]);
const selectedData = useMemo<[number, number, number[][]][]>(
() =>
runs.map((run, i) => {
const [wallTime, step, ...item] = dataset?.[i]?.find(row => row[1] === run.steps[run.index]) ?? [
0,
0,
[],
[],
[],
[],
[],
[],
[]
];
return [wallTime, step, zip(...item) as number[][]];
}),
[dataset, runs]
);
const data = useMemo(
() =>
selectedData.map((item, i) => {
const run = runs[i];
return {
name: run.label,
z: i,
itemStyle: {
color: run.colors[0]
},
lineStyle: {
color: run.colors[0]
},
data: item[2],
encode: {
x: [1],
y: [0]
}
};
}),
[selectedData, runs]
);
const formatter = useCallback(
(params: EChartOption.Tooltip.Format | EChartOption.Tooltip.Format[]) => {
const series = Array.isArray(params) ? params[0].data : params.data;
const points = nearestPoint(
selectedData.map(s => s[2]),
series[1]
);
const columns = [
{
label: t('pr-curve:threshold')
},
{
label: t('pr-curve:precision')
},
{
label: t('pr-curve:recall')
},
{
label: t('pr-curve:true-positives')
},
{
label: t('pr-curve:false-positives')
},
{
label: t('pr-curve:true-negatives')
},
{
label: t('pr-curve:false-negatives')
}
];
const data = points.map(([precision, recall, tp, fp, tn, fn, threshold]) => [
valueFormatter(threshold),
axisFormatter(precision),
axisFormatter(recall),
tp,
fp,
tn,
fn
]);
return renderToStaticMarkup(
<TooltipTable run={t('common:runs')} runs={runs} columns={columns} data={data} />
);
},
[selectedData, runs, t]
);
const options = useMemo(
() => ({
...chartOptions,
tooltip: {
...chartOptions.tooltip,
formatter
}
}),
[formatter]
);
// display error only on first fetch
if (!data && error) {
return <Error>{t('common:error')}</Error>;
}
return (
<Wrapper>
<StyledLineChart ref={echart} title={tag} options={options} data={data} loading={loading} zoom />
<Toolbox
items={[
{
icon: 'maximize',
activeIcon: 'minimize',
tooltip: t('scalars:maximize'),
activeTooltip: t('scalars:minimize'),
toggle: true,
onClick: toggleMaximized
},
{
icon: 'restore-size',
tooltip: t('scalars:restore'),
onClick: () => echart.current?.restore()
},
{
icon: 'download',
tooltip: t('scalars:download-image'),
onClick: () => echart.current?.saveAsImage()
}
]}
/>
</Wrapper>
);
};
export default PRCurveChart;
import React, {FunctionComponent, useEffect, useState} from 'react';
import {Run, TimeType} from '~/resource/pr-curve';
import {ellipsis, size, textLighterColor} from '~/utils/style';
import Field from '~/components/Field';
import RangeSlider from '~/components/RangeSlider';
import {format} from 'd3-format';
import {formatTime} from '~/utils';
import styled from 'styled-components';
import {useTranslation} from '~/utils/i18n';
const relativeFormatter = format('.2f');
const TimeDisplay = styled.div`
color: ${textLighterColor};
font-size: 0.857142857em;
padding-left: 1.666666667em;
margin-bottom: 0.416666667em;
`;
const Label = styled.span<{color: string}>`
display: inline-block;
padding-left: 1.428571429em;
position: relative;
${ellipsis()}
&::before {
content: '';
display: block;
${size('0.857142857em', '0.857142857em')}
background-color: ${props => props.color};
border-radius: 50%;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
`;
const FullWidthRangeSlider = styled(RangeSlider)`
width: 100%;
`;
const typeMap = {
[TimeType.WallTime]: 'wallTimes',
[TimeType.Relative]: 'relatives',
[TimeType.Step]: 'steps'
} as const;
const formatter = {
[TimeType.WallTime]: (wallTime: number, {i18n}: ReturnType<typeof useTranslation>) =>
formatTime(wallTime, i18n.language),
[TimeType.Relative]: (relative: number) => `${relativeFormatter(relative)} ms`,
[TimeType.Step]: (step: number, {t}: ReturnType<typeof useTranslation>) => `${t('common:time-mode.step')} ${step}`
} as const;
type StepSliderProps = {
run: Run;
type: TimeType;
onChange?: (step: number) => unknown;
};
const StepSlider: FunctionComponent<StepSliderProps> = ({onChange, run, type}) => {
const translation = useTranslation('common');
const [index, setIndex] = useState(run.index);
useEffect(() => setIndex(run.index), [run.index]);
return (
<Field
label={
<Label color={run.colors[0]} title={run.label}>
{run.label}
</Label>
}
>
<TimeDisplay>
{run[typeMap[type]][index] == null ? '...' : formatter[type](run[typeMap[type]][index], translation)}
</TimeDisplay>
<FullWidthRangeSlider
min={0}
max={run.steps.length ? run.steps.length - 1 : 0}
step={1}
value={index}
onChange={setIndex}
onChangeComplete={() => onChange?.(index)}
/>
</Field>
);
};
export default StepSlider;
......@@ -18,6 +18,7 @@ const StyledAside = styled(Aside)`
overflow-y: auto;
display: flex;
flex-direction: column;
margin-bottom: 0;
.run-select {
flex: auto;
......
import LineChart, {LineChartRef} from '~/components/LineChart';
import {
Dataset,
Range,
ScalarDataset,
SortingMethod,
......@@ -14,21 +14,26 @@ import {
transform,
xAxisMap
} from '~/resource/scalars';
import LineChart, {LineChartRef, XAxisType, YAxisType} from '~/components/LineChart';
import React, {FunctionComponent, useCallback, useMemo, useRef, useState} from 'react';
import {rem, size} from '~/utils/style';
import ChartToolbox from '~/components/ChartToolbox';
import {EChartOption} from 'echarts';
import {Run} from '~/types';
import TooltipTable from '~/components/TooltipTable';
import {cycleFetcher} from '~/utils/fetch';
import ee from '~/utils/event';
import {format} from 'd3-format';
import queryString from 'query-string';
import {renderToStaticMarkup} from 'react-dom/server';
import styled from 'styled-components';
import useHeavyWork from '~/hooks/useHeavyWork';
import {useRunningRequest} from '~/hooks/useRequest';
import {useTranslation} from '~/utils/i18n';
const labelFormatter = format('.8');
const smoothWasm = () =>
import('@visualdl/wasm').then(({scalar_transform}): typeof transform => params =>
scalar_transform(params.datasets, params.smoothing)
......@@ -47,14 +52,6 @@ const Wrapper = styled.div`
flex-direction: column;
align-items: stretch;
justify-content: space-between;
.echarts td.run .run-indicator {
${size(12, 12)}
display: inline-block;
border-radius: 6px;
vertical-align: middle;
margin-right: 5px;
}
`;
const StyledLineChart = styled(LineChart)`
......@@ -74,17 +71,6 @@ const Error = styled.div`
align-items: center;
`;
enum XAxisType {
value = 'value',
log = 'log',
time = 'time'
}
enum YAxisType {
value = 'value',
log = 'log'
}
type ScalarChartProps = {
cid: symbol;
runs: Run[];
......@@ -94,7 +80,6 @@ type ScalarChartProps = {
sortingMethod: SortingMethod;
outlier?: boolean;
running?: boolean;
onToggleMaximized?: (maximized: boolean) => void;
};
const ScalarChart: FunctionComponent<ScalarChartProps> = ({
......@@ -123,7 +108,7 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
setMaximized(m => !m);
}, [cid, maximized]);
const xAxisType = useMemo(() => (xAxis === 'wall' ? XAxisType.time : XAxisType.value), [xAxis]);
const xAxisType = useMemo(() => (xAxis === XAxis.WallTime ? XAxisType.time : XAxisType.value), [xAxis]);
const [yAxisType, setYAxisType] = useState<YAxisType>(YAxisType.value);
const toggleYAxisType = useCallback(() => {
......@@ -179,13 +164,20 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
const formatter = useCallback(
(params: EChartOption.Tooltip.Format | EChartOption.Tooltip.Format[]) => {
const data = Array.isArray(params) ? params[0].data : params.data;
const step = data[1];
const points = nearestPoint(smoothedDatasets ?? [], runs, step);
const series: Dataset[number] = Array.isArray(params) ? params[0].data : params.data;
const points = nearestPoint(smoothedDatasets ?? [], runs, series[1]);
const sort = sortingMethodMap[sortingMethod];
return tooltip(sort ? sort(points, data) : points, maxStepLength, i18n);
const sorted = sort(points, series);
const {columns, data} = tooltip(
sorted.map(i => i.item),
maxStepLength,
i18n
);
return renderToStaticMarkup(
<TooltipTable run={t('common:runs')} runs={sorted.map(i => i.run)} columns={columns} data={data} />
);
},
[smoothedDatasets, runs, sortingMethod, maxStepLength, i18n]
[smoothedDatasets, runs, sortingMethod, maxStepLength, t, i18n]
);
const options = useMemo(
......@@ -201,7 +193,9 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
axisPointer: {
label: {
formatter:
xAxisType === XAxisType.time ? undefined : ({value}: {value: number}) => format('.8')(value)
xAxisType === XAxisType.time
? undefined
: ({value}: {value: number}) => labelFormatter(value)
}
}
},
......
import React, {FunctionComponent, useCallback, useEffect, useState} from 'react';
import RadioButton from '~/components/RadioButton';
import RadioGroup from '~/components/RadioGroup';
import {TimeMode} from '~/types';
import {useTranslation} from '~/utils/i18n';
const timeModes = [TimeMode.Step, TimeMode.Relative, TimeMode.WallTime] as const;
type TimeModeSelectProps = {
value: TimeMode;
onChange?: (value: TimeMode) => unknown;
};
const TimeModeSelect: FunctionComponent<TimeModeSelectProps> = ({value, onChange}) => {
const {t} = useTranslation('common');
const [timeMode, setTimeMode] = useState(value);
useEffect(() => setTimeMode(value), [value]);
const change = useCallback(
(v: TimeMode) => {
setTimeMode(v);
onChange?.(v);
},
[onChange]
);
return (
<RadioGroup value={timeMode} onChange={change}>
{timeModes.map(value => (
<RadioButton key={value} value={value}>
{t(`common:time-mode.${value}`)}
</RadioButton>
))}
</RadioGroup>
);
};
export default TimeModeSelect;
import React, {FunctionComponent} from 'react';
import {rem, size} from '~/utils/style';
import {Run} from '~/types';
import styled from 'styled-components';
const Wrapper = styled.div`
table {
border-spacing: none;
text-align: left;
table-layout: fixed;
font-size: ${rem(12)};
th,
td {
margin: 0;
> span {
display: inline-block;
}
}
th {
font-size: 1.166666667em;
font-weight: bold;
padding: 0 0.285714286em;
&.run > span {
min-width: 4.285714286em;
max-width: 12.857142857em;
}
}
td {
padding: 0 0.333333333em;
&.run-indicator > span {
${size(12, 12)}
border-radius: 6px;
vertical-align: middle;
background-color: currentColor;
}
}
}
`;
type TooltipTableProps = {
run: string;
runs: Run[];
columns: {
label: string;
width?: string;
}[];
data?: (string | number)[][];
};
const TooltipTable: FunctionComponent<TooltipTableProps> = ({run, runs, columns, data}) => {
// CANNOT use translation here
// because we use `ReactDOMServer.renderToStaticMarkup` to render this component into echarts tooltip
// `ReactDOMServer.renderToStaticMarkup` WILL NOT call hydrate so translation will never be initialized
// const {t} = useTranslation('common');
return (
<Wrapper>
<table>
<thead>
<tr>
<th className="run-indicator"></th>
<th className="run">{run}</th>
{columns.map((column, i) => (
<th key={i}>
<span style={{width: column.width ?? 'auto'}}>{column.label}</span>
</th>
))}
</tr>
</thead>
<tbody>
{data?.map((row, j) => (
<tr key={j}>
<td className="run-indicator">
<span style={{color: runs[j]?.colors[0]}}></span>
</td>
<td className="run">
<span>{runs[j]?.label}</span>
</td>
{row.map((cell, k) => (
<td key={k}>
<span>{cell}</span>
</td>
))}
</tr>
))}
</tbody>
</table>
</Wrapper>
);
};
export default TooltipTable;
......@@ -5,17 +5,35 @@ import {fetcher} from '~/utils/fetch';
import intersection from 'lodash/intersection';
import useRequest from '~/hooks/useRequest';
const allNavItems = ['scalars', 'histogram', 'samples', 'graphs', 'high-dimensional'];
enum Pages {
Scalars = 'scalars',
Histogram = 'histogram',
Samples = 'samples',
Graphs = 'graphs',
HighDimensional = 'high-dimensional',
PRCurve = 'pr-curve'
}
const pages = [
Pages.Scalars,
Pages.Histogram,
Pages.Samples,
Pages.Graphs,
Pages.HighDimensional,
Pages.PRCurve
] as const;
export const navMap = {
scalar: 'scalars',
histogram: 'histogram',
image: 'samples',
graph: 'graphs',
embeddings: 'high-dimensional'
scalar: Pages.Scalars,
histogram: Pages.Histogram,
image: Pages.Samples,
graph: Pages.Graphs,
embeddings: Pages.HighDimensional,
'pr-curve': Pages.PRCurve
} as const;
const useNavItems = () => {
const [components, setComponents] = useState<string[]>([]);
const [components, setComponents] = useState<Pages[]>([]);
const {data, mutate} = useRequest<(keyof typeof navMap)[]>('/components', fetcher, {
refreshInterval: components.length ? 61 * 1000 : 15 * 1000,
......@@ -36,7 +54,7 @@ const useNavItems = () => {
}, [mutate]);
useEffect(() => {
setComponents(intersection(allNavItems, data?.map(component => navMap[component]) ?? []));
setComponents(intersection(pages, data?.map(component => navMap[component]) ?? []));
}, [data]);
return components;
......
......@@ -32,7 +32,7 @@
"test": "echo \"Error: no test specified\" && exit 0"
},
"dependencies": {
"@tippyjs/react": "4.0.4",
"@tippyjs/react": "4.0.5",
"@visualdl/i18n": "2.0.0-beta.43",
"@visualdl/netron": "2.0.0-beta.43",
"@visualdl/wasm": "2.0.0-beta.43",
......@@ -65,13 +65,13 @@
"devDependencies": {
"@babel/core": "7.10.3",
"@types/d3-format": "1.3.1",
"@types/echarts": "4.6.2",
"@types/echarts": "4.6.3",
"@types/file-saver": "2.0.1",
"@types/lodash": "4.14.156",
"@types/lodash": "4.14.157",
"@types/mime-types": "2.1.0",
"@types/node": "14.0.13",
"@types/node": "14.0.14",
"@types/nprogress": "0.2.0",
"@types/react": "16.9.38",
"@types/react": "16.9.41",
"@types/react-dom": "16.9.8",
"@types/styled-components": "5.1.0",
"@visualdl/mock": "2.0.0-beta.43",
......
import ChartPage, {WithChart} from '~/components/ChartPage';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {Run, StepInfo, Tag, TimeType} from '~/resource/pr-curve';
import {borderColor, rem} from '~/utils/style';
import {AsideSection} from '~/components/Aside';
import Content from '~/components/Content';
import Error from '~/components/Error';
import Field from '~/components/Field';
import PRCurveChart from '~/components/PRCurvePage/PRCurveChart';
import Preloader from '~/components/Preloader';
import RunAside from '~/components/RunAside';
import StepSlider from '~/components/PRCurvePage/StepSlider';
import TimeModeSelect from '~/components/TimeModeSelect';
import Title from '~/components/Title';
import {cycleFetcher} from '~/utils/fetch';
import queryString from 'query-string';
import styled from 'styled-components';
import {useRunningRequest} from '~/hooks/useRequest';
import useTagFilter from '~/hooks/useTagFilter';
const StepSliderWrapper = styled.div`
max-height: 30vh;
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
> ${AsideSection}:last-child {
padding-bottom: ${rem(20)};
margin-bottom: 0;
}
+ .run-section {
border-top: 1px solid ${borderColor};
margin-top: 0;
padding-top: ${rem(20)};
}
`;
const PRCurve: NextI18NextPage = () => {
const {t} = useTranslation(['pr-curve', 'common']);
const [running, setRunning] = useState(true);
const {runs, tags, selectedRuns, onChangeRuns, loadingRuns, loadingTags} = useTagFilter('pr-curve', running);
const [indexes, setIndexes] = useState<Record<string, number>>({});
const onChangeIndexes = useCallback(
(run: string, index: number) =>
setIndexes(indexes => ({
...indexes,
[run]: index
})),
[]
);
useEffect(
() =>
setIndexes(indexes =>
selectedRuns.reduce<typeof indexes>((m, c) => {
if (indexes[c.label] != null) {
m[c.label] = indexes[c.label];
}
return m;
}, {})
),
[selectedRuns]
);
const {data: stepInfo} = useRunningRequest<StepInfo[]>(
selectedRuns.map(run => `/pr-curve/steps?${queryString.stringify({run: run.label})}`),
!!running,
(...urls) => cycleFetcher(urls)
);
const runWithInfo = useMemo<Run[]>(
() =>
selectedRuns.map((run, i) => ({
...run,
index: indexes[run.label] ?? (stepInfo?.[i].length ?? 1) - 1,
steps: stepInfo?.[i].map(j => j[1]) ?? [],
wallTimes: stepInfo?.[i].map(j => Math.floor(j[0] * 1000)) ?? [],
relatives: stepInfo?.[i].map(j => (j[0] - stepInfo[i][0][0]) * 1000) ?? []
})),
[selectedRuns, stepInfo, indexes]
);
const [timeType, setTimeType] = useState<TimeType>(TimeType.Step);
const prCurveTags = useMemo<Tag[]>(
() =>
tags.map(tag => ({
...tag,
runs: tag.runs.map(run => ({
...run,
index: 0,
steps: [] as Run['steps'],
wallTimes: [] as Run['wallTimes'],
relatives: [] as Run['relatives'],
...runWithInfo.find(r => r.label === run.label)
}))
})),
[tags, runWithInfo]
);
const aside = useMemo(
() =>
runs.length ? (
<RunAside
runs={runs}
selectedRuns={selectedRuns}
onChangeRuns={onChangeRuns}
running={running}
onToggleRunning={setRunning}
>
<AsideSection>
<Field label={t('pr-curve:time-display-type')}>
<TimeModeSelect value={timeType} onChange={setTimeType} />
</Field>
</AsideSection>
<StepSliderWrapper>
{runWithInfo.map(run => (
<AsideSection key={run.label}>
<StepSlider
run={run}
type={timeType}
onChange={index => onChangeIndexes(run.label, index)}
/>
</AsideSection>
))}
</StepSliderWrapper>
</RunAside>
) : null,
[t, onChangeRuns, running, runs, selectedRuns, timeType, runWithInfo, onChangeIndexes]
);
const withChart = useCallback<WithChart<Tag>>(
({label, runs, ...args}) => <PRCurveChart runs={runs} tag={label} {...args} running={running} />,
[running]
);
return (
<>
<Preloader url="/runs" />
<Preloader url="/scalars/tags" />
<Title>{t('common:pr-curve')}</Title>
<Content aside={aside} loading={loadingRuns}>
{!loadingRuns && !runs.length ? (
<Error />
) : (
<ChartPage items={prCurveTags} withChart={withChart} loading={loadingRuns || loadingTags} />
)}
</Content>
</>
);
};
PRCurve.getInitialProps = () => ({
namespacesRequired: ['pr-curve', 'common']
});
export default PRCurve;
import ChartPage, {WithChart} from '~/components/ChartPage';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import React, {useCallback, useMemo, useState} from 'react';
import {SortingMethod, XAxis, sortingMethod as toolTipSortingValues, xAxis as xAxisValues} from '~/resource/scalars';
import {SortingMethod, XAxis, sortingMethod as toolTipSortingValues} from '~/resource/scalars';
import {AsideSection} from '~/components/Aside';
import Checkbox from '~/components/Checkbox';
......@@ -9,13 +9,12 @@ import Content from '~/components/Content';
import Error from '~/components/Error';
import Field from '~/components/Field';
import Preloader from '~/components/Preloader';
import RadioButton from '~/components/RadioButton';
import RadioGroup from '~/components/RadioGroup';
import RunAside from '~/components/RunAside';
import ScalarChart from '~/components/ScalarsPage/ScalarChart';
import Select from '~/components/Select';
import Slider from '~/components/Slider';
import {Tag} from '~/types';
import TimeModeSelect from '~/components/TimeModeSelect';
import Title from '~/components/Title';
import {rem} from '~/utils/style';
import styled from 'styled-components';
......@@ -42,7 +41,7 @@ const Scalars: NextI18NextPage = () => {
const [smoothing, setSmoothing] = useState(0.6);
const [xAxis, setXAxis] = useState<XAxis>(xAxisValues[0]);
const [xAxis, setXAxis] = useState<XAxis>(XAxis.Step);
const [tooltipSorting, setTooltipSorting] = useState<SortingMethod>(toolTipSortingValues[0]);
......@@ -81,13 +80,7 @@ const Scalars: NextI18NextPage = () => {
</AsideSection>
<AsideSection>
<Field label={t('scalars:x-axis')}>
<RadioGroup value={xAxis} onChange={setXAxis}>
{xAxisValues.map(value => (
<RadioButton key={value} value={value}>
{t(`scalars:x-axis-value.${value}`)}
</RadioButton>
))}
</RadioGroup>
<TimeModeSelect value={xAxis} onChange={setXAxis} />
</Field>
</AsideSection>
</RunAside>
......
......@@ -9,6 +9,7 @@
"histogram": "Histogram",
"loading": "Please wait while loading data",
"next-page": "Next Page",
"pr-curve": "PR-Curve",
"previous-page": "Prev Page",
"run": "Run",
"running": "Running",
......@@ -27,6 +28,11 @@
"stop": "Stop",
"stop-realtime-refresh": "Stop realtime refresh",
"stopped": "Stopped",
"time-mode": {
"relative": "Relative",
"step": "Step",
"wall": "Wall Time"
},
"total-page": "{{count}} page, jump to",
"total-page_plural": "{{count}} pages, jump to",
"unselected-empty": "Nothing selected. <1/>Please select display data from right side."
......
{
"false-negatives": "FN",
"false-positives": "FP",
"precision": "Precision",
"recall": "Recall",
"threshold": "Threshold",
"time-display-type": "Time Display Type",
"true-negatives": "TN",
"true-positives": "TP"
}
......@@ -15,10 +15,5 @@
"nearest": "Nearest"
},
"value": "Value",
"x-axis": "X-Axis",
"x-axis-value": {
"relative": "Relative",
"step": "Step",
"wall": "Wall Time"
}
"x-axis": "X-Axis"
}
......@@ -9,6 +9,7 @@
"histogram": "直方图",
"loading": "数据载入中,请稍等",
"next-page": "下一页",
"pr-curve": "PR曲线",
"previous-page": "上一页",
"run": "运行",
"running": "运行中",
......@@ -27,6 +28,11 @@
"stop": "停止",
"stop-realtime-refresh": "停止实时数据刷新",
"stopped": "已停止",
"time-mode": {
"relative": "Relative",
"step": "Step",
"wall": "Wall Time"
},
"total-page": "共 {{count}} 页,跳转至",
"total-page_plural": "共 {{count}} 页,跳转至",
"unselected-empty": "未选中任何数据<1/>请在右侧操作栏选择要展示的数据"
......
{
"false-negatives": "FN",
"false-positives": "FP",
"precision": "Precision",
"recall": "Recall",
"threshold": "Threshold",
"time-display-type": "时间显示类型",
"true-negatives": "TN",
"true-positives": "TP"
}
......@@ -15,10 +15,5 @@
"nearest": "最近"
},
"value": "Value",
"x-axis": "X轴",
"x-axis-value": {
"relative": "Relative",
"step": "Step",
"wall": "Wall Time"
}
"x-axis": "X轴"
}
export const options = {
legend: {
data: []
},
tooltip: {
position: ['10%', '100%']
},
xAxis: {
min: 0,
max: 1
},
yAxis: {
min: 0,
max: 1,
splitNumber: 5
}
};
export const nearestPoint = (data: number[][][], recall: number): number[][] => {
return data.map(series => {
let delta = Number.POSITIVE_INFINITY;
let index = 0;
for (let i = 0; i < series.length; i++) {
if (series[i][1] - recall < Number.EPSILON) {
return series[i];
}
if (Math.abs(series[i][1] - recall) < delta) {
delta = Math.abs(series[i][1] - recall);
index = i;
}
}
return series[index];
});
};
export * from './types';
export * from './chart';
export * from './data';
import {Run as BaseRun, Tag as BaseTag, TimeMode} from '~/types';
export {TimeMode as TimeType};
type Step = number;
type WallTime = number;
type Relative = number;
type Precision = number;
type Recall = number;
type TruePositives = number;
type FalsePositives = number;
type TrueNegatives = number;
type FalseNegatives = number;
type Thresholds = number;
export type PRCurveDataItem = [
WallTime,
Step,
Precision[],
Recall[],
TruePositives[],
FalsePositives[],
TrueNegatives[],
FalseNegatives[],
Thresholds[]
];
export type PRCurveData = PRCurveDataItem[];
export interface Run extends BaseRun {
index: number;
steps: Step[];
wallTimes: WallTime[];
relatives: Relative[];
}
export type Tag = BaseTag<Run>;
export type StepInfo = [WallTime, Step][];
import {Dataset, TooltipData, XAxis} from './types';
import {Dataset, XAxis} from './types';
import {I18n} from '@visualdl/i18n';
import {Run} from '~/types';
......@@ -64,87 +64,36 @@ export const chartData = ({data, runs, xAxis}: {data: Dataset[]; runs: Run[]; xA
})
.flat();
// TODO: make it better, don't concat html
export const tooltip = (data: TooltipData[], stepLength: number, i18n: I18n) => {
const indexPropMap = {
time: 0,
step: 1,
value: 2,
smoothed: 3,
relative: 4
} as const;
const widthPropMap: Record<string, number | readonly [number, number]> = {
run: [60, 180],
time: 150,
step: Math.max(stepLength * 8, 40),
value: 60,
smoothed: 70,
relative: 60
} as const;
const translatePropMap = {
run: 'common:runs',
time: 'scalars:x-axis-value.wall',
step: 'scalars:x-axis-value.step',
value: 'scalars:value',
smoothed: 'scalars:smoothed',
relative: 'scalars:x-axis-value.relative'
} as const;
const transformedData = data.map(item => {
const data = item.item;
export const tooltip = (data: Dataset, stepLength: number, i18n: I18n) => {
return {
run: item.run,
smoothed: valueFormatter(data[indexPropMap.smoothed] ?? Number.NaN),
value: valueFormatter(data[indexPropMap.value] ?? Number.NaN),
step: data[indexPropMap.step],
time: formatTime(data[indexPropMap.time], i18n.language),
// Relative display value should take easy-read into consideration.
// Better to tranform data to 'day:hour', 'hour:minutes', 'minute: seconds' and second only.
relative: Math.floor(data[indexPropMap.relative] * 60 * 60) + 's'
} as const;
});
const renderContent = (content: string, width: number | readonly [number, number]) =>
`<div style="overflow: hidden; ${
Array.isArray(width)
? `min-width:${(width as [number, number])[0]};max-width:${(width as [number, number])[1]};`
: `width:${width as number}px;`
}">${content}</div>`;
let headerHtml = '<tr style="font-size:14px;">';
headerHtml += (Object.keys(transformedData[0]) as (keyof typeof transformedData[0])[])
.map(key => {
return `<th style="padding: 0 4px; font-weight: bold;" class="${key}">${renderContent(
i18n.t(translatePropMap[key]),
widthPropMap[key]
)}</th>`;
})
.join('');
headerHtml += '</tr>';
const content = transformedData
.map(item => {
let str = '<tr style="font-size:12px;">';
str += Object.keys(item)
.map(key => {
let content = '';
if (key === 'run') {
content += `<span class="run-indicator" style="background-color:${
item[key].colors?.[0] ?? 'transpanent'
}"></span>`;
content += `<span title="${item[key].label}">${item[key].label}</span>`;
} else {
content += item[key as keyof typeof item];
columns: [
{
label: i18n.t('scalars:smoothed'),
width: '5em'
},
{
label: i18n.t('scalars:value'),
width: '4.285714286em'
},
{
label: i18n.t('common:time-mode.step'),
width: `${Math.max(stepLength * 0.571428571, 2.857142857)}em`
},
{
label: i18n.t('common:time-mode.wall'),
width: '10.714285714em'
},
{
label: i18n.t('common:time-mode.relative'),
width: '4.285714286em'
}
return `<td style="padding: 0 4px;" class="${key}">${renderContent(
content,
widthPropMap[key as keyof typeof item]
)}</td>`;
})
.join('');
str += '</tr>';
return str;
})
.join('');
return `<table style="text-align: left;table-layout: fixed;"><thead>${headerHtml}</thead><tbody>${content}</tbody><table>`;
],
data: data.map(([time, step, value, smoothed, relative]) => [
valueFormatter(smoothed ?? Number.NaN),
valueFormatter(value ?? Number.NaN),
step,
formatTime(time, i18n.language),
Math.floor(relative * 60 * 60) + 's'
])
};
};
......@@ -103,6 +103,6 @@ export const nearestPoint = (data: Dataset[], runs: Run[], step: number) =>
}
return {
run: runs[index],
item: nearestItem || []
item: nearestItem || [0, 0, 0, 0, 0]
};
});
import {TooltipData} from './types';
import {SortingMethod as SM, TooltipData, XAxis} from './types';
import sortBy from 'lodash/sortBy';
export const xAxis = ['step', 'relative', 'wall'] as const;
export const xAxisMap = {
step: 1,
relative: 4,
wall: 0
[XAxis.Step]: 1,
[XAxis.Relative]: 4,
[XAxis.WallTime]: 0
} as const;
export const sortingMethod = ['default', 'descending', 'ascending', 'nearest'] as const;
export const sortingMethodMap = {
default: null,
descending: (points: TooltipData[]) => sortBy(points, point => point.item[3]).reverse(),
ascending: (points: TooltipData[]) => sortBy(points, point => point.item[3]),
export const sortingMethod = [SM.Default, SM.Descending, SM.Ascending, SM.Nearest] as const;
export const sortingMethodMap: Record<SM, (points: TooltipData[], data: number[]) => TooltipData[]> = {
[SM.Default]: (points: TooltipData[]) => points,
[SM.Descending]: (points: TooltipData[]) => sortBy(points, point => point.item[3]).reverse(),
[SM.Ascending]: (points: TooltipData[]) => sortBy(points, point => point.item[3]),
// Compare other ponts width the trigger point, caculate the nearest sort.
nearest: (points: TooltipData[], data: number[]) => sortBy(points, point => point.item[3] - data[2])
[SM.Nearest]: (points: TooltipData[], data: number[]) => sortBy(points, point => point.item[3] - data[2])
} as const;
export * from './types';
......
import {sortingMethodMap, xAxisMap} from './index';
import {Run} from '~/types';
import {Run, TimeMode} from '~/types';
type Value = number;
type WallTime = number;
......@@ -11,8 +9,13 @@ type Relative = number;
export type Dataset = [WallTime, Step, Value, Smoothed, Relative][];
export type ScalarDataset = [WallTime, Step, Value][];
export type XAxis = keyof typeof xAxisMap;
export type SortingMethod = keyof typeof sortingMethodMap;
export {TimeMode as XAxis};
export enum SortingMethod {
Default = 'default',
Descending = 'descending',
Ascending = 'ascending',
Nearest = 'nearest'
}
export type Range = {
min: number;
......@@ -21,5 +24,5 @@ export type Range = {
export type TooltipData = {
run: Run;
item: number[];
item: Dataset[number];
};
......@@ -3,8 +3,8 @@ export interface Run {
colors: [string, string];
}
export interface Tag {
runs: Run[];
export interface Tag<R = Run> {
runs: R[];
label: string;
}
......@@ -12,3 +12,9 @@ export interface TagWithSingleRun {
label: string;
run: Run;
}
export enum TimeMode {
Step = 'step',
Relative = 'relative',
WallTime = 'wall'
}
......@@ -50,8 +50,8 @@
"devDependencies": {
"@types/express": "4.17.6",
"@types/hoist-non-react-statics": "3.3.1",
"@types/node": "14.0.13",
"@types/react": "16.9.38",
"@types/node": "14.0.14",
"@types/react": "16.9.41",
"@types/react-dom": "16.9.8",
"typescript": "3.9.5"
},
......
export default ['embeddings', 'scalar', 'image', 'graph', 'histogram'];
export default ['embeddings', 'scalar', 'image', 'graph', 'histogram', 'pr-curve'];
此差异已折叠。
import {Request} from 'express';
export default (request: Request) => {
if (request.query.run === 'train') {
return [
[1593069993.786464, 0],
[1593069993.787353, 1],
[1593069993.7881448, 2],
[1593069993.788836, 3],
[1593069993.7894, 4],
[1593069993.790076, 5],
[1593069993.790763, 6],
[1593069993.791473, 7],
[1593069993.792149, 8],
[1593069993.792763, 9]
];
}
return [
[1593069993.5386739, 0],
[1593069993.539396, 1],
[1593069993.540066, 2],
[1593069993.540662, 3],
[1593069993.541333, 4],
[1593069993.542078, 5],
[1593069993.5431821, 6],
[1593069993.543998, 7],
[1593069993.5449128, 8],
[1593069993.54562, 9]
];
};
export default {
test: ['layer2/biases/summaries/mean', 'test/1234', 'another'],
train: [
'layer2/biases/summaries/mean',
'layer2/biases/summaries/accuracy',
'layer2/biases/summaries/cost',
'test/431',
'others'
]
};
......@@ -38,7 +38,7 @@
"devDependencies": {
"@types/express": "4.17.6",
"@types/faker": "4.1.12",
"@types/node": "14.0.13",
"@types/node": "14.0.14",
"typescript": "3.9.5"
},
"peerDependencies": {
......
......@@ -35,12 +35,12 @@
"dagre": "0.8.5",
"long": "4.0.0",
"marked": "1.1.0",
"netron": "lutzroeder/netron#9f9be2e1d0cdeb538fcddbc359f1b4138223a953",
"netron": "PeterPanZH/netron",
"pako": "1.0.11",
"protobufjs": "lutzroeder/protobuf.js#b9a9d027589356226f4704f9d77f2639f52172f3"
},
"devDependencies": {
"autoprefixer": "9.8.2",
"autoprefixer": "9.8.4",
"copy-webpack-plugin": "6.0.2",
"css-loader": "3.6.0",
"html-webpack-plugin": "4.3.0",
......
......@@ -1026,19 +1026,21 @@ view.ModelFactoryService = class {
this.register('./tflite', ['.tflite', '.lite', '.tfl', '.bin', '.pb', '.tmfile', '.h5', '.model', '.json']);
this.register('./tf', ['.pb', '.meta', '.pbtxt', '.prototxt', '.json', '.index', '.ckpt']);
this.register('./mediapipe', ['.pbtxt']);
this.register('./sklearn', ['.pkl', '.joblib', '.model', '.meta', '.pb']);
this.register('./uff', ['.uff', '.pb', '.trt', '.pbtxt', '.uff.txt']);
this.register('./sklearn', ['.pkl', '.pickle', '.joblib', '.model', '.meta', '.pb', '.pt', '.h5']);
this.register('./cntk', ['.model', '.cntk', '.cmf', '.dnn']);
this.register('./paddle', ['.paddle', '__model__']);
this.register('./paddle', ['.paddle', '.pdmodel', '__model__']);
this.register('./armnn', ['.armnn']);
this.register('./bigdl', ['.model', '.bigdl']);
this.register('./darknet', ['.cfg', '.model']);
this.register('./mnn', ['.mnn']);
this.register('./ncnn', ['.param', '.bin', '.cfg.ncnn', '.weights.ncnn']);
this.register('./tnn', ['.tnnproto', '.tnnmodel']);
this.register('./tengine', ['.tmfile']);
this.register('./barracuda', ['.nn']);
this.register('./openvino', ['.xml', '.bin']);
this.register('./flux', ['.bson']);
this.register('./chainer', ['.npz', '.h5', '.hd5', '.hdf5']);
this.register('./npz', ['.npz', '.h5', '.hd5', '.hdf5']);
this.register('./dl4j', ['.zip']);
this.register('./mlnet', ['.zip']);
}
......@@ -1300,27 +1302,21 @@ view.ModelFactoryService = class {
return Promise.reject(new ModelError('File has no content.', true));
}
const list = [
{name: 'ELF executable', value: '\x7FELF'},
{name: 'Git LFS header', value: 'version https://git-lfs.github.com/spec/v1\n'},
{name: 'Git LFS header', value: 'oid sha256:'},
{name: 'HTML markup', value: '<html>'},
{name: 'HTML markup', value: '<!DOCTYPE html>'},
{name: 'HTML markup', value: '<!DOCTYPE HTML>'},
{name: 'HTML markup', value: '\n\n\n\n\n<!DOCTYPE html>'},
{name: 'HTML markup', value: '\n\n\n\n\n\n<!DOCTYPE html>'},
{name: 'Unity metadata', value: 'fileFormatVersion:'},
{name: 'Vulkan SwiftShader ICD manifest', value: '{"file_format_version": "1.0.0", "ICD":'},
{name: 'StringIntLabelMapProto data', value: 'item {\r\n id:'},
{name: 'StringIntLabelMapProto data', value: 'item {\r\n name:'},
{name: 'StringIntLabelMapProto data', value: 'item {\n id:'},
{name: 'StringIntLabelMapProto data', value: 'item {\n name:'},
{name: 'Python source code', value: 'import sys, types, os;'}
{name: 'ELF executable', value: /^\x7FELF/},
{name: 'Git LFS header', value: /^version https:\/\/git-lfs.github.com\/spec\/v1\n/},
{name: 'Git LFS header', value: /^oid sha256:/},
{name: 'HTML markup', value: /^\s*<html>/},
{name: 'HTML markup', value: /^\s*<!DOCTYPE html>/},
{name: 'HTML markup', value: /^\s*<!DOCTYPE HTML>/},
{name: 'Unity metadata', value: /^fileFormatVersion:/},
{name: 'Vulkan SwiftShader ICD manifest', value: /^{\s*"file_format_version":\s*"1.0.0"\s*,\s*"ICD":/},
{name: 'StringIntLabelMapProto data', value: /^item\s*{\r?\n\s*id:/},
{name: 'StringIntLabelMapProto data', value: /^item\s*{\r?\n\s*name:/},
{name: 'Python source code', value: /^\s*import sys, types, os;/}
];
const text = new TextDecoder().decode(buffer.subarray(0, Math.min(1024, buffer.length)));
for (const item of list) {
if (
buffer.length >= item.value.length &&
buffer.subarray(0, item.value.length).every((v, i) => v === item.value.charCodeAt(i))
) {
if (text.match(item.value)) {
return Promise.reject(new ModelError('Invalid file content. File contains ' + item.name + '.', true));
}
}
......
......@@ -46,9 +46,9 @@
},
"devDependencies": {
"@types/express": "4.17.6",
"@types/node": "14.0.13",
"@types/node": "14.0.14",
"@types/shelljs": "0.8.8",
"@types/webpack": "4.41.17",
"@types/webpack": "4.41.18",
"@types/webpack-dev-middleware": "3.7.1",
"@visualdl/mock": "2.0.0-beta.43",
"cross-env": "7.0.2",
......
......@@ -31,7 +31,7 @@
"test": "echo \"Error: no test specified\" && exit 0"
},
"devDependencies": {
"@types/node": "14.0.13",
"@types/node": "14.0.14",
"@types/rimraf": "3.0.0",
"@visualdl/core": "2.0.0-beta.43",
"cross-env": "7.0.2",
......
......@@ -2393,10 +2393,10 @@
dependencies:
defer-to-connect "^1.0.1"
"@tippyjs/react@4.0.4":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.0.4.tgz#7ba87ff8ad27eb4d5fae6daa4217f7d4de72088d"
integrity sha512-XifxYQU9Wx52gWRkF5WNDAurKh+s0O6B+j3nLLSCnhz8HigQlHuvsr+5ISiqFpeoA5/NAgFxLi6FPLNq8DfD8g==
"@tippyjs/react@4.0.5":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.0.5.tgz#804399370d929b29bb6387b7dad9452635ee4221"
integrity sha512-Q7hv4Kkz5XlL71KP/SY8mdrfOkNkOmyQbMl2gh5fNPSzsqlPvAFbMyOP8iI0MS/sHASYGfth4+D9SzyTkrheTw==
dependencies:
tippy.js "^6.2.0"
......@@ -2430,10 +2430,10 @@
resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-1.3.1.tgz#35bf88264bd6bcda39251165bb827f67879c4384"
integrity sha512-KAWvReOKMDreaAwOjdfQMm0HjcUMlQG47GwqdVKgmm20vTd2pucj0a70c3gUSHrnsmo6H2AMrkBsZU2UhJLq8A==
"@types/echarts@4.6.2":
version "4.6.2"
resolved "https://registry.yarnpkg.com/@types/echarts/-/echarts-4.6.2.tgz#f66b8736e2a554f432d58ec0361bf9c9d908ccfc"
integrity sha512-ovishXXoibnoPTgevsmZuuoVaHGzFlvBEsGzcdldlRwxiUsL+ChOfRat5JRWv5W2zUHxkposYQw+m0aDJM+S3Q==
"@types/echarts@4.6.3":
version "4.6.3"
resolved "https://registry.yarnpkg.com/@types/echarts/-/echarts-4.6.3.tgz#227b33b106203ceb2ca4fa51760f406618caa89a"
integrity sha512-XsDsLtPDxSGPqcHvVEbH+z5r9lPiduNOf4aL3NjGprQ/4SliDiTS7DErimHpVdiEOoVFzwjn2usvgLOb1JqOJA==
dependencies:
"@types/zrender" "*"
......@@ -2510,10 +2510,10 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==
"@types/lodash@4.14.156":
version "4.14.156"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.156.tgz#cbe30909c89a1feeb7c60803e785344ea0ec82d1"
integrity sha512-l2AgHXcKUwx2DsvP19wtRPqZ4NkONjmorOdq4sMcxIjqdIuuV/ULo2ftuv4NUpevwfW7Ju/UKLqo0ZXuEt/8lQ==
"@types/lodash@4.14.157":
version "4.14.157"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.157.tgz#fdac1c52448861dfde1a2e1515dbc46e54926dc8"
integrity sha512-Ft5BNFmv2pHDgxV5JDsndOWTRJ+56zte0ZpYLowp03tW+K+t8u8YMOzAnpuqPgzX6WO1XpDIUm7u04M8vdDiVQ==
"@types/long@^4.0.0":
version "4.0.1"
......@@ -2552,10 +2552,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.5.tgz#3d03acd3b3414cf67faf999aed11682ed121f22b"
integrity sha512-90hiq6/VqtQgX8Sp0EzeIsv3r+ellbGj4URKj5j30tLlZvRUpnAe9YbYnjl3pJM93GyXU0tghHhvXHq+5rnCKA==
"@types/node@14.0.13":
version "14.0.13"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.13.tgz#ee1128e881b874c371374c1f72201893616417c9"
integrity sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA==
"@types/node@14.0.14":
version "14.0.14"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce"
integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ==
"@types/node@^10.1.0":
version "10.17.24"
......@@ -2619,10 +2619,10 @@
"@types/prop-types" "*"
csstype "^2.2.0"
"@types/react@16.9.38":
version "16.9.38"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.38.tgz#868405dace93a4095d3e054f4c4a1de7a1ac0680"
integrity sha512-pHAeZbjjNRa/hxyNuLrvbxhhnKyKNiLC6I5fRF2Zr/t/S6zS41MiyzH4+c+1I9vVfvuRt1VS2Lodjr4ZWnxrdA==
"@types/react@16.9.41":
version "16.9.41"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.41.tgz#925137ee4d2ff406a0ecf29e8e9237390844002e"
integrity sha512-6cFei7F7L4wwuM+IND/Q2cV1koQUvJ8iSV+Gwn0c3kvABZ691g7sp3hfEQHOUBJtccl1gPi+EyNjMIl9nGA0ug==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"
......@@ -2716,7 +2716,19 @@
"@types/webpack-sources" "*"
source-map "^0.6.0"
"@types/webpack@4.41.17", "@types/webpack@^4.41.8":
"@types/webpack@4.41.18":
version "4.41.18"
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.18.tgz#2945202617866ecdffa582087f1b6de04a7eed55"
integrity sha512-mQm2R8vV2BZE/qIDVYqmBVLfX73a8muwjs74SpjEyJWJxeXBbsI9L65Pcia9XfYLYWzD1c1V8m+L0p30y2N7MA==
dependencies:
"@types/anymatch" "*"
"@types/node" "*"
"@types/tapable" "*"
"@types/uglify-js" "*"
"@types/webpack-sources" "*"
source-map "^0.6.0"
"@types/webpack@^4.41.8":
version "4.41.17"
resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.17.tgz#0a69005e644d657c85b7d6ec1c826a71bebd1c93"
integrity sha512-6FfeCidTSHozwKI67gIVQQ5Mp0g4X96c2IXxX75hYEQJwST/i6NyZexP//zzMOBb+wG9jJ7oO8fk9yObP2HWAw==
......@@ -3402,14 +3414,14 @@ autobind-decorator@^1.3.4:
resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-1.4.3.tgz#4c96ffa77b10622ede24f110f5dbbf56691417d1"
integrity sha1-TJb/p3sQYi7eJPEQ9du/VmkUF9E=
autoprefixer@9.8.2:
version "9.8.2"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.2.tgz#7347396ee576b18687041bfbacd76d78e27baa56"
integrity sha512-9UwMMU8Rg7Fj0c55mbOpXrr/2WrRqoOwOlLNTyyYt+nhiyQdIBWipp5XWzt+Lge8r3DK5y+EHMc1OBf8VpZA6Q==
autoprefixer@9.8.4:
version "9.8.4"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.4.tgz#736f1012673a70fa3464671d78d41abd54512863"
integrity sha512-84aYfXlpUe45lvmS+HoAWKCkirI/sw4JK0/bTeeqgHYco3dcsOn0NqdejISjptsYwNji/21dnkDri9PsYKk89A==
dependencies:
browserslist "^4.12.0"
caniuse-lite "^1.0.30001084"
kleur "^4.0.1"
caniuse-lite "^1.0.30001087"
colorette "^1.2.0"
normalize-range "^0.1.2"
num2fraction "^1.2.2"
postcss "^7.0.32"
......@@ -4012,10 +4024,10 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001043:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001064.tgz#a0f49689119ba08943b09968e118faf3f645add0"
integrity sha512-hdBcQMFvJIrOhkpAZiRXz04Cmetwc9NekeuNl0qZfHOugxOhJKxsjF1RmISMPFjIF4PPx1reliIzbfN42EiQ5A==
caniuse-lite@^1.0.30001084:
version "1.0.30001087"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001087.tgz#4a0bdc5998a114fcf8b7954e7ba6c2c29831c54a"
integrity sha512-KAQRGtt+eGCQBSp2iZTQibdCf9oe6cNTi5lmpsW38NnxP4WMYzfU6HCRmh4kJyh6LrTM9/uyElK4xcO93kafpg==
caniuse-lite@^1.0.30001087:
version "1.0.30001088"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001088.tgz#23a6b9e192106107458528858f2c0e0dba0d9073"
integrity sha512-6eYUrlShRYveyqKG58HcyOfPgh3zb2xqs7NvT2VVtP3hEUeeWvc3lqhpeMTxYWBBeeaT9A4bKsrtjATm66BTHg==
caseless@~0.12.0:
version "0.12.0"
......@@ -4330,6 +4342,11 @@ color@^3.0.0:
color-convert "^1.9.1"
color-string "^1.5.2"
colorette@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.0.tgz#45306add826d196e8c87236ac05d797f25982e63"
integrity sha512-soRSroY+OF/8OdA3PTQXwaDJeMc7TfknKKrxeSCencL2a4+Tx5zhxmmv7hdpCjhKBjehzp8+bwe/T68K0hpIjw==
columnify@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb"
......@@ -8098,11 +8115,6 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
kleur@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.0.1.tgz#3d4948534b666e2578f93b6fafb62108e64f05ef"
integrity sha512-Qs6SqCLm63rd0kNVh+wO4XsWLU6kgfwwaPYsLiClWf0Tewkzsa6MvB21bespb8cz+ANS+2t3So1ge3gintzhlw==
latest-version@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face"
......@@ -9051,9 +9063,9 @@ netmask@^1.0.6:
resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=
netron@lutzroeder/netron#9f9be2e1d0cdeb538fcddbc359f1b4138223a953:
version "4.1.9"
resolved "https://codeload.github.com/lutzroeder/netron/tar.gz/9f9be2e1d0cdeb538fcddbc359f1b4138223a953"
netron@PeterPanZH/netron:
version "4.3.5"
resolved "https://codeload.github.com/PeterPanZH/netron/tar.gz/2c04a5d19b18ec09ddb9087f99177e7d27f156bd"
dependencies:
d3 "5.16.0"
dagre "0.8.5"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册