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

Histogram (#670)

* doc: update readme

* feat: histogram
上级 69c90307
......@@ -11,7 +11,7 @@
</p>
<p align="center">
<a href="javascript:void(0)"><img src="https://img.shields.io/badge/QQ_Group-1045783368-52B6EF?style=social&logo=tencent-qq&logoColor=000&logoWidth=20" alt="QQ Group" /></a>
<a href="https://jq.qq.com/?_wv=1027&k=TyzyVT4C" target="_blank" rel="noreferrer"><img src="https://img.shields.io/badge/QQ_Group-1045783368-52B6EF?style=social&logo=tencent-qq&logoColor=000&logoWidth=20" alt="QQ Group" /></a>
</p>
## 介绍
......@@ -245,7 +245,9 @@ app.run(logdir="./log")
## 开源贡献
VisualDL 是由 [PaddlePaddle](https://www.paddlepaddle.org/)[ECharts](https://echarts.apache.org/) 合作推出的开源项目。欢迎所有人使用,提意见以及贡献代码。
VisualDL 是由 [PaddlePaddle](https://www.paddlepaddle.org/)[ECharts](https://echarts.apache.org/) 合作推出的开源项目。
Graph 相关功能由 [Netron](https://github.com/lutzroeder/netron) 提供技术支持。
欢迎所有人使用,提意见以及贡献代码。
## 更多细节
......@@ -254,7 +256,7 @@ VisualDL 是由 [PaddlePaddle](https://www.paddlepaddle.org/) 和 [ECharts](http
## 技术交流
欢迎您加入VisualDL官方qq群:1045783368 与飞桨团队以及其他用户共同针对VisualDL进行讨论与交流。
欢迎您加入VisualDL官方QQ群:1045783368 与飞桨团队以及其他用户共同针对VisualDL进行讨论与交流。
<p align="center">
<img src="https://user-images.githubusercontent.com/48054808/82522691-c2758680-9b5c-11ea-9aee-fca994aba175.png" width="20%"/>
......
......@@ -113,6 +113,7 @@ This project is based on following projects:
- [React](https://reactjs.org/)
- [ECharts](https://echarts.apache.org/)
- [wasm-pack](https://rustwasm.github.io/wasm-pack/)
- [Netron](https://github.com/lutzroeder/netron)
## Author
<table><tr><td align="center"><a href="https://github.com/PeterPanZH"><img src="https://avatars0.githubusercontent.com/u/3366499?s=460&v=4" width="120px;" alt="PeterPanZH"/><br /><sub><b>PeterPanZH</b></sub></a></td><td align="center"><a href="https://github.com/Niandalu"><img src="https://avatars1.githubusercontent.com/u/6406875?s=460&v=4" width="120px;" alt="Niandalu"/><br /><sub><b>Niandalu</b></sub></a></td></tr></table>
......
......@@ -112,6 +112,8 @@ VisualDL 支持最新版本的 [Google Chrome](https://www.google.com/chrome/)
- [Next.js](https://nextjs.org/)
- [React](https://reactjs.org/)
- [ECharts](https://echarts.apache.org/)
- [wasm-pack](https://rustwasm.github.io/wasm-pack/)
- [Netron](https://github.com/lutzroeder/netron)
## 作者
<table><tr><td align="center"><a href="https://github.com/PeterPanZH"><img src="https://avatars0.githubusercontent.com/u/3366499?s=460&v=4" width="120px;" alt="PeterPanZH"/><br /><sub><b>PeterPanZH</b></sub></a></td><td align="center"><a href="https://github.com/Niandalu"><img src="https://avatars1.githubusercontent.com/u/6406875?s=460&v=4" width="120px;" alt="Niandalu"/><br /><sub><b>Niandalu</b></sub></a></td></tr></table>
......
import {Dimension, DivideParams, Point, Reduction, divide} from '~/resource/high-dimensional';
import {Dimension, Reduction, divide} from '~/resource/high-dimensional';
import React, {FunctionComponent, useMemo} from 'react';
import {contentHeight, primaryColor, rem} from '~/utils/style';
......@@ -10,11 +10,8 @@ import {useRunningRequest} from '~/hooks/useRequest';
import {useTranslation} from '~/utils/i18n';
const divideWasm = () =>
import('@visualdl/wasm').then(({divide}) => (params: DivideParams) =>
(divide(params.points, params.labels, !!params.visibility, params.keyword ?? '') as unknown) as [
Point[],
Point[]
]
import('@visualdl/wasm').then(({high_dimensional_divide}): typeof divide => params =>
high_dimensional_divide(params.points, params.labels, !!params.visibility, params.keyword ?? '')
);
const divideWorker = () => new Worker('~/worker/high-dimensional/divide.worker.ts', {type: 'module'});
......
import {HistogramData, Modes, OffsetData, OverlayData, options as chartOptions, transform} from '~/resource/histogram';
import LineChart, {LineChartRef} from '~/components/LineChart';
import React, {FunctionComponent, useCallback, useMemo, useRef, useState} from 'react';
import StackChart, {StackChartRef} from '~/components/StackChart';
import {rem, size} from '~/utils/style';
import ChartToolbox from '~/components/ChartToolbox';
import {EChartOption} from 'echarts';
import {Run} from '~/types';
import ee from '~/utils/event';
import queryString from 'query-string';
import styled from 'styled-components';
import useHeavyWork from '~/hooks/useHeavyWork';
import {useRunningRequest} from '~/hooks/useRequest';
import {useTranslation} from '~/utils/i18n';
const transformWasm = () =>
import('@visualdl/wasm').then(({histogram_transform}): typeof transform => params =>
histogram_transform(params.data, params.mode)
);
const transformWorker = () => new Worker('~/worker/histogram/transform.worker.ts', {type: 'module'});
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 StyledStackChart = styled(StackChart)`
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 HistogramChartProps = {
cid: symbol;
run: Run;
tag: string;
mode: Modes;
running?: boolean;
onToggleMaximized?: (maximized: boolean) => void;
};
const HistogramChart: FunctionComponent<HistogramChartProps> = ({cid, run, tag, mode, running}) => {
const {t} = useTranslation(['histogram', 'common']);
const echart = useRef<LineChartRef | StackChartRef>(null);
const {data: dataset, error, loading} = useRunningRequest<HistogramData>(
`/histogram/list?${queryString.stringify({run: run.label, tag})}`,
!!running
);
const [maximized, setMaximized] = useState<boolean>(false);
const toggleMaximized = useCallback(() => {
ee.emit('toggle-chart-size', cid, !maximized);
setMaximized(m => !m);
}, [cid, maximized]);
const title = useMemo(() => `${tag} (${run.label})`, [tag, run.label]);
const params = useMemo(
() => ({
data: dataset ?? [],
mode
}),
[dataset, mode]
);
const data = useHeavyWork(transformWasm, transformWorker, transform, params);
const chartData = useMemo(() => {
type Optional<T> = T | undefined;
if (mode === Modes.Overlay) {
return (data as Optional<OverlayData>)?.data.map(items => ({
name: `step${items[0][1]}`,
data: items,
encode: {
x: [2],
y: [3]
}
}));
}
if (mode === Modes.Offset) {
return {
...((data as Optional<OffsetData>) ?? {})
};
}
}, [data, mode]);
const options = useMemo(
() => ({
...chartOptions[mode],
color: [run.colors[0]]
}),
[mode, run]
);
const chart = useMemo(() => {
if (mode === Modes.Overlay) {
return (
<StyledLineChart
ref={echart as React.RefObject<LineChartRef>}
title={title}
data={chartData as EChartOption<EChartOption.SeriesLine>['series']}
options={options}
loading={loading}
/>
);
}
if (mode === Modes.Offset) {
return (
<StyledStackChart
ref={echart as React.RefObject<StackChartRef>}
title={title}
data={chartData as EChartOption<EChartOption.SeriesCustom>['series'] & OffsetData}
options={options}
loading={loading}
/>
);
}
return null;
}, [chartData, loading, mode, options, title]);
// display error only on first fetch
if (!data && error) {
return <Error>{t('common:error')}</Error>;
}
return (
<Wrapper>
{chart}
<Toolbox
items={[
{
icon: 'maximize',
activeIcon: 'minimize',
tooltip: t('histogram:maximize'),
activeTooltip: t('histogram:minimize'),
toggle: true,
onClick: toggleMaximized
},
{
icon: 'download',
tooltip: t('histogram:download-image'),
onClick: () => echart.current?.saveAsImage()
}
]}
/>
</Wrapper>
);
};
export default HistogramChart;
import * as chart from '~/utils/chart';
import React, {useCallback, useEffect, useImperativeHandle} from 'react';
import React, {useEffect, useImperativeHandle} from 'react';
import {WithStyled, position, primaryColor, size} from '~/utils/style';
import {EChartOption} from 'echarts';
import GridLoader from 'react-spinners/GridLoader';
import {dataURL2Blob} from '~/utils/image';
import defaultsDeep from 'lodash/defaultsDeep';
import {formatTime} from '~/utils';
import {saveAs} from 'file-saver';
import styled from 'styled-components';
import useECharts from '~/hooks/useECharts';
import {useTranslation} from '~/utils/i18n';
......@@ -28,23 +27,12 @@ const Wrapper = styled.div`
}
`;
type Range = {
min: EChartOption.BasicComponents.CartesianAxis['min'];
max: EChartOption.BasicComponents.CartesianAxis['max'];
};
type LineChartProps = {
options?: EChartOption;
title?: string;
legend?: string[];
data?: Partial<NonNullable<EChartOption<EChartOption.SeriesLine>['series']>>;
xAxis?: string;
yAxis?: string;
xType?: EChartOption.BasicComponents.CartesianAxis.Type;
yType?: EChartOption.BasicComponents.CartesianAxis.Type;
xRange?: Range;
yRange?: Range;
tooltip?: string | EChartOption.Tooltip.Formatter;
loading?: boolean;
zoom?: boolean;
};
export type LineChartRef = {
......@@ -53,12 +41,12 @@ export type LineChartRef = {
};
const LineChart = React.forwardRef<LineChartRef, LineChartProps & WithStyled>(
({title, legend, data, xAxis, yAxis, xType, yType, xRange, yRange, tooltip, loading, className}, ref) => {
({options, data, title, loading, zoom, className}, ref) => {
const {i18n} = useTranslation();
const {ref: echartRef, echart, wrapper} = useECharts<HTMLDivElement>({
const {ref: echartRef, echart, wrapper, saveAsImage} = useECharts<HTMLDivElement>({
loading: !!loading,
zoom: true,
zoom,
autoFit: true
});
......@@ -69,68 +57,62 @@ const LineChart = React.forwardRef<LineChartRef, LineChartProps & WithStyled>(
});
},
saveAsImage: () => {
if (echart) {
const blob = dataURL2Blob(echart.getDataURL({type: 'png', pixelRatio: 2, backgroundColor: '#FFF'}));
saveAs(blob, `${title?.replace(/[/\\?%*:|"<>]/g, '_') || 'scalar'}.png`);
}
saveAsImage(title);
}
}));
const xAxisFormatter = useCallback(
(value: number) => (xType === 'time' ? formatTime(value, i18n.language, 'LTS') : value),
[xType, i18n.language]
);
useEffect(() => {
if (process.browser) {
echart?.setOption(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {color, colorAlt, ...defaults} = chart;
let chartOptions: EChartOption = defaultsDeep(
{
color: chart.color,
title: {
...chart.title,
text: title ?? ''
},
tooltip: {
...chart.tooltip,
...(tooltip
? {
formatter: tooltip
}
: {})
},
toolbox: chart.toolbox,
legend: {
...chart.legend,
data: legend ?? []
},
grid: chart.grid,
xAxis: {
...chart.xAxis,
name: xAxis || '',
type: xType || 'value',
axisLabel: {
...chart.xAxis.axisLabel,
formatter: xAxisFormatter
},
...(xRange || {})
series: data?.map(item =>
defaultsDeep(
{
// show symbol if there is only one point
showSymbol: (item?.data?.length ?? 0) <= 1,
type: 'line'
},
item,
chart.series
)
)
},
options,
defaults
);
if ((chartOptions?.xAxis as EChartOption.XAxis).type === 'time') {
chartOptions = defaultsDeep(
{
xAxis: {
axisLabel: {
formatter: (value: number) => formatTime(value, i18n.language, 'LTS')
}
}
},
yAxis: {
...chart.yAxis,
name: yAxis || '',
type: yType || 'value',
...(yRange || {})
chartOptions
);
}
if ((chartOptions?.yAxis as EChartOption.YAxis).type === 'time') {
chartOptions = defaultsDeep(
{
yAxis: {
axisLabel: {
formatter: (value: number) => formatTime(value, i18n.language, 'LTS')
}
}
},
series: data?.map(item => ({
...chart.series,
// show symbol if there is only one point
showSymbol: (item?.data?.length ?? 0) <= 1,
...item
}))
} as EChartOption,
{notMerge: true}
);
chartOptions
);
}
echart?.setOption(chartOptions, {notMerge: true});
}
}, [data, title, legend, xAxis, yAxis, xType, yType, xAxisFormatter, xRange, yRange, tooltip, echart]);
}, [options, data, title, i18n.language, echart]);
return (
<Wrapper ref={wrapper} className={className}>
......
import {
Dataset,
Range,
RangeParams,
TransformParams,
SortingMethod,
XAxis,
chartData,
options as chartOptions,
nearestPoint,
range,
singlePointRange,
......@@ -28,12 +29,12 @@ import {useRunningRequest} from '~/hooks/useRequest';
import {useTranslation} from '~/utils/i18n';
const smoothWasm = () =>
import('@visualdl/wasm').then(({transform}) => (params: TransformParams) =>
(transform(params.datasets, params.smoothing) as unknown) as Dataset[]
import('@visualdl/wasm').then(({scalar_transform}): typeof transform => params =>
scalar_transform(params.datasets, params.smoothing)
);
const rangeWasm = () =>
import('@visualdl/wasm').then(({range}) => (params: RangeParams) =>
(range(params.datasets, params.outlier) as unknown) as Range
import('@visualdl/wasm').then(({scalar_range}): typeof range => params =>
scalar_range(params.datasets, params.outlier)
);
const smoothWorker = () => new Worker('~/worker/scalars/smooth.worker.ts', {type: 'module'});
......@@ -88,8 +89,8 @@ type ScalarChartProps = {
runs: Run[];
tag: string;
smoothing: number;
xAxis: keyof typeof xAxisMap;
sortingMethod: keyof typeof sortingMethodMap;
xAxis: XAxis;
sortingMethod: SortingMethod;
outlier?: boolean;
running?: boolean;
onToggleMaximized?: (maximized: boolean) => void;
......@@ -115,7 +116,6 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
(...urls) => cycleFetcher(urls)
);
const smooth = false;
const [maximized, setMaximized] = useState<boolean>(false);
const toggleMaximized = useCallback(() => {
ee.emit('toggle-chart-size', cid, !maximized);
......@@ -166,10 +166,9 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
chartData({
data: smoothedDatasets.slice(0, runs.length),
runs,
smooth,
xAxis
}),
[smoothedDatasets, runs, smooth, xAxis]
[smoothedDatasets, runs, xAxis]
);
const formatter = useCallback(
......@@ -183,6 +182,25 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
[smoothedDatasets, runs, sortingMethod, i18n]
);
const options = useMemo(
() => ({
...chartOptions,
tooltip: {
...chartOptions.tooltip,
formatter
},
xAxis: {
type: xAxisType,
...ranges.x
},
yAxis: {
type: yAxisType,
...ranges.y
}
}),
[formatter, ranges, xAxisType, yAxisType]
);
// display error only on first fetch
if (!data && error) {
return <Error>{t('common:error')}</Error>;
......@@ -190,17 +208,7 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
return (
<Wrapper>
<StyledLineChart
ref={echart}
title={tag}
xRange={ranges.x}
yRange={ranges.y}
xType={xAxisType}
yType={yAxisType}
tooltip={formatter}
data={data}
loading={loading}
/>
<StyledLineChart ref={echart} title={tag} options={options} data={data} loading={loading} zoom />
<Toolbox
items={[
{
......
import * as chart from '~/utils/chart';
import React, {useCallback, useEffect, useImperativeHandle} from 'react';
import {WithStyled, position, primaryColor, size} from '~/utils/style';
import {EChartOption} from 'echarts';
import GridLoader from 'react-spinners/GridLoader';
import {dataURL2Blob} from '~/utils/image';
import defaultsDeep from 'lodash/defaultsDeep';
import {saveAs} from 'file-saver';
import styled from 'styled-components';
import useECharts from '~/hooks/useECharts';
import {useTranslation} from '~/utils/i18n';
const Wrapper = styled.div`
position: relative;
> .echarts {
height: 100%;
}
> .loading {
${size('100%')}
${position('absolute', 0, null, null, 0)}
display: flex;
justify-content: center;
align-items: center;
}
`;
type renderItem = NonNullable<EChartOption.SeriesCustom['renderItem']>;
type renderItemArguments = NonNullable<renderItem['arguments']>;
type RenderItem = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
params: any,
api: Required<NonNullable<renderItemArguments['api']>>
) => NonNullable<renderItem['return']>;
type GetValue = (i: number) => number;
type GetCoord = (p: [number, number]) => [number, number];
type StackChartProps = {
options?: EChartOption;
title?: string;
data?: Partial<NonNullable<EChartOption<EChartOption.SeriesCustom>['series']>[number]> & {
minZ: number;
maxZ: number;
minX: number;
maxX: number;
minStep: number;
maxStep: number;
};
loading?: boolean;
zoom?: boolean;
};
export type StackChartRef = {
saveAsImage(): void;
};
const StackChart = React.forwardRef<StackChartRef, StackChartProps & WithStyled>(
({options, data, title, loading, zoom, className}, ref) => {
const {i18n} = useTranslation();
const {ref: echartRef, echart, wrapper, saveAsImage} = useECharts<HTMLDivElement>({
loading: !!loading,
zoom,
autoFit: true
});
useImperativeHandle(ref, () => ({
saveAsImage: () => {
saveAsImage(title);
}
}));
const {minZ, maxZ, minStep, maxStep, minX, maxX, ...seriesData} = data ?? {
minZ: 0,
maxZ: 0,
minStep: 0,
maxStep: 0,
minX: 0,
maxX: 0,
data: null
};
const rawData = (seriesData.data as number[][]) ?? [];
const getPoint = useCallback(
(x: number, y: number, z: number, getCoord: GetCoord, yValueMapHeight: number) => {
const pt = getCoord([x, y]);
// linear map in z axis
pt[1] -= ((z - minZ) / (maxZ - minZ)) * yValueMapHeight;
return pt;
},
[minZ, maxZ]
);
const makePolyPoints = useCallback(
(dataIndex: number, getValue: GetValue, getCoord: GetCoord, yValueMapHeight: number) => {
const points = [];
let i = 0;
while (rawData[dataIndex] && i < rawData[dataIndex].length) {
const x = getValue(i++);
const y = getValue(i++);
const z = getValue(i++);
points.push(getPoint(x, y, z, getCoord, yValueMapHeight));
}
return points;
},
[getPoint, rawData]
);
useEffect(() => {
if (process.browser) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {color, colorAlt, ...defaults} = chart;
const chartOptions: EChartOption = defaultsDeep(
{
title: {
text: title ?? ''
},
visualMap: {
min: minStep,
max: maxStep
},
xAxis: {
min: minX,
max: maxX
},
grid: {
top: '40%'
},
tooltip: {
axisPointer: {
axis: 'y',
snap: false
}
},
series: [
{
type: 'custom',
dimensions: ['x', 'y'],
data: rawData as number[][],
renderItem: ((params, api) => {
const points = makePolyPoints(
params.dataIndex as number,
api.value as GetValue,
api.coord as GetCoord,
(params.coordSys.y as number) - 10
);
return {
type: 'polygon',
silent: true,
shape: {
points
},
style: api.style({
stroke: chart.xAxis.axisLine.lineStyle.color,
lineWidth: 1
})
};
}) as RenderItem
}
]
},
options,
defaults
);
echart?.setOption(chartOptions, {notMerge: true});
}
}, [options, data, title, i18n.language, echart, rawData, minX, maxX, minStep, maxStep, makePolyPoints]);
return (
<Wrapper ref={wrapper} className={className}>
{!echart && (
<div className="loading">
<GridLoader color={primaryColor} size="10px" />
</div>
)}
<div className="echarts" ref={echartRef}></div>
</Wrapper>
);
}
);
export default StackChart;
......@@ -2,6 +2,8 @@ import {MutableRefObject, useCallback, useEffect, useLayoutEffect, useRef, useSt
import {maskColor, primaryColor, textColor} from '~/utils/style';
import {ECharts} from 'echarts';
import {dataURL2Blob} from '~/utils/image';
import {saveAs} from 'file-saver';
const useECharts = <T extends HTMLElement, W extends HTMLElement = HTMLDivElement>(options: {
loading?: boolean;
......@@ -12,6 +14,7 @@ const useECharts = <T extends HTMLElement, W extends HTMLElement = HTMLDivElemen
ref: MutableRefObject<T | null>;
wrapper: MutableRefObject<W | null>;
echart: ECharts | null;
saveAsImage: (filename?: string) => void;
} => {
const ref = useRef<T | null>(null);
const echartInstance = useRef<ECharts | null>(null);
......@@ -82,7 +85,17 @@ const useECharts = <T extends HTMLElement, W extends HTMLElement = HTMLDivElemen
}
}, [options.autoFit]);
return {ref, echart, wrapper};
const saveAsImage = useCallback(
(filename?: string) => {
if (echart) {
const blob = dataURL2Blob(echart.getDataURL({type: 'png', pixelRatio: 2, backgroundColor: '#FFF'}));
saveAs(blob, `${filename?.replace(/[/\\?%*:|"<>]/g, '_') || 'chart'}.png`);
}
},
[echart]
);
return {ref, echart, wrapper, saveAsImage};
};
export default useECharts;
......@@ -5,9 +5,10 @@ import {fetcher} from '~/utils/fetch';
import intersection from 'lodash/intersection';
import useRequest from '~/hooks/useRequest';
const allNavItems = ['scalars', 'samples', 'graphs', 'high-dimensional'];
const allNavItems = ['scalars', 'histogram', 'samples', 'graphs', 'high-dimensional'];
export const navMap = {
scalar: 'scalars',
histogram: 'histogram',
image: 'samples',
graph: 'graphs',
embeddings: 'high-dimensional'
......
......@@ -37,6 +37,7 @@
"@visualdl/netron": "2.0.0-beta.43",
"@visualdl/wasm": "2.0.0-beta.43",
"bignumber.js": "9.0.0",
"d3-format": "1.4.4",
"echarts": "4.8.0",
"echarts-gl": "1.1.1",
"eventemitter3": "4.0.4",
......@@ -52,7 +53,6 @@
"query-string": "6.13.1",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-hooks-worker": "0.9.0",
"react-input-range": "1.3.0",
"react-is": "16.13.1",
"react-spinners": "0.8.3",
......@@ -64,6 +64,7 @@
},
"devDependencies": {
"@babel/core": "7.10.2",
"@types/d3-format": "1.3.1",
"@types/echarts": "4.6.1",
"@types/file-saver": "2.0.1",
"@types/lodash": "4.14.155",
......
import ChartPage, {WithChart} from '~/components/ChartPage';
import {Modes, modes} from '~/resource/histogram';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import React, {useCallback, useMemo, useState} from 'react';
import {AsideSection} from '~/components/Aside';
import Content from '~/components/Content';
import Error from '~/components/Error';
import Field from '~/components/Field';
import HistogramChart from '~/components/HistogramPage/HistogramChart';
import Preloader from '~/components/Preloader';
import RadioButton from '~/components/RadioButton';
import RadioGroup from '~/components/RadioGroup';
import RunAside from '~/components/RunAside';
import {TagWithSingleRun} from '~/types';
import Title from '~/components/Title';
import useTagFilter from '~/hooks/useTagFilter';
const Histogram: NextI18NextPage = () => {
const {t} = useTranslation(['histogram', 'common']);
const [running, setRunning] = useState(true);
const {runs, tags, selectedRuns, onChangeRuns, loadingRuns, loadingTags} = useTagFilter('histogram', running);
const tagsWithSingleRun = useMemo(
() =>
tags.reduce<(TagWithSingleRun & {id: string})[]>((result, tag) => {
result.push(...tag.runs.map(run => ({id: `${tag.label}-${run.label}`, label: tag.label, run})));
return result;
}, []),
[tags]
);
const [mode, setMode] = useState<Modes>(Modes.Offset);
const aside = useMemo(
() =>
runs.length ? (
<RunAside
runs={runs}
selectedRuns={selectedRuns}
onChangeRuns={onChangeRuns}
running={running}
onToggleRunning={setRunning}
>
<AsideSection>
<Field label={t('histogram:mode')}>
<RadioGroup value={mode} onChange={setMode}>
{modes.map(value => (
<RadioButton key={value} value={value}>
{t(`histogram:mode-value.${value}`)}
</RadioButton>
))}
</RadioGroup>
</Field>
</AsideSection>
</RunAside>
) : null,
[t, mode, onChangeRuns, running, runs, selectedRuns]
);
const withChart = useCallback<WithChart<TagWithSingleRun>>(
({label, run, ...args}) => <HistogramChart run={run} tag={label} {...args} mode={mode} running={running} />,
[running, mode]
);
return (
<>
<Preloader url="/runs" />
<Preloader url="/histogram/tags" />
<Title>{t('common:histogram')}</Title>
<Content aside={aside} loading={loadingRuns}>
{!loadingRuns && !runs.length ? (
<Error />
) : (
<ChartPage items={tagsWithSingleRun} withChart={withChart} loading={loadingRuns || loadingTags} />
)}
</Content>
</>
);
};
Histogram.getInitialProps = () => ({
namespacesRequired: ['histogram', 'common']
});
export default Histogram;
import ChartPage, {WithChart} from '~/components/ChartPage';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import React, {useCallback, useMemo, useState} from 'react';
import {sortingMethodMap, xAxisMap} from '~/resource/scalars';
import {SortingMethod, XAxis, sortingMethod as toolTipSortingValues, xAxis as xAxisValues} from '~/resource/scalars';
import {AsideSection} from '~/components/Aside';
import Checkbox from '~/components/Checkbox';
......@@ -21,11 +21,6 @@ import {rem} from '~/utils/style';
import styled from 'styled-components';
import useTagFilter from '~/hooks/useTagFilter';
type XAxis = keyof typeof xAxisMap;
const xAxisValues = ['step', 'relative', 'wall'] as const;
type TooltipSorting = keyof typeof sortingMethodMap;
const toolTipSortingValues = ['default', 'descending', 'ascending', 'nearest'] as const;
const TooltipSortingDiv = styled.div`
margin-top: ${rem(20)};
display: flex;
......@@ -49,7 +44,7 @@ const Scalars: NextI18NextPage = () => {
const [xAxis, setXAxis] = useState<XAxis>(xAxisValues[0]);
const [tooltipSorting, setTooltipSorting] = useState<TooltipSorting>(toolTipSortingValues[0]);
const [tooltipSorting, setTooltipSorting] = useState<SortingMethod>(toolTipSortingValues[0]);
const [ignoreOutliers, setIgnoreOutliers] = useState(false);
......@@ -71,7 +66,7 @@ const Scalars: NextI18NextPage = () => {
<span>{t('scalars:tooltip-sorting')}</span>
<Select
list={toolTipSortingValues.map(value => ({
label: t(`tooltip-sorting-value.${value}`),
label: t(`scalars:tooltip-sorting-value.${value}`),
value
}))}
value={tooltipSorting}
......@@ -89,7 +84,7 @@ const Scalars: NextI18NextPage = () => {
<RadioGroup value={xAxis} onChange={setXAxis}>
{xAxisValues.map(value => (
<RadioButton key={value} value={value}>
{t(`x-axis-value.${value}`)}
{t(`scalars:x-axis-value.${value}`)}
</RadioButton>
))}
</RadioGroup>
......
......@@ -6,6 +6,7 @@
"error": "Error occurred",
"graphs": "Graphs",
"high-dimensional": "High Dimensional",
"histogram": "Histogram",
"loading": "Please wait while loading data",
"next-page": "Next Page",
"previous-page": "Prev Page",
......@@ -14,19 +15,19 @@
"runs": "Runs",
"samples": "Samples",
"scalars": "Scalars",
"search": "Search",
"search-empty": "Nothing found. Please try again with another word. <1/>Or you can <3>see all charts</3>.",
"search-result": "Search Result",
"search-runs": "Search runs",
"search-tags": "Search tags in RegExp",
"search": "Search",
"select": "Please Select",
"select-all": "Select All",
"select-runs": "Select Runs",
"select": "Please Select",
"start-realtime-refresh": "Start realtime refresh",
"stop-realtime-refresh": "Stop realtime refresh",
"stop": "Stop",
"stop-realtime-refresh": "Stop realtime refresh",
"stopped": "Stopped",
"total-page_plural": "{{count}} pages, jump to",
"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."
}
{
"error-with-status": "A {{statusCode}} error occurred on server",
"error-without-status": "An error occurred on the server",
"page-not-found": "Page Not Found",
"common": {
"title": "No visualized data.",
"description": "Possible reasons are:",
"1": "Log files are not generated. Please refer to <1>README</1> to create log files.",
"2": "Log files are generated but data is not written yet. Please refer to <1>VisualDL User Guide</1> to write visualized data.",
"3": "Log files are generated and data is writte. Please try to <1>Refresh</1>.",
"4": "Log files are generated but path to log directory is wrong. Please check your directory and try again."
}
"4": "Log files are generated but path to log directory is wrong. Please check your directory and try again.",
"description": "Possible reasons are:",
"title": "No visualized data."
},
"error-with-status": "A {{statusCode}} error occurred on server",
"error-without-status": "An error occurred on the server",
"page-not-found": "Page Not Found"
}
{
"change-model": "Change Model",
"model-properties": "Model Properties",
"node-properties": "Node Properties",
"node-documentation": "Documentation",
"nothing-matched": "Nothing matched",
"display-data": "Select Display Data",
"show-attributes": "Show Attributes",
"show-initializers": "Show Initializers",
"show-node-names": "Show Node Names",
"documentation": {
"attributes": "Attributes",
"examples": "Examples",
"inputs": "Inputs",
"outputs": "Outputs",
"references": "References",
"support": "Support",
"support-info": "In domain <1>{{domain}}</1> since version <3>{{since_version}}</3> at support level <5>{{support_level}}</5>.",
"type-constraints": "Type Constraints"
},
"experimental-supported-model": "Experimental supported models: ",
"experimental-supported-model-list": "TorchScript, PyTorch, Torch, ArmNN, BigDL, Chainer, CNTK, Deeplearning4j, MediaPipe, ML.NET, MNN, OpenVINO, Scikit-learn, Tengine, TensorFlow.js, TensorFlow",
"export-file": "Export File",
"export-png": "PNG",
"export-svg": "SVG",
"upload-tip": "Click or Drop file here to view neural network models",
"upload-model": "Upload Model",
"supported-model": "Supported models: ",
"experimental-supported-model": "Experimental supported models: ",
"supported-model-list": "PaddlePaddle, ONNX, Keras, Core ML, Caffe, Caffe2, Darknet, MXNet, ncnn, TensorFlow Lite",
"experimental-supported-model-list": "TorchScript, PyTorch, Torch, ArmNN, BigDL, Chainer, CNTK, Deeplearning4j, MediaPipe, ML.NET, MNN, OpenVINO, Scikit-learn, Tengine, TensorFlow.js, TensorFlow",
"model-properties": "Model Properties",
"node-documentation": "Documentation",
"node-properties": "Node Properties",
"nothing-matched": "Nothing matched",
"properties": {
"format": "Format",
"producer": "Producer",
"source": "Source",
"name": "Name",
"version": "Version",
"description": "Description",
"attributes": "Attributes",
"author": "Author",
"company": "Company",
"license": "License",
"description": "Description",
"domain": "Domain",
"format": "Format",
"imports": "Imports",
"runtime": "Runtime",
"type": "Type",
"tags": "Tags",
"inputs": "Inputs",
"outputs": "Outputs",
"attributes": "Attributes"
},
"documentation": {
"attributes": "Attributes",
"inputs": "Inputs",
"license": "License",
"name": "Name",
"outputs": "Outputs",
"type-constraints": "Type Constraints",
"examples": "Examples",
"references": "References",
"support": "Support",
"support-info": "In domain <1>{{domain}}</1> since version <3>{{since_version}}</3> at support level <5>{{support_level}}</5>."
"producer": "Producer",
"runtime": "Runtime",
"source": "Source",
"tags": "Tags",
"type": "Type",
"version": "Version"
},
"restore-size": "Restore Size",
"show-attributes": "Show Attributes",
"show-initializers": "Show Initializers",
"show-node-names": "Show Node Names",
"supported-model": "Supported models: ",
"supported-model-list": "PaddlePaddle, ONNX, Keras, Core ML, Caffe, Caffe2, Darknet, MXNet, ncnn, TensorFlow Lite",
"upload-model": "Upload Model",
"upload-tip": "Click or Drop file here to view neural network models",
"zoom-in": "Zoom In",
"zoom-out": "Zoom Out"
}
{
"download-image": "Download image",
"maximize": "Maximize",
"minimize": "Minimize",
"mode": "Mode",
"mode-value": {
"offset": "Offset",
"overlay": "Overlay"
}
}
......@@ -6,6 +6,7 @@
"error": "发生错误",
"graphs": "网络结构",
"high-dimensional": "高维数据映射",
"histogram": "直方图",
"loading": "数据载入中,请稍等",
"next-page": "下一页",
"previous-page": "上一页",
......@@ -14,19 +15,19 @@
"runs": "数据流",
"samples": "样本数据",
"scalars": "标量数据",
"search": "搜索",
"search-empty": "没有找到您期望的内容,你可以尝试其他搜索词<1/>或者点击<3>查看全部图表</3>",
"search-result": "搜索结果",
"search-runs": "搜索数据流",
"search-tags": "搜索标签(支持正则)",
"search": "搜索",
"select": "请选择",
"select-all": "全选",
"select-runs": "选择数据流",
"select": "请选择",
"start-realtime-refresh": "运行实时数据刷新",
"stop-realtime-refresh": "停止实时数据刷新",
"stop": "停止",
"stop-realtime-refresh": "停止实时数据刷新",
"stopped": "已停止",
"total-page_plural": "共 {{count}} 页,跳转至",
"total-page": "共 {{count}} 页,跳转至",
"total-page_plural": "共 {{count}} 页,跳转至",
"unselected-empty": "未选中任何数据<1/>请在右侧操作栏选择要展示的数据"
}
{
"error-with-status": "服务器发生了一个 {{statusCode}} 错误",
"error-without-status": "服务器发生了一个错误",
"page-not-found": "页面不存在",
"common": {
"title": "无可视化结果展示",
"description": "有以下几种可能原因,请您参考相应解决方案:",
"1": "未生成日志文件。请参考 <1>README</1> 创建日志文件。",
"2": "已生成日志文件,但尚未打点数据。请参考 <1>VisualDL使用指南</1> ,对需要进行可视化的数据进行打点记录。",
"3": "已生成文件并打点数据,请尝试 <1>刷新</1> 。",
"4": "已生成文件,但日志文件路径错误,请确保文件路径正确。"
}
"4": "已生成文件,但日志文件路径错误,请确保文件路径正确。",
"description": "有以下几种可能原因,请您参考相应解决方案:",
"title": "无可视化结果展示"
},
"error-with-status": "服务器发生了一个 {{statusCode}} 错误",
"error-without-status": "服务器发生了一个错误",
"page-not-found": "页面不存在"
}
{
"change-model": "更换模型",
"model-properties": "模型属性",
"node-properties": "节点属性",
"node-documentation": "文档",
"nothing-matched": "无匹配的内容",
"display-data": "选择展示数据",
"show-attributes": "显示参数",
"show-initializers": "显示初始化参数",
"show-node-names": "显示节点名称",
"documentation": {
"attributes": "属性",
"examples": "示例",
"inputs": "输入",
"outputs": "输出",
"references": "参考",
"support": "支持",
"support-info": "从 <3>{{since_version}}</3> 版本起域名 <1>{{domain}}</1> 的支持等级为 <5>{{support_level}}</5>。",
"type-constraints": "类型约束"
},
"experimental-supported-model": "VisualDL实验性支持:",
"experimental-supported-model-list": "TorchScript、PyTorch、Torch、 ArmNN、BigDL、Chainer、CNTK、Deeplearning4j、MediaPipe、ML.NET、MNN、OpenVINO、Scikit-learn、Tengine、TensorFlow.js、TensorFlow",
"export-file": "导出文件",
"export-png": "PNG",
"export-svg": "SVG",
"upload-tip": "点击或拖拽文件到页面上传模型,进行结构展示",
"upload-model": "上传模型",
"supported-model": "VisualDL支持:",
"experimental-supported-model": "VisualDL实验性支持:",
"supported-model-list": "PaddlePaddle、ONNX、Keras、Core ML、Caffe、Caffe2、Darknet、MXNet、ncnn、TensorFlow Lite",
"experimental-supported-model-list": "TorchScript、PyTorch、Torch、 ArmNN、BigDL、Chainer、CNTK、Deeplearning4j、MediaPipe、ML.NET、MNN、OpenVINO、Scikit-learn、Tengine、TensorFlow.js、TensorFlow",
"model-properties": "模型属性",
"node-documentation": "文档",
"node-properties": "节点属性",
"nothing-matched": "无匹配的内容",
"properties": {
"format": "格式",
"producer": "框架",
"source": "源",
"name": "名称",
"version": "版本",
"description": "描述",
"attributes": "属性",
"author": "作者",
"company": "公司",
"license": "许可证",
"description": "描述",
"domain": "域名",
"format": "格式",
"imports": "导入",
"runtime": "运行时",
"type": "类型",
"tags": "标签",
"inputs": "输入",
"outputs": "输出",
"attributes": "属性"
},
"documentation": {
"attributes": "属性",
"inputs": "输入",
"license": "许可证",
"name": "名称",
"outputs": "输出",
"type-constraints": "类型约束",
"examples": "示例",
"references": "参考",
"support": "支持",
"support-info": "从 <3>{{since_version}}</3> 版本起域名 <1>{{domain}}</1> 的支持等级为 <5>{{support_level}}</5>。"
"producer": "框架",
"runtime": "运行时",
"source": "源",
"tags": "标签",
"type": "类型",
"version": "版本"
},
"restore-size": "重置大小",
"show-attributes": "显示参数",
"show-initializers": "显示初始化参数",
"show-node-names": "显示节点名称",
"supported-model": "VisualDL支持:",
"supported-model-list": "PaddlePaddle、ONNX、Keras、Core ML、Caffe、Caffe2、Darknet、MXNet、ncnn、TensorFlow Lite",
"upload-model": "上传模型",
"upload-tip": "点击或拖拽文件到页面上传模型,进行结构展示",
"zoom-in": "放大",
"zoom-out": "缩小"
}
{
"display-all-label": "展示所有标签",
"dimension": "维度",
"2d": "二维",
"3d": "三维",
"reduction-method": "降维方法",
"dimension": "维度",
"display-all-label": "展示所有标签",
"pca": "PCA",
"reduction-method": "降维方法",
"tsne": "T-SNE"
}
{
"download-image": "下载图片",
"maximize": "最大化",
"minimize": "最小化",
"mode": "直方图模式",
"mode-value": {
"offset": "Offset",
"overlay": "Overlay"
}
}
{
"image": "图片",
"audio": "音频",
"text": "文本",
"brightness": "亮度",
"contrast": "对比度",
"download-image": "下载$t(image)",
"image": "图片",
"show-actual-size": "按真实大小展示",
"step": "Step",
"download-image": "下载$t(image)",
"brightness": "亮度",
"contrast": "对比度"
"text": "文本"
}
{
"smoothing": "平滑度",
"value": "Value",
"axis": "坐标轴",
"download-image": "下载图片",
"ignore-outliers": "图表缩放时忽略极端值",
"maximize": "最大化",
"minimize": "最小化",
"restore": "还原",
"smoothed": "Smoothed",
"x-axis": "X轴",
"x-axis-value": {
"step": "Step",
"relative": "Relative",
"wall": "Wall Time"
},
"smoothing": "平滑度",
"tooltip-sorting": "标签排序方法",
"tooltip-sorting-value": {
"ascending": "升序",
"default": "默认",
"descending": "降序",
"ascending": "升序",
"nearest": "最近"
},
"ignore-outliers": "图表缩放时忽略极端值",
"maximize": "最大化",
"minimize": "最小化",
"restore": "还原",
"axis": "坐标轴",
"download-image": "下载图片"
"value": "Value",
"x-axis": "X轴",
"x-axis-value": {
"relative": "Relative",
"step": "Step",
"wall": "Wall Time"
}
}
import {DivideParams, Point} from './types';
import {Point} from './types';
export * from './types';
......@@ -30,5 +30,14 @@ const combineLabel = (points: Point['value'][], labels: string[], visibility?: b
};
});
export const divide = ({points, keyword, labels, visibility}: DivideParams) =>
dividePoints(combineLabel(points, labels, visibility), keyword);
export const divide = ({
points,
keyword,
labels,
visibility
}: {
points: Point['value'][];
keyword?: string;
labels: string[];
visibility?: boolean;
}) => dividePoints(combineLabel(points, labels, visibility), keyword);
......@@ -6,10 +6,3 @@ export type Point = {
value: [number, number] | [number, number, number];
showing: boolean;
};
export type DivideParams = {
points: Point['value'][];
keyword?: string;
labels: string[];
visibility?: boolean;
};
import {EChartOption, VisualMap} from 'echarts';
import {Modes} from './types';
const baseOptions: EChartOption = {
legend: {
data: []
},
tooltip: {
showContent: false
}
};
export const options: Record<Modes, EChartOption> = {
overlay: {
...baseOptions,
axisPointer: {
link: [
{
xAxisIndex: 'all'
}
],
show: true,
snap: true,
triggerTooltip: true
},
yAxis: {
axisLine: {
onZero: false
}
}
},
offset: {
...baseOptions,
visualMap: ({
type: 'continuous',
show: false,
dimension: 1,
inRange: {
colorLightness: [0.5, 0.8],
colorSaturation: [0.5, 0.8]
}
} as unknown) as VisualMap.Continuous[], // Fix echarts type bug
xAxis: {
axisLine: {
onZero: false
}
},
yAxis: {
axisLine: {
onZero: false
},
inverse: true,
splitLine: {
show: false
}
}
}
};
import {HistogramData, Modes, OffsetData, OffsetDataItem, OverlayData, OverlayDataItem} from './types';
function computeHistogram(
data: {left: number; right: number; count: number}[],
min: number,
max: number,
binsNum = 30
) {
if (min === max) {
// Create bins even if all the data has a single value.
max = min * 1.1 + 1;
min = min / 1.1 - 1;
}
const stepWidth = (max - min) / binsNum;
const range: number[] = [];
for (let i = min; i < max; i += stepWidth) {
range.push(i);
}
let itemIndex = 0;
return range.map(binLeft => {
const binRight = binLeft + stepWidth;
let yValue = 0;
while (itemIndex < data.length) {
const itemRight = Math.min(max, data[itemIndex].right);
const itemLeft = Math.max(min, data[itemIndex].left);
const overlap = Math.min(itemRight, binRight) - Math.max(itemLeft, binLeft);
const count = (overlap / (itemRight - itemLeft)) * data[itemIndex].count;
if (overlap > 0) {
yValue += count;
}
// If `itemRight` is bigger than `binRight`, then this bin is
// finished and there also has data for the next bin, so don't increment
// `itemIndex`.
if (itemRight > binRight) {
break;
}
itemIndex++;
}
return {
x: binLeft,
dx: stepWidth,
y: yValue
};
});
}
export function transform({data, mode}: {data: HistogramData; mode: Modes}) {
const temp = data.map(([time, step, items]) => ({
time,
step,
min: Math.min(...items.map(item => item[0])),
max: Math.max(...items.map(item => item[1])),
items: items.map(([left, right, count]) => ({left, right, count}))
}));
const min = Math.min(...temp.map(({min}) => min));
const max = Math.max(...temp.map(({max}) => max));
const overlay = temp.map(({time, step, items}) =>
computeHistogram(items, min, max).map<OverlayDataItem>(({x, dx, y}) => [time, step, x + dx / 2, Math.floor(y)])
);
if (mode === Modes.Overlay) {
return {
min,
max,
data: overlay
} as OverlayData;
}
if (mode === Modes.Offset) {
let minStep = Infinity;
let maxStep = -Infinity;
let minZ = Infinity;
let maxZ = -Infinity;
const offset = overlay.map(items => {
const step = items[0][1];
step > maxStep && (maxStep = step);
step < minStep && (minStep = step);
return items.reduce<OffsetDataItem[]>((m, [, , x, y]) => {
y > maxZ && (maxZ = y);
y < minZ && (minZ = y);
return [...m, x, step, y];
}, []);
});
return {
minX: min,
maxX: max,
minZ,
maxZ,
minStep,
maxStep,
data: offset
} as OffsetData;
}
return undefined as never;
}
import {Modes} from './types';
export const modes = [Modes.Offset, Modes.Overlay] as const;
export * from './types';
export * from './chart';
export * from './data';
export enum Modes {
Offset = 'offset',
Overlay = 'overlay'
}
type Time = number;
type Step = number;
type Left = number;
type Right = number;
type Count = number;
type Item = [Left, Right, Count];
export type HistogramDataItem = [Time, Step, Item[]];
export type HistogramData = HistogramDataItem[];
export type OverlayDataItem = [Time, Step, number, number];
export type OverlayData = {
min: number;
max: number;
data: OverlayDataItem[][];
};
export type OffsetDataItem = number;
export type OffsetData = {
minX: number;
maxX: number;
minZ: number;
maxZ: number;
minStep: number;
maxStep: number;
data: OffsetDataItem[][];
};
import {Dataset, TooltipData, XAxis} from './types';
import {I18n} from '@visualdl/i18n';
import {Run} from '~/types';
import {format} from 'd3-format';
import {formatTime} from '~/utils';
import {xAxisMap} from './index';
const valueFormatter = format('.5');
export const options = {
legend: {
data: []
},
tooltip: {
position: ['10%', '100%']
}
};
export const chartData = ({data, runs, xAxis}: {data: Dataset[]; runs: Run[]; xAxis: XAxis}) =>
data
.map((dataset, i) => {
// smoothed data:
// [0] wall time
// [1] step
// [2] orginal value
// [3] smoothed value
// [4] relative
const name = runs[i].label;
const color = runs[i].colors[0];
const colorAlt = runs[i].colors[1];
return [
{
name,
z: i,
itemStyle: {
color: colorAlt
},
lineStyle: {
color: colorAlt
},
data: dataset,
encode: {
x: [xAxisMap[xAxis]],
y: [2]
}
},
{
name,
z: runs.length + i,
itemStyle: {
color
},
lineStyle: {
color
},
data: dataset,
encode: {
x: [xAxisMap[xAxis]],
y: [3]
}
}
];
})
.flat();
// TODO: make it better, don't concat html
export const tooltip = (data: TooltipData[], i18n: I18n) => {
const indexPropMap = {
time: 0,
step: 1,
value: 2,
smoothed: 3,
relative: 4
} as const;
const widthPropMap = {
run: [60, 180] as [number, number],
time: 150,
step: 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;
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 | [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];
}
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>`;
};
import BigNumber from 'bignumber.js';
import {Dataset} from './types';
import {Run} from '~/types';
import cloneDeep from 'lodash/cloneDeep';
import compact from 'lodash/compact';
import maxBy from 'lodash/maxBy';
import minBy from 'lodash/minBy';
import {quantile} from '~/utils';
export const transform = ({datasets, smoothing}: {datasets: Dataset[]; smoothing: number}) =>
// https://en.wikipedia.org/wiki/Moving_average
datasets.map(seriesData => {
const data = cloneDeep(seriesData);
let last = new BigNumber(data.length > 0 ? 0 : Number.NaN);
let numAccum = 0;
let startValue = 0;
const bigSmoothing = new BigNumber(smoothing);
data.forEach((d, i) => {
const nextVal = new BigNumber(d[2]);
// second to millisecond.
const millisecond = (d[0] = Math.floor(d[0] * 1000));
if (i === 0) {
startValue = millisecond;
}
// relative time, millisecond to hours.
d[4] = Math.floor(millisecond - startValue) / (60 * 60 * 1000);
if (!nextVal.isFinite()) {
d[3] = nextVal.toNumber();
} else {
// last = last * smoothing + (1 - smoothing) * nextVal;
last = last.multipliedBy(bigSmoothing).plus(bigSmoothing.minus(1).negated().multipliedBy(nextVal));
numAccum++;
let debiasWeight = new BigNumber(1);
if (!bigSmoothing.isEqualTo(1)) {
//debiasWeight = 1.0 - Math.pow(smoothing, numAccum);
debiasWeight = bigSmoothing.exponentiatedBy(numAccum).minus(1).negated();
}
// d[3] = last / debiasWeight;
d[3] = last.dividedBy(debiasWeight).toNumber();
}
});
return data;
});
export const singlePointRange = (value: number) => ({
min: value ? Math.min(value * 2, 0) : -0.5,
max: value ? Math.max(value * 2, 0) : 0.5
});
export const range = ({datasets, outlier}: {datasets: Dataset[]; outlier: boolean}) => {
const ranges = compact(
datasets?.map(dataset => {
if (dataset.length == 0) return;
const values = dataset.map(v => v[2]);
if (!outlier) {
// Get the orgin data range.
return {
min: Math.min(...values) ?? 0,
max: Math.max(...values) ?? 0
};
} else {
// Get the quantile range.
const sorted = dataset.map(v => v[2]).sort();
return {
min: quantile(sorted, 0.05),
max: quantile(values, 0.95)
};
}
})
);
const min = minBy(ranges, range => range.min)?.min ?? 0;
const max = maxBy(ranges, range => range.max)?.max ?? 0;
if (!(min === 0 && max === 0)) {
return {
min: min > 0 ? min * 0.9 : min * 1.1,
max: max > 0 ? max * 1.1 : max * 0.9
};
}
};
export const nearestPoint = (data: Dataset[], runs: Run[], step: number) =>
data.map((series, index) => {
let nearestItem;
if (step === 0) {
nearestItem = series[0];
} else {
for (let i = 0; i < series.length; i++) {
const item = series[i];
if (item[1] === step) {
nearestItem = item;
break;
}
if (item[1] > step) {
nearestItem = series[i - 1 >= 0 ? i - 1 : 0];
break;
}
if (!nearestItem) {
nearestItem = series[series.length - 1];
}
}
}
return {
run: runs[index],
item: nearestItem || []
};
});
import * as chart from '~/utils/chart';
import {ChartDataParams, Dataset, RangeParams, TooltipData, TransformParams} from './types';
import {formatTime, quantile} from '~/utils';
import BigNumber from 'bignumber.js';
import {I18n} from '@visualdl/i18n';
import {Run} from '~/types';
import cloneDeep from 'lodash/cloneDeep';
import compact from 'lodash/compact';
import maxBy from 'lodash/maxBy';
import minBy from 'lodash/minBy';
import {TooltipData} from './types';
import sortBy from 'lodash/sortBy';
BigNumber.config({EXPONENTIAL_AT: [-6, 7]});
export * from './types';
export const xAxis = ['step', 'relative', 'wall'] as const;
export const xAxisMap = {
step: 1,
relative: 4,
wall: 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]),
// Compare other ponts width the trigger point, caculate the nearest sort.
nearest: (points: TooltipData[], data: number[]) => sortBy(points, point => point.item[3] - data[2])
};
export const transform = ({datasets, smoothing}: TransformParams) =>
// https://en.wikipedia.org/wiki/Moving_average
datasets.map(seriesData => {
const data = cloneDeep(seriesData);
let last = new BigNumber(data.length > 0 ? 0 : Number.NaN);
let numAccum = 0;
let startValue = 0;
const bigSmoothing = new BigNumber(smoothing);
data.forEach((d, i) => {
const nextVal = new BigNumber(d[2]);
// second to millisecond.
const millisecond = (d[0] = Math.floor(d[0] * 1000));
if (i === 0) {
startValue = millisecond;
}
// relative time, millisecond to hours.
d[4] = Math.floor(millisecond - startValue) / (60 * 60 * 1000);
if (!nextVal.isFinite()) {
d[3] = nextVal.toNumber();
} else {
// last = last * smoothing + (1 - smoothing) * nextVal;
last = last.multipliedBy(bigSmoothing).plus(bigSmoothing.minus(1).negated().multipliedBy(nextVal));
numAccum++;
let debiasWeight = new BigNumber(1);
if (!bigSmoothing.isEqualTo(1)) {
//debiasWeight = 1.0 - Math.pow(smoothing, numAccum);
debiasWeight = bigSmoothing.exponentiatedBy(numAccum).minus(1).negated();
}
// d[3] = last / debiasWeight;
d[3] = last.dividedBy(debiasWeight).toNumber();
}
});
return data;
});
export const chartData = ({data, runs, smooth, xAxis}: ChartDataParams) =>
data
.map((dataset, i) => {
// smoothed data:
// [0] wall time
// [1] step
// [2] orginal value
// [3] smoothed value
// [4] relative
const name = runs[i].label;
const color = runs[i].colors[0];
const colorAlt = runs[i].colors[1];
return [
{
name,
z: i,
lineStyle: {
color: colorAlt,
width: chart.series.lineStyle.width
},
data: dataset,
encode: {
x: [xAxisMap[xAxis]],
y: [2]
},
smooth
},
{
name,
z: runs.length + i,
itemStyle: {
color
},
data: dataset,
encode: {
x: [xAxisMap[xAxis]],
y: [3]
},
smooth
}
];
})
.flat();
export const singlePointRange = (value: number) => ({
min: value ? Math.min(value * 2, 0) : -0.5,
max: value ? Math.max(value * 2, 0) : 0.5
});
export const range = ({datasets, outlier}: RangeParams) => {
const ranges = compact(
datasets?.map(dataset => {
if (dataset.length == 0) return;
const values = dataset.map(v => v[2]);
if (!outlier) {
// Get the orgin data range.
return {
min: Math.min(...values) ?? 0,
max: Math.max(...values) ?? 0
};
} else {
// Get the quantile range.
const sorted = dataset.map(v => v[2]).sort();
return {
min: quantile(sorted, 0.05),
max: quantile(values, 0.95)
};
}
})
);
} as const;
const min = minBy(ranges, range => range.min)?.min ?? 0;
const max = maxBy(ranges, range => range.max)?.max ?? 0;
if (!(min === 0 && max === 0)) {
return {
min: min > 0 ? min * 0.9 : min * 1.1,
max: max > 0 ? max * 1.1 : max * 0.9
};
}
};
export const nearestPoint = (data: Dataset[], runs: Run[], step: number) =>
data.map((series, index) => {
let nearestItem;
if (step === 0) {
nearestItem = series[0];
} else {
for (let i = 0; i < series.length; i++) {
const item = series[i];
if (item[1] === step) {
nearestItem = item;
break;
}
if (item[1] > step) {
nearestItem = series[i - 1 >= 0 ? i - 1 : 0];
break;
}
if (!nearestItem) {
nearestItem = series[series.length - 1];
}
}
}
return {
run: runs[index],
item: nearestItem || []
};
});
// TODO: make it better, don't concat html
export const tooltip = (data: TooltipData[], i18n: I18n) => {
const indexPropMap = {
time: 0,
step: 1,
value: 2,
smoothed: 3,
relative: 4
} as const;
const widthPropMap = {
run: [60, 180] as [number, number],
time: 150,
step: 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;
return {
run: item.run,
// use precision then toString to remove trailling 0
smoothed: new BigNumber(data[indexPropMap.smoothed] ?? Number.NaN).precision(5).toString(),
value: new BigNumber(data[indexPropMap.value] ?? Number.NaN).precision(5).toString(),
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 | [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];
}
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>`;
};
export * from './types';
export * from './chart';
export * from './data';
import {sortingMethodMap, xAxisMap} from './index';
import {Run} from '~/types';
import {xAxisMap} from './index';
export type Dataset = number[][];
export type XAxis = keyof typeof xAxisMap;
export type SortingMethod = keyof typeof sortingMethodMap;
export type Range = {
min: number;
max: number;
......@@ -12,20 +16,3 @@ export type TooltipData = {
run: Run;
item: number[];
};
export type TransformParams = {
datasets: Dataset[];
smoothing: number;
};
export type ChartDataParams = {
data: Dataset[];
runs: Run[];
smooth: boolean;
xAxis: keyof typeof xAxisMap;
};
export type RangeParams = {
datasets: Dataset[];
outlier: boolean;
};
......@@ -7,7 +7,8 @@
"lib": [
"esnext",
"esnext.asynciterable",
"dom"
"dom",
"webworker"
],
"esModuleInterop": true,
"allowJs": true,
......
......@@ -7,3 +7,8 @@ export interface Tag {
runs: Run[];
label: string;
}
export interface TagWithSingleRun {
label: string;
run: Run;
}
import {format} from 'd3-format';
import {primaryColor} from '~/utils/style';
export const color = [
'#2932E1',
'#00CC88',
......@@ -50,14 +53,14 @@ export const title = {
export const tooltip = {
trigger: 'axis',
position: ['10%', '100%'],
backgroundColor: 'rgba(0, 0, 0, 0.75)',
hideDelay: 100,
enterable: false,
axisPointer: {
type: 'cross',
label: {
show: true
show: true,
formatter: ({value}: {value: number}) => format('.4')(value)
},
lineStyle: {
color: '#2932E1',
......@@ -101,7 +104,7 @@ export const legend = {
};
export const grid = {
left: 50,
left: 60,
top: 60,
right: 30,
bottom: 30
......@@ -124,7 +127,8 @@ export const xAxis = {
},
axisLabel: {
fontSize: 12,
color: '#666'
color: '#666',
formatter: format('.4')
},
splitLine: {
show: false
......@@ -147,7 +151,7 @@ export const yAxis = {
axisLabel: {
fontSize: 12,
color: '#666',
formatter: (v: number) => (v < 0.0001 ? v.toExponential(1) : Number.parseFloat(v.toFixed(4)))
formatter: format('.4')
},
splitLine: {
lineStyle: {
......@@ -157,10 +161,10 @@ export const yAxis = {
};
export const series = {
type: 'line',
hoverAnimation: false,
animationDuration: 100,
lineStyle: {
color: primaryColor,
width: 1.5
}
};
......@@ -19,6 +19,5 @@ export const quantile = (values: number[], p: number) => {
const i0 = i.integerValue().toNumber();
const value0 = new BigNumber(values[i0]);
const value1 = new BigNumber(values[i0 + 1]);
// return value0 + (value1 - value0) * (i - i0);
return value0.plus(value1.minus(value0).multipliedBy(i.minus(i0))).toNumber();
};
export default <P = unknown, R = unknown>(handler: (data: P) => R): void =>
self.addEventListener('message', ({data}: MessageEvent & {data: P}) => {
self.postMessage(handler(data));
});
import {DivideParams, divide} from '~/resource/high-dimensional';
import {divide} from '~/resource/high-dimensional';
import worker from '~/utils/worker';
import {exposeWorker} from 'react-hooks-worker';
// exposeWorker can only handle Promise
exposeWorker((data: DivideParams) => Promise.resolve(divide(data)));
worker(divide);
import {transform} from '~/resource/histogram';
import worker from '~/utils/worker';
worker(transform);
import {RangeParams, range} from '~/resource/scalars';
import {range} from '~/resource/scalars';
import worker from '~/utils/worker';
import {exposeWorker} from 'react-hooks-worker';
// exposeWorker can only handle Promise
exposeWorker((data: RangeParams) => Promise.resolve(range(data)));
worker(range);
import {TransformParams, transform} from '~/resource/scalars';
import {transform} from '~/resource/scalars';
import worker from '~/utils/worker';
import {exposeWorker} from 'react-hooks-worker';
// exposeWorker can only handle Promise
exposeWorker((data: TransformParams) => Promise.resolve(transform(data)));
worker(transform);
export default ['embeddings', 'scalar', 'image', 'graph'];
export default ['embeddings', 'scalar', 'image', 'graph', 'histogram'];
export default [
[
1515224840.945252,
0,
[
[-4.826786994934082, -5.099814079160488, 0.0],
[-5.099814079160488, -4.636194617418625, 1.0],
[-3.8315657995195243, -3.48324163592684, 125.0],
[-3.48324163592684, -3.1665833053880363, 436.0],
[-2.6170109961884593, -2.379100905625872, 5362.0],
[-2.379100905625872, -2.1628190051144287, 9589.0],
[-1.47723448201245, -1.3429404381931362, 54279.0],
[-1.3429404381931362, -1.220854943811942, 64480.0],
[-1.0089710279437536, -0.917246389039776, 89844.0],
[-0.917246389039776, -0.8338603536725235, 93854.0],
[-0.8338603536725235, -0.7580548669750213, 96552.0],
[-0.7580548669750213, -0.6891407881591103, 96980.0],
[-0.6891407881591103, -0.6264916255991911, 97035.0],
[-0.6264916255991911, -0.56953784145381, 94798.0],
[-1.316866795643118e-5, -1.1971516324028345e-5, 8.0],
[-1.1971516324028345e-5, -1.0883196658207586e-5, 1.0],
[-1.0883196658207586e-5, -9.893815143825077e-6, 1.0],
[-9.893815143825077e-6, -8.994377403477343e-6, 2.0],
[-8.994377403477343e-6, -8.176706730433948e-6, 2.0],
[-8.176706730433948e-6, -7.4333697549399525e-6, 2.0],
[-7.4333697549399525e-6, -6.757608868127229e-6, 1.0],
[-6.757608868127229e-6, -6.143280789206572e-6, 4.0],
[-6.143280789206572e-6, -5.077091561327744e-6, 0.0],
[-5.077091561327744e-6, -4.615537783025222e-6, 1.0],
[-4.615537783025222e-6, -4.1959434391138375e-6, 0.0],
[-4.1959434391138375e-6, -3.8144940355580335e-6, 2.0],
[-3.8144940355580335e-6, -3.467721850507303e-6, 1.0],
[-3.467721850507303e-6, -2.865885826865539e-6, 0.0],
[-2.865885826865539e-6, -2.605350751695944e-6, 1.0],
[-2.605350751695944e-6, -2.153182439418135e-6, 0.0],
[-1.2154153537011338e-6, -1.1049230488192125e-6, 1.0],
[-1.1049230488192125e-6, -5.670002325218022e-7, 0.0],
[2.5662008430919505e-5, 2.822820927401146e-5, 8.0],
[2.822820927401146e-5, 3.1051030201412604e-5, 4.0],
[3.1051030201412604e-5, 3.415613322155387e-5, 4.0],
[3.415613322155387e-5, 3.757174654370926e-5, 6.0],
[3.757174654370926e-5, 4.132892119808019e-5, 7.0],
[4.132892119808019e-5, 4.546181331788821e-5, 8.0],
[4.546181331788821e-5, 5.000799464967703e-5, 19.0],
[5.000799464967703e-5, 5.500879411464474e-5, 18.0],
[5.500879411464474e-5, 6.050967352610922e-5, 10.0],
[6.050967352610922e-5, 6.656064087872014e-5, 12.0],
[6.656064087872014e-5, 7.321670496659217e-5, 13.0],
[7.321670496659217e-5, 8.053837546325138e-5, 14.0],
[8.053837546325138e-5, 8.859221300957652e-5, 18.0],
[8.859221300957652e-5, 9.745143431053419e-5, 22.0],
[9.745143431053419e-5, 0.00010719657774158762, 23.0],
[0.00010719657774158762, 0.00011791623551574639, 34.0],
[0.917246389039776, 1.0089710279437536, 124200.0],
[1.0089710279437536, 1.109868130738129, 119511.0],
[1.109868130738129, 1.220854943811942, 111589.0],
[1.220854943811942, 1.3429404381931362, 101296.0],
[2.8787120958073054, 3.1665833053880363, 3382.0],
[3.1665833053880363, 3.48324163592684, 1351.0],
[3.48324163592684, 3.8315657995195243, 468.0],
[3.8315657995195243, 4.214722379471477, 105.0]
]
],
[
1515224846.83122,
70,
[
[-5.609264373779297, -5.609795487076537, 0.0],
[-5.609795487076537, -5.099814079160488, 5.0],
[-5.099814079160488, -4.636194617418625, 26.0],
[-4.636194617418625, -4.214722379471477, 114.0],
[-4.214722379471477, -3.8315657995195243, 379.0],
[-3.8315657995195243, -3.48324163592684, 1072.0],
[-3.48324163592684, -3.1665833053880363, 2542.0],
[-3.1665833053880363, -2.8787120958073054, 5260.0],
[-2.8787120958073054, -2.6170109961884593, 9568.0],
[-2.6170109961884593, -2.379100905625872, 15632.0],
[-2.379100905625872, -2.1628190051144287, 23154.0],
[-2.1628190051144287, -1.9661990955585713, 31489.0],
[-1.9661990955585713, -1.7874537232350647, 41367.0],
[-1.7874537232350647, -1.624957930213695, 50809.0],
[-0.052566063576589855, -0.04778733052417259, 10183.0],
[-0.04778733052417259, -0.043443027749247805, 9196.0],
[-0.01143990698806325, -0.010399915443693864, 2264.0],
[-0.010399915443693864, -0.00945446858517624, 2012.0],
[0.0007211649713244094, 0.0007932814684568504, 152.0],
[0.0007932814684568504, 0.0008726096153025355, 168.0],
[0.0008726096153025355, 0.0009598705768327891, 179.0],
[0.0009598705768327891, 0.001055857634516068, 198.0],
[0.47069243095356195, 0.5177616740489182, 96395.0],
[0.5177616740489182, 0.56953784145381, 103088.0],
[0.56953784145381, 0.6264916255991911, 110245.0],
[0.6264916255991911, 0.6891407881591103, 116777.0],
[0.6891407881591103, 0.7580548669750213, 123501.0],
[0.7580548669750213, 0.8338603536725235, 127806.0],
[0.8338603536725235, 0.917246389039776, 132276.0],
[0.917246389039776, 1.0089710279437536, 134169.0],
[1.0089710279437536, 1.109868130738129, 133209.0],
[1.109868130738129, 1.220854943811942, 129736.0],
[2.6170109961884593, 2.8787120958073054, 14814.0],
[2.8787120958073054, 3.1665833053880363, 8284.0],
[3.8315657995195243, 4.214722379471477, 699.0],
[4.214722379471477, 4.636194617418625, 245.0],
[4.636194617418625, 5.099814079160488, 64.0],
[5.099814079160488, 5.609795487076537, 22.0],
[5.609795487076537, 6.1707750357841915, 2.0],
[6.1707750357841915, 6.077327728271484, 0.0]
]
],
[
1515224850.414384,
100,
[
[-5.622415065765381, -6.1707750357841915, 0.0],
[-6.1707750357841915, -5.609795487076537, 1.0],
[-4.636194617418625, -4.214722379471477, 130.0],
[-4.214722379471477, -3.8315657995195243, 364.0],
[-3.8315657995195243, -3.48324163592684, 1063.0],
[-3.48324163592684, -3.1665833053880363, 2440.0],
[-2.6170109961884593, -2.379100905625872, 15079.0],
[-2.379100905625872, -2.1628190051144287, 22447.0],
[-1.0089710279437536, -0.917246389039776, 89632.0],
[-0.917246389039776, -0.8338603536725235, 91513.0],
[-0.8338603536725235, -0.7580548669750213, 91158.0],
[-0.7580548669750213, -0.6891407881591103, 89568.0],
[-0.6891407881591103, -0.6264916255991911, 88244.0],
[-0.6264916255991911, -0.56953784145381, 84929.0],
[-4.757441129747921e-8, 5.15454756838002e-7, 0.0],
[5.15454756838002e-7, 5.670002325218022e-7, 1.0],
[5.670002325218022e-7, 6.237002557739824e-7, 0.0],
[6.237002557739824e-7, 6.860702813513807e-7, 1.0],
[6.860702813513807e-7, 8.301450404351707e-7, 0.0],
[8.301450404351707e-7, 9.131595444786879e-7, 1.0],
[9.131595444786879e-7, 1.0044754989265568e-6, 1.0],
[1.0044754989265568e-6, 1.6177178357762093e-6, 0.0],
[6.757608868127229e-6, 7.4333697549399525e-6, 1.0],
[7.4333697549399525e-6, 8.176706730433948e-6, 2.0],
[8.176706730433948e-6, 8.994377403477343e-6, 1.0],
[8.994377403477343e-6, 9.893815143825077e-6, 0.0],
[9.893815143825077e-6, 1.0883196658207586e-5, 3.0],
[1.0883196658207586e-5, 1.1971516324028345e-5, 2.0],
[1.1971516324028345e-5, 1.316866795643118e-5, 3.0],
[1.316866795643118e-5, 1.44855347520743e-5, 4.0],
[3.757174654370926e-5, 4.132892119808019e-5, 8.0],
[4.132892119808019e-5, 4.546181331788821e-5, 8.0],
[4.546181331788821e-5, 5.000799464967703e-5, 7.0],
[5.000799464967703e-5, 5.500879411464474e-5, 10.0],
[5.500879411464474e-5, 6.050967352610922e-5, 13.0],
[6.050967352610922e-5, 6.656064087872014e-5, 11.0],
[6.656064087872014e-5, 7.321670496659217e-5, 15.0],
[7.321670496659217e-5, 8.053837546325138e-5, 16.0],
[8.053837546325138e-5, 8.859221300957652e-5, 27.0],
[8.859221300957652e-5, 9.745143431053419e-5, 17.0],
[0.8338603536725235, 0.917246389039776, 130481.0],
[0.917246389039776, 1.0089710279437536, 133238.0],
[1.0089710279437536, 1.109868130738129, 131855.0],
[1.109868130738129, 1.220854943811942, 129006.0],
[2.6170109961884593, 2.8787120958073054, 14697.0],
[2.8787120958073054, 3.1665833053880363, 8234.0],
[3.1665833053880363, 3.48324163592684, 4187.0],
[3.48324163592684, 3.8315657995195243, 1800.0],
[4.636194617418625, 5.099814079160488, 67.0],
[5.099814079160488, 5.609795487076537, 22.0],
[5.609795487076537, 6.1707750357841915, 3.0],
[6.1707750357841915, 6.0968852043151855, 0.0]
]
],
[
1515224852.17382,
120,
[
[-5.641714572906494, -6.1707750357841915, 0.0],
[-6.1707750357841915, -5.609795487076537, 1.0],
[-5.609795487076537, -5.099814079160488, 8.0],
[-5.099814079160488, -4.636194617418625, 23.0],
[-4.636194617418625, -4.214722379471477, 134.0],
[-4.214722379471477, -3.8315657995195243, 325.0],
[-3.8315657995195243, -3.48324163592684, 980.0],
[-3.48324163592684, -3.1665833053880363, 2241.0],
[-3.1665833053880363, -2.8787120958073054, 4732.0],
[-2.8787120958073054, -2.6170109961884593, 8511.0],
[-2.6170109961884593, -2.379100905625872, 14412.0],
[-2.379100905625872, -2.1628190051144287, 21324.0],
[-2.1628190051144287, -1.9661990955585713, 30265.0],
[-1.9661990955585713, -1.7874537232350647, 39939.0],
[-1.7874537232350647, -1.624957930213695, 49754.0],
[-1.624957930213695, -1.47723448201245, 59029.0],
[-1.47723448201245, -1.3429404381931362, 67624.0],
[-1.3429404381931362, -1.220854943811942, 75809.0],
[-1.220854943811942, -1.109868130738129, 82164.0],
[-1.109868130738129, -1.0089710279437536, 86962.0],
[-1.0089710279437536, -0.917246389039776, 90354.0],
[-0.917246389039776, -0.8338603536725235, 91382.0],
[-0.8338603536725235, -0.7580548669750213, 91667.0],
[-0.7580548669750213, -0.6891407881591103, 90086.0],
[-0.6891407881591103, -0.6264916255991911, 88401.0],
[-0.6264916255991911, -0.56953784145381, 85407.0],
[3.1051030201412604e-5, 3.415613322155387e-5, 8.0],
[8.053837546325138e-5, 8.859221300957652e-5, 11.0],
[0.012583897686869577, 0.013842287455556535, 2645.0],
[0.013842287455556535, 0.01522651620111219, 3037.0],
[0.01522651620111219, 0.01674916782122341, 3297.0],
[0.01674916782122341, 0.018424084603345752, 3686.0],
[0.018424084603345752, 0.02026649306368033, 3954.0],
[0.02026649306368033, 0.022293142370048362, 4388.0],
[0.022293142370048362, 0.0245224566070532, 4888.0],
[0.0245224566070532, 0.026974702267758523, 5350.0],
[0.026974702267758523, 0.02967217249453438, 5800.0],
[0.02967217249453438, 0.03263938974398782, 6459.0],
[0.03263938974398782, 0.035903328718386605, 7217.0],
[0.035903328718386605, 0.03949366159022527, 7816.0],
[0.03949366159022527, 0.043443027749247805, 8647.0],
[0.043443027749247805, 0.04778733052417259, 9398.0],
[0.04778733052417259, 0.052566063576589855, 10457.0],
[0.052566063576589855, 0.057822669934248845, 11420.0],
[0.057822669934248845, 0.06360493692767373, 12728.0],
[0.06360493692767373, 0.06996543062044111, 13975.0],
[0.06996543062044111, 0.07696197368248522, 15413.0],
[0.07696197368248522, 0.08465817105073375, 17143.0],
[0.08465817105073375, 0.09312398815580714, 19025.0],
[0.09312398815580714, 0.10243638697138786, 20509.0],
[0.10243638697138786, 0.11268002566852665, 22060.0],
[0.11268002566852665, 0.12394802823537933, 24938.0],
[0.12394802823537933, 0.13634283105891729, 27263.0],
[1.7874537232350647, 1.9661990955585713, 68215.0],
[1.9661990955585713, 2.1628190051144287, 51944.0],
[2.1628190051144287, 2.379100905625872, 36899.0],
[2.379100905625872, 2.6170109961884593, 23872.0],
[4.636194617418625, 5.099814079160488, 68.0],
[5.099814079160488, 5.609795487076537, 22.0],
[5.609795487076537, 6.1707750357841915, 3.0],
[6.1707750357841915, 6.084466934204102, 0.0]
]
]
];
export default {
test: ['layer2/biases/summaries/mean'],
train: ['layer2/biases/summaries/mean', 'layer2/biases/summaries/accuracy', 'layer2/biases/summaries/cost']
};
#[derive(Serialize, Deserialize)]
pub struct Point {
name: String,
value: Vec<f64>,
showing: bool,
}
impl Point {
pub fn new(name: String, value: Vec<f64>, showing: bool) -> Self {
Point {
name,
value,
showing,
}
}
}
#[derive(Serialize, Deserialize)]
pub struct DividedPoints(Vec<Point>, Vec<Point>);
impl DividedPoints {
pub fn new(matched: Vec<Point>, missing: Vec<Point>) -> Self {
DividedPoints(matched, missing)
}
}
pub fn divide(
points: Vec<Vec<f64>>,
labels: Vec<String>,
visibility: bool,
keyword: String,
) -> DividedPoints {
let mut matched: Vec<Point> = vec![];
let mut missing: Vec<Point> = vec![];
for (i, point) in points.iter().enumerate() {
let mut name: String = String::from("");
let ptr: *const String = &labels[i];
if !ptr.is_null() {
name = labels[i].clone();
}
let point_with_label: Point = Point::new(name.clone(), point.to_vec(), visibility);
if keyword == String::from("") {
missing.push(point_with_label);
} else {
if name.contains(&keyword) {
matched.push(point_with_label);
} else {
missing.push(point_with_label);
}
}
}
return DividedPoints::new(matched, missing);
}
use std::f64;
trait FloatIterExt {
fn float_min(&mut self) -> f64;
fn float_max(&mut self) -> f64;
}
impl<T> FloatIterExt for T
where
T: Iterator<Item = f64>,
{
fn float_max(&mut self) -> f64 {
self.fold(f64::NAN, f64::max)
}
fn float_min(&mut self) -> f64 {
self.fold(f64::NAN, f64::min)
}
}
struct Histogram {
left: f64,
right: f64,
count: f64,
}
struct Coordinates {
x: f64,
dx: f64,
y: f64,
}
impl Coordinates {
pub fn new(x: f64, dx: f64, y: f64) -> Self {
Coordinates { x, dx, y }
}
}
#[derive(Serialize, Deserialize)]
struct Item(f64, f64, f64);
#[derive(Serialize, Deserialize)]
pub struct Data(f64, f64, Vec<Item>);
#[derive(Serialize, Deserialize)]
struct OverlayItem(f64, f64, f64, f64);
#[derive(Serialize, Deserialize)]
pub struct Overlay {
min: f64,
max: f64,
data: Vec<Vec<OverlayItem>>,
}
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize)]
pub struct Offset {
minX: f64,
maxX: f64,
minZ: f64,
maxZ: f64,
minStep: f64,
maxStep: f64,
data: Vec<Vec<f64>>,
}
fn compute_histogram(
data: &Vec<Histogram>,
mut min: f64,
mut max: f64,
bins_num: i64,
) -> Vec<Coordinates> {
if min == max {
max = min * 1.1 + 1.;
min = min / 1.1 - 1.;
}
let step_width: f64 = (max - min) / bins_num as f64;
let mut range: Vec<f64> = vec![];
let mut optional = Some(min);
while let Some(i) = optional {
if i > max {
optional = None;
} else {
range.push(i);
optional = Some(i + step_width);
}
}
let mut item_index: usize = 0;
let mut result: Vec<Coordinates> = vec![];
for bin_left in range {
let bin_right: f64 = bin_left + step_width;
let mut y: f64 = 1.;
let n: usize = data.len();
while item_index < n {
let item_right: f64 = max.min(data[item_index].right);
let item_left: f64 = min.max(data[item_index].left);
let overlap: f64 = item_right.min(bin_right) - item_left.max(bin_left);
let count: f64 = (overlap / (item_right - item_left)) * data[item_index].count;
if overlap > 0. {
y += count;
}
if item_right > bin_right {
break;
}
item_index += 1;
}
result.push(Coordinates::new(bin_left, step_width, y));
}
return result;
}
pub fn transform_overlay(data: Vec<Data>) -> Overlay {
struct Temp {
time: f64,
step: f64,
min: f64,
max: f64,
}
let mut temp: Vec<Temp> = vec![];
let mut items: Vec<Vec<Histogram>> = vec![];
for item in data {
temp.push(Temp {
time: item.0,
step: item.1,
min: item.2.iter().map(|x| x.0).float_min(),
max: item.2.iter().map(|x| x.1).float_max(),
});
items.push(
item.2
.iter()
.map(|x| Histogram {
left: x.0,
right: x.1,
count: x.2,
})
.collect(),
);
}
let min: f64 = temp.iter().map(|x| x.min).float_min();
let max: f64 = temp.iter().map(|x| x.max).float_max();
let mut overlay: Vec<Vec<OverlayItem>> = vec![];
for (i, item) in temp.iter().enumerate() {
let computed: Vec<Coordinates> = compute_histogram(&items[i], min, max, 30);
overlay.push(
computed
.iter()
.map(|x| OverlayItem(item.time, item.step, x.x + x.dx / 2.0_f64, x.y.floor()))
.collect(),
);
}
return Overlay {
min,
max,
data: overlay,
};
}
pub fn transform_offset(data: Vec<Data>) -> Offset {
let overlay: Overlay = transform_overlay(data);
let mut min_step: f64 = std::f64::INFINITY;
let mut max_step: f64 = std::f64::NEG_INFINITY;
let mut min_z: f64 = std::f64::INFINITY;
let mut max_z: f64 = std::f64::NEG_INFINITY;
let mut offset: Vec<Vec<f64>> = vec![];
for items in overlay.data {
let step: f64 = items[0].1;
if step > max_step {
max_step = step;
}
if step < min_step {
min_step = step;
}
let mut res: Vec<f64> = vec![];
for item in items {
let x: f64 = item.2;
let y: f64 = item.3;
if y > max_z {
max_z = y;
}
if y < min_z {
min_z = y;
}
res.push(x);
res.push(step);
res.push(y);
}
offset.push(res);
}
return Offset {
minX: overlay.min,
maxX: overlay.max,
minZ: min_z,
maxZ: max_z,
minStep: min_step,
maxStep: max_step,
data: offset,
};
}
mod utils;
use wasm_bindgen::prelude::*;
mod high_dimensional;
mod histogram;
mod scalar;
mod utils;
#[macro_use]
extern crate serde_derive;
pub fn main() {}
#[derive(Serialize, Deserialize)]
struct Dataset(f64, i64, f64);
#[derive(Serialize, Deserialize)]
struct Smoothed(i64, i64, f64, f64, f64);
#[wasm_bindgen]
pub fn transform(js_datasets: &JsValue, smoothing: f64) -> JsValue {
pub fn scalar_transform(js_datasets: &JsValue, smoothing: f64) -> JsValue {
utils::set_panic_hook();
let datasets: Vec<Vec<Dataset>> = js_datasets.into_serde().unwrap();
let mut result: Vec<Vec<Smoothed>> = vec![];
for dataset in datasets.iter() {
let mut row: Vec<Smoothed> = vec![];
let mut last: f64 = std::f64::NAN;
if dataset.len() > 0 {
last = 0_f64;
}
let mut num_accum: i32 = 0;
let mut start_value: i64 = 0;
for (i, d) in dataset.iter().enumerate() {
let mut r: Smoothed = Smoothed(0, d.1, d.2, 0.0, 0.0);
let next_val: f64 = d.2;
// second to millisecond.
let millisecond: i64 = ((d.0 as f64) * 1000_f64).floor() as i64;
r.0 = millisecond;
if i == 0 {
start_value = millisecond;
}
// Relative time, millisecond to hours.
r.4 = ((millisecond - start_value) as f64) / (60 * 60 * 1000) as f64;
if next_val.is_infinite() {
r.3 = next_val;
} else {
last = last * smoothing + (1.0 - smoothing) * next_val;
num_accum += 1;
let mut debias_weight: f64 = 1.0_f64;
if smoothing != 1.0 {
debias_weight = (1.0_f64 - smoothing.powi(num_accum)).into();
}
r.3 = last / debias_weight;
}
row.push(r);
}
result.push(row);
}
let datasets: Vec<Vec<scalar::Dataset>> = js_datasets.into_serde().unwrap();
let result: Vec<Vec<scalar::Smoothed>> = scalar::transform(datasets, smoothing);
return JsValue::from_serde(&result).unwrap();
}
#[derive(Serialize, Deserialize)]
struct Range {
min: f64,
max: f64,
}
impl Range {
pub fn new(min: f64, max: f64) -> Self {
Range { min, max }
}
}
fn quantile(values: Vec<f64>, p: f64) -> f64 {
let n: usize = values.len();
if n == 0 {
return std::f64::NAN;
}
if p <= 0. || n < 2 {
return values[0];
}
if p >= 1. {
return values[n - 1];
}
let i: f64 = ((n - 1) as f64) * p;
let i0: usize = i.floor() as usize;
let value0: f64 = values[i0];
let value1: f64 = values[i0 + 1];
return value0 + (value1 - value0) * (i - (i0 as f64));
}
#[wasm_bindgen]
pub fn range(js_datasets: &JsValue, outlier: bool) -> JsValue {
pub fn scalar_range(js_datasets: &JsValue, outlier: bool) -> JsValue {
utils::set_panic_hook();
let datasets: Vec<Vec<Smoothed>> = js_datasets.into_serde().unwrap();
let mut ranges: Vec<Range> = vec![];
for data in datasets.iter() {
let n: usize = data.len();
if n == 0 {
continue;
}
let values: Vec<f64> = data.iter().map(|x| x.2).collect();
let mut sorted: Vec<f64> = values.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
if !outlier {
ranges.push(Range::new(sorted[0], sorted[n - 1]));
} else {
ranges.push(Range::new(
quantile(sorted, 0.05_f64),
quantile(values, 0.95),
));
}
}
let mut min: f64 = 0.;
let mut max: f64 = 1.;
if ranges.len() != 0 {
min = ranges
.iter()
.min_by(|x, y| x.min.partial_cmp(&y.max).unwrap())
.unwrap()
.min;
max = ranges
.iter()
.max_by(|x, y| x.max.partial_cmp(&y.max).unwrap())
.unwrap()
.max;
if min > 0. {
min *= 0.9;
} else {
min *= 1.1;
}
if max > 0. {
max *= 1.1;
} else {
max *= 0.9;
}
}
let result = Range::new(min, max);
let datasets: Vec<Vec<scalar::Smoothed>> = js_datasets.into_serde().unwrap();
let result = scalar::range(datasets, outlier);
return JsValue::from_serde(&result).unwrap();
}
#[derive(Serialize, Deserialize)]
struct Point {
name: String,
value: Vec<f64>,
showing: bool,
}
#[derive(Serialize, Deserialize)]
struct DividedPoints(Vec<Point>, Vec<Point>);
impl Point {
pub fn new(name: String, value: Vec<f64>, showing: bool) -> Self {
Point {
name,
value,
showing,
}
#[wasm_bindgen]
pub fn histogram_transform(js_data: &JsValue, mode: String) -> JsValue {
utils::set_panic_hook();
let data: Vec<histogram::Data> = js_data.into_serde().unwrap();
if mode == String::from("overlay") {
let result = histogram::transform_overlay(data);
return JsValue::from_serde(&result).unwrap();
}
}
impl DividedPoints {
pub fn new(matched: Vec<Point>, missing: Vec<Point>) -> Self {
DividedPoints(matched, missing)
if mode == String::from("offset") {
let result = histogram::transform_offset(data);
return JsValue::from_serde(&result).unwrap();
}
return JsValue::null();
}
#[wasm_bindgen]
pub fn divide(
pub fn high_dimensional_divide(
js_points: &JsValue,
js_labels: &JsValue,
visibility: bool,
keyword: String,
) -> JsValue {
utils::set_panic_hook();
let points: Vec<Vec<f64>> = js_points.into_serde().unwrap();
let labels: Vec<String> = js_labels.into_serde().unwrap();
let mut matched: Vec<Point> = vec![];
let mut missing: Vec<Point> = vec![];
for (i, point) in points.iter().enumerate() {
let mut name: String = String::from("");
let ptr: *const String = &labels[i];
if !ptr.is_null() {
name = labels[i].clone();
}
let point_with_label: Point = Point::new(name.clone(), point.to_vec(), visibility);
if keyword == String::from("") {
missing.push(point_with_label);
} else {
if name.contains(&keyword) {
matched.push(point_with_label);
} else {
missing.push(point_with_label);
}
}
}
let result: DividedPoints = DividedPoints::new(matched, missing);
let result: high_dimensional::DividedPoints =
high_dimensional::divide(points, labels, visibility, keyword);
return JsValue::from_serde(&result).unwrap();
}
#[derive(Serialize, Deserialize)]
pub struct Dataset(f64, i64, f64);
#[derive(Serialize, Deserialize)]
pub struct Smoothed(i64, i64, f64, f64, f64);
#[derive(Serialize, Deserialize)]
pub struct Range {
min: f64,
max: f64,
}
impl Range {
pub fn new(min: f64, max: f64) -> Self {
Range { min, max }
}
}
fn quantile(values: Vec<f64>, p: f64) -> f64 {
let n: usize = values.len();
if n == 0 {
return std::f64::NAN;
}
if p <= 0. || n < 2 {
return values[0];
}
if p >= 1. {
return values[n - 1];
}
let i: f64 = ((n - 1) as f64) * p;
let i0: usize = i.floor() as usize;
let value0: f64 = values[i0];
let value1: f64 = values[i0 + 1];
return value0 + (value1 - value0) * (i - (i0 as f64));
}
pub fn transform(datasets: Vec<Vec<Dataset>>, smoothing: f64) -> Vec<Vec<Smoothed>> {
let mut result: Vec<Vec<Smoothed>> = vec![];
for dataset in datasets.iter() {
let mut row: Vec<Smoothed> = vec![];
let mut last: f64 = std::f64::NAN;
if dataset.len() > 0 {
last = 0_f64;
}
let mut num_accum: i32 = 0;
let mut start_value: i64 = 0;
for (i, d) in dataset.iter().enumerate() {
let mut r: Smoothed = Smoothed(0, d.1, d.2, 0.0, 0.0);
let next_val: f64 = d.2;
// second to millisecond.
let millisecond: i64 = ((d.0 as f64) * 1000_f64).floor() as i64;
r.0 = millisecond;
if i == 0 {
start_value = millisecond;
}
// Relative time, millisecond to hours.
r.4 = ((millisecond - start_value) as f64) / (60 * 60 * 1000) as f64;
if next_val.is_infinite() {
r.3 = next_val;
} else {
last = last * smoothing + (1.0 - smoothing) * next_val;
num_accum += 1;
let mut debias_weight: f64 = 1.0_f64;
if smoothing != 1.0 {
debias_weight = (1.0_f64 - smoothing.powi(num_accum)).into();
}
r.3 = last / debias_weight;
}
row.push(r);
}
result.push(row);
}
return result;
}
pub fn range(datasets: Vec<Vec<Smoothed>>, outlier: bool) -> Range {
let mut ranges: Vec<Range> = vec![];
for data in datasets.iter() {
let n: usize = data.len();
if n == 0 {
continue;
}
let values: Vec<f64> = data.iter().map(|x| x.2).collect();
let mut sorted: Vec<f64> = values.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
if !outlier {
ranges.push(Range::new(sorted[0], sorted[n - 1]));
} else {
ranges.push(Range::new(
quantile(sorted, 0.05_f64),
quantile(values, 0.95),
));
}
}
let mut min: f64 = 0.;
let mut max: f64 = 1.;
if ranges.len() != 0 {
min = ranges
.iter()
.min_by(|x, y| x.min.partial_cmp(&y.max).unwrap())
.unwrap()
.min;
max = ranges
.iter()
.max_by(|x, y| x.max.partial_cmp(&y.max).unwrap())
.unwrap()
.max;
if min > 0. {
min *= 0.9;
} else {
min *= 1.1;
}
if max > 0. {
max *= 1.1;
} else {
max *= 0.9;
}
}
return Range::new(min, max);
}
......@@ -2334,6 +2334,11 @@
dependencies:
"@types/node" "*"
"@types/d3-format@1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-1.3.1.tgz#35bf88264bd6bcda39251165bb827f67879c4384"
integrity sha512-KAWvReOKMDreaAwOjdfQMm0HjcUMlQG47GwqdVKgmm20vTd2pucj0a70c3gUSHrnsmo6H2AMrkBsZU2UhJLq8A==
"@types/echarts@4.6.1":
version "4.6.1"
resolved "https://registry.yarnpkg.com/@types/echarts/-/echarts-4.6.1.tgz#de6ede6b8069123150d53f3350f9e38533f1970e"
......@@ -4955,7 +4960,7 @@ d3-force@1:
d3-quadtree "1"
d3-timer "1"
d3-format@1:
d3-format@1, d3-format@1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.4.tgz#356925f28d0fd7c7983bfad593726fce46844030"
integrity sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw==
......@@ -10794,11 +10799,6 @@ react-dom@16.13.1:
prop-types "^15.6.2"
scheduler "^0.19.1"
react-hooks-worker@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/react-hooks-worker/-/react-hooks-worker-0.9.0.tgz#cf6e481711045d539368c83ba0fa42bd97c71a09"
integrity sha512-aDKlrc9Dh8O0Wag2mWbNpuXbTB/kX1tGzq74bFdxSfKg6KHvF9ft789WpatmCBQbszdgXEi3pS/BCj698JXCJQ==
react-i18next@11.5.0:
version "11.5.0"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.5.0.tgz#84a9bb535d44c0c1b336b94de164515c2cc2a714"
......
......@@ -147,9 +147,9 @@ class Api(object):
return self._get_with_retry('data/plugin/histogram/tags', lib.get_histogram_tags)
@result()
def histogram_histogram(self, run, tag):
key = os.path.join('data/plugin/embeddings/embeddings', run, tag)
return self._get_with_retry(key, lib.get_embeddings, run, tag)
def histogram_list(self, run, tag):
key = os.path.join('data/plugin/histogram/histogram', run, tag)
return self._get_with_retry(key, lib.get_histogram, run, tag)
@result('application/octet-stream', lambda s: {"Content-Disposition": 'attachment; filename="%s"' % s.model_name} if len(s.model_name) else None)
def graphs_graph(self):
......@@ -175,7 +175,7 @@ def create_api_call(logdir, model, cache_timeout):
'audio/list': (api.audio_list, ['run', 'tag']),
'audio/audio': (api.audio_audio, ['run', 'tag', 'index']),
'embeddings/embedding': (api.embeddings_embedding, ['run', 'tag', 'reduction', 'dimension']),
'histogram/histogram': (api.histogram_histogram, ['run', 'tag']),
'histogram/list': (api.histogram_list, ['run', 'tag']),
'graphs/graph': (api.graphs_graph, [])
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册