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

chore: use wasm to handle datasets (#578)

上级 9fd2b6f5
......@@ -24,6 +24,7 @@ module.exports = {
version: 'detect'
}
},
ignorePatterns: ['node_modules/', 'dist/', 'out/', '_next', 'wasm/'],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'error',
......
import React, {FunctionComponent} from 'react';
import {useTranslation} from '~/utils/i18n';
import {NodeType, TypedNode} from '~/resource/graph';
import {NodeType, TypedNode} from '~/resource/graphs';
import styled from 'styled-components';
import {WithStyled} from '~/utils/style';
......
import React, {FunctionComponent, useMemo} from 'react';
import styled from 'styled-components';
import useSWR from 'swr';
import queryString from 'query-string';
import {rem, primaryColor} from '~/utils/style';
import useHeavyWork from '~/hooks/useHeavyWork';
import {divide, Dimension, Reduction, DivideParams, Point} from '~/resource/high-dimensional';
import ScatterChart from '~/components/ScatterChart';
const height = rem(600);
const divideWasm = () =>
import('~/wasm/pkg').then(({divide}) => (params: DivideParams) =>
(divide(params.points, params.labels, !!params.visibility, params.keyword ?? '') as unknown) as [
Point[],
Point[]
]
);
const divideWorker = () => new Worker('~/worker/high-dimensional/divide.worker.ts', {type: 'module'});
const StyledScatterChart = styled(ScatterChart)`
height: ${height};
`;
const label = {
show: true,
position: 'top',
formatter: (params: {data: {name: string; showing: boolean}}) => (params.data.showing ? params.data.name : '')
};
type Data = {
embedding: ([number, number] | [number, number, number])[];
labels: string[];
};
type HighDimensionalChartProps = {
run: string;
running?: boolean;
labelVisibility?: boolean;
reduction: Reduction;
keyword: string;
dimension: Dimension;
};
const HighDimensionalChart: FunctionComponent<HighDimensionalChartProps> = ({
run,
running,
labelVisibility,
keyword,
reduction,
dimension
}) => {
const {data, error} = useSWR<Data>(
`/embeddings/embedding?${queryString.stringify({
run: run ?? '',
dimension: Number.parseInt(dimension),
reduction
})}`,
{
refreshInterval: running ? 15 * 1000 : 0
}
);
const divideParams = useMemo(
() => ({
points: data?.embedding ?? [],
keyword,
labels: data?.labels ?? [],
visibility: labelVisibility
}),
[data, labelVisibility, keyword]
);
const points = useHeavyWork(divideWasm, divideWorker, divide, divideParams);
const chartData = useMemo(() => {
return [
{
name: 'highlighted',
data: points?.[0] ?? [],
label
},
{
name: 'others',
data: points?.[1] ?? [],
label,
color: primaryColor
}
];
}, [points]);
return <StyledScatterChart loading={!data && !error} data={chartData} gl={dimension === '3d'} />;
};
export default HighDimensionalChart;
......@@ -3,8 +3,8 @@ import {EChartOption} from 'echarts';
import {WithStyled} from '~/utils/style';
import {useTranslation} from '~/utils/i18n';
import useECharts from '~/hooks/useECharts';
import {formatTime} from '~/utils';
import * as chart from '~/utils/chart';
import {formatTime} from '~/utils/scalars';
type LineChartProps = {
title?: string;
......
......@@ -4,8 +4,8 @@ import useSWR from 'swr';
import queryString from 'query-string';
import {em, size, ellipsis, primaryColor, textLightColor} from '~/utils/style';
import GridLoader from 'react-spinners/GridLoader';
import StepSlider from '~/components/StepSlider';
import Image from '~/components/Image';
import StepSlider from '~/components/SamplesPage/StepSlider';
const width = em(430);
const height = em(384);
......
......@@ -2,41 +2,44 @@ import React, {FunctionComponent, useCallback, useMemo} from 'react';
import styled from 'styled-components';
import useSWR from 'swr';
import queryString from 'query-string';
import compact from 'lodash/compact';
import minBy from 'lodash/minBy';
import maxBy from 'lodash/maxBy';
import sortBy from 'lodash/sortBy';
import {EChartOption} from 'echarts';
import {em, size} from '~/utils/style';
import {useTranslation} from '~/utils/i18n';
import useHeavyWork from '~/hooks/useHeavyWork';
import {cycleFetcher} from '~/utils/fetch';
import {transform, range, tooltip, TooltipData} from '~/utils/scalars';
import * as chart from '~/utils/chart';
import {
transform,
chartData,
range,
tooltip,
sortingMethodMap,
xAxisMap,
Dataset,
Range,
TransformParams,
RangeParams
} from '~/resource/scalars';
import LineChart from '~/components/LineChart';
const width = em(430);
const height = em(320);
const smoothWasm = () =>
import('~/wasm/pkg').then(({transform}) => (params: TransformParams) =>
(transform(params.datasets, params.smoothing) as unknown) as Dataset[]
);
const rangeWasm = () =>
import('~/wasm/pkg').then(({range}) => (params: RangeParams) =>
(range(params.datasets, params.outlier) as unknown) as Range
);
const smoothWorker = () => new Worker('~/worker/scalars/smooth.worker.ts', {type: 'module'});
const rangeWorker = () => new Worker('~/worker/scalars/range.worker.ts', {type: 'module'});
const StyledLineChart = styled(LineChart)`
${size(height, width)}
`;
export const xAxisMap = {
step: 1,
relative: 4,
wall: 0
};
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])
};
type DataSet = number[][];
type ScalarChartProps = {
runs: string[];
tag: string;
......@@ -59,7 +62,7 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
const {t, i18n} = useTranslation(['scalars', 'common']);
// TODO: maybe we can create a custom hook here
const {data: datasets, error} = useSWR<DataSet[]>(
const {data: datasets, error} = useSWR<Dataset[]>(
runs.map(run => `/scalars/list?${queryString.stringify({run, tag})}`),
(...urls) => cycleFetcher(urls),
{
......@@ -70,66 +73,35 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
const type = xAxis === 'wall' ? 'time' : 'value';
const smooth = xAxis !== 'wall';
const smoothedDatasets = useMemo(() => datasets?.map(dataset => transform(dataset, smoothing)), [
datasets,
smoothing
]);
const transformParams = useMemo(
() => ({
datasets: datasets ?? [],
smoothing
}),
[datasets, smoothing]
);
const smoothedDatasets = useHeavyWork(smoothWasm, smoothWorker, transform, transformParams) ?? [];
const rangeParams = useMemo(
() => ({
datasets: smoothedDatasets,
outlier: !!outlier
}),
[smoothedDatasets, outlier]
);
const yRange = useHeavyWork(rangeWasm, rangeWorker, range, rangeParams);
const data = useMemo(
() =>
smoothedDatasets
?.map((dataset, i) => {
// smoothed data:
// [0] wall time
// [1] step
// [2] orginal value
// [3] smoothed value
// [4] relative
const name = runs[i];
return [
{
name,
z: i,
lineStyle: {
width: chart.series.lineStyle.width,
opacity: 0.5
},
data: dataset,
encode: {
x: [xAxisMap[xAxis]],
y: [2]
},
smooth
},
{
name,
z: runs.length + i,
data: dataset,
encode: {
x: [xAxisMap[xAxis]],
y: [3]
},
smooth
}
];
})
.flat(),
[runs, smooth, smoothedDatasets, xAxis]
chartData({
data: smoothedDatasets,
runs,
smooth,
xAxis
}),
[smoothedDatasets, runs, smooth, xAxis]
);
const yRange = useMemo(() => {
const ranges = compact(smoothedDatasets?.map(dataset => range(dataset, outlier)));
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
};
}
}, [outlier, smoothedDatasets]);
const formatter = useCallback(
(params: EChartOption.Tooltip.Format | EChartOption.Tooltip.Format[]) => {
const data = Array.isArray(params) ? params[0].data : params.data;
......
import React, {FunctionComponent, useEffect, useMemo} from 'react';
import {EChartOption} from 'echarts';
import {WithStyled, primaryColor} from '~/utils/style';
import {WithStyled} from '~/utils/style';
import useECharts from '~/hooks/useECharts';
import {Dimension} from '~/types';
const SYMBOL_SIZE = 12;
type Point = {
name: string;
value: [number, number] | [number, number, number];
};
const options2D = {
xAxis: {},
yAxis: {},
toolbox: {
show: true,
showTitle: false,
itemSize: 0,
type ScatterChartProps = {
keyword: string;
loading: boolean;
points: Point[];
dimension: Dimension;
feature: {
dataZoom: {},
restore: {},
saveAsImage: {}
}
}
};
const assemble2D = (points: {highlighted: Point[]; others: Point[]}, label: EChartOption.SeriesBar['label']) => {
// eslint-disable-next-line
const createSeries = (name: string, data: Point[], patch?: {[k: string]: any}) => ({
name,
symbolSize: SYMBOL_SIZE,
data,
type: 'scatter',
label,
...(patch || {})
});
return {
xAxis: {},
yAxis: {},
toolbox: {
show: true,
showTitle: false,
itemSize: 0,
feature: {
dataZoom: {},
restore: {},
saveAsImage: {}
}
},
series: [
createSeries('highlighted', points.highlighted),
createSeries('others', points.others, {color: primaryColor})
]
};
const options3D = {
grid3D: {},
xAxis3D: {},
yAxis3D: {},
zAxis3D: {}
};
const assemble3D = (points: {highlighted: Point[]; others: Point[]}, label: EChartOption.SeriesBar['label']) => {
// eslint-disable-next-line
const createSeries = (name: string, data: Point[], patch?: {[k: string]: any}) => ({
name,
symbolSize: SYMBOL_SIZE,
data,
type: 'scatter3D',
label,
...(patch || {})
});
return {
grid3D: {},
xAxis3D: {},
yAxis3D: {},
zAxis3D: {},
series: [
createSeries('highlighted', points.highlighted),
createSeries('others', points.others, {color: primaryColor})
]
};
const series2D = {
symbolSize: SYMBOL_SIZE,
type: 'scatter'
};
const getChartOptions = (
settings: Pick<ScatterChartProps, 'dimension'> & {points: {highlighted: Point[]; others: Point[]}}
) => {
const {dimension, points} = settings;
const label = {
show: true,
position: 'top',
formatter: (params: {data: {name: string; showing: boolean}}) => (params.data.showing ? params.data.name : '')
};
const assemble = dimension === '2d' ? assemble2D : assemble3D;
return assemble(points, label);
const series3D = {
symbolSize: SYMBOL_SIZE,
type: 'scatter3D'
};
const dividePoints = (points: Point[], keyword: string): [Point[], Point[]] => {
if (!keyword) {
return [[], points];
}
const matched: Point[] = [];
const missing: Point[] = [];
points.forEach(point => {
if (point.name.includes(keyword)) {
matched.push(point);
return;
}
missing.push(point);
});
return [matched, missing];
type ScatterChartProps = {
loading?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data?: Record<string, any>[];
gl?: boolean;
};
const ScatterChart: FunctionComponent<ScatterChartProps & WithStyled> = ({
points,
keyword,
loading,
dimension,
className
}) => {
const ScatterChart: FunctionComponent<ScatterChartProps & WithStyled> = ({data, loading, gl, className}) => {
const {ref, echart} = useECharts<HTMLDivElement>({
loading,
gl: dimension === '3d'
gl
});
const [highlighted, others] = useMemo(() => dividePoints(points, keyword), [points, keyword]);
const chartOptions = useMemo(
() =>
getChartOptions({
dimension,
points: {
highlighted,
others
}
}),
[dimension, highlighted, others]
() => ({
...(gl ? options3D : options2D),
series:
data?.map(series => ({
...(gl ? series3D : series2D),
...series
})) ?? []
}),
[gl, data]
);
useEffect(() => {
if (process.browser) {
echart?.current?.setOption(chartOptions, {notMerge: true});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
echart?.current?.setOption(chartOptions as any, {notMerge: true});
}
}, [chartOptions, echart]);
......
import {useEffect, useRef, useState, useCallback} from 'react';
const useHeavyWork = <T = unknown, P = unknown>(
createWasm: (() => Promise<(arg: P) => T> | null) | null,
createWorker: (() => Worker | null) | null,
fallback: ((arg: P) => T) | null,
params: P
) => {
const wasm = useRef(null as Promise<(arg: P) => T> | null);
const worker = useRef(null as Worker | null);
const [result, setResult] = useState(undefined as T | undefined);
const runFallback = useCallback((p: P) => fallback && setResult(fallback(p)), [fallback]);
useEffect(() => {
if (process.browser) {
try {
if (createWasm && typeof WebAssembly !== 'undefined') {
if (!wasm.current) {
wasm.current = createWasm();
}
wasm.current
?.then((work: (arg: P) => T) => setResult(work(params)))
.catch(() => runFallback(params));
return;
}
if (createWorker && typeof Worker !== 'undefined') {
if (!worker.current) {
worker.current = createWorker();
}
worker.current?.postMessage(params);
worker.current?.addEventListener('message', ({data}: MessageEvent & {data: T}) => setResult(data));
worker.current?.addEventListener('error', () => runFallback(params));
return;
}
} catch (e) {
if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line no-console
console.error('Error during heavy work, trying to use fallback');
// eslint-disable-next-line no-console
console.error(e);
}
runFallback(params);
}
}
runFallback(params);
}, [createWasm, createWorker, runFallback, params]);
return result;
};
export default useHeavyWork;
module.exports = {
'**/*.ts?(x)': filenames => [
'tsc -p tsconfig.json --noEmit',
'tsc -p tsconfig.server.json --noEmit',
'tsc -p server/tsconfig.json --noEmit',
`eslint ${filenames.join(' ')}`
],
'**/*.js?(x)': filenames => `eslint ${filenames.join(' ')}`
......
......@@ -27,29 +27,55 @@ module.exports = {
poweredByHeader: false,
env: {
...APP,
BUILD_ID: '',
DEFAULT_LANGUAGE,
LOCALE_PATH,
LANGUAGES,
PUBLIC_PATH: publicPath,
API_URL: apiUrl
},
exportPathMap: defaultPathMap => {
return {
...defaultPathMap,
...Object.entries(defaultPathMap).reduce((prev, [path, router]) => {
otherLanguages.forEach(lang => (prev[`/${lang}${path}`] = router));
return prev;
}, {})
};
},
exportPathMap: defaultPathMap => ({
...defaultPathMap,
...Object.entries(defaultPathMap).reduce((prev, [path, router]) => {
otherLanguages.forEach(lang => (prev[`/${lang}${path}`] = router));
return prev;
}, {})
}),
experimental: {
basePath: publicPath
},
webpack: config => {
webpack: (config, {dev, webpack}) => {
const WorkerPlugin = require('worker-plugin');
config.output.webassemblyModuleFilename = 'static/wasm/[modulehash].wasm';
config.resolve = config.resolve || {};
config.resolve.alias = config.resolve.alias || {};
config.resolve.alias['~'] = path.resolve(__dirname);
config.node = Object.assign({}, config.node, {
// eslint-disable-next-line @typescript-eslint/camelcase
child_process: 'empty',
fs: 'empty'
});
config.plugins = [
...(config.plugins || []),
new WorkerPlugin({
globalObject: 'self'
})
];
if (!dev || !!process.env.WITH_WASM) {
const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin');
config.plugins.push(
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, 'wasm')
})
);
} else {
config.plugins.push(new webpack.IgnorePlugin(/^~\/wasm\//));
}
return config;
}
};
......@@ -24,13 +24,15 @@
"directory": "frontend"
},
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon --watch server --ext ts --exec \"ts-node --project=tsconfig.server.json\" server/index.ts",
"dev": "cross-env NODE_ENV=development nodemon --watch server --ext ts --exec \"ts-node --project server/tsconfig.json\" server/index.ts",
"dev:wasm": "cross-env NODE_ENV=development WITH_WASM=1 nodemon --watch server --ext ts --exec \"ts-node --project server/tsconfig.json\" server/index.ts",
"build:next": "next build",
"build:server": "tsc --project tsconfig.server.json",
"build:server": "tsc --project server/tsconfig.json",
"build:wasm": "wasm-pack build wasm",
"build": "./scripts/build.sh",
"export": "yarn build:next && next export",
"start": "NODE_ENV=production node dist/server/index.js",
"lint": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.server.json --noEmit && eslint --ext .tsx,.jsx.ts,.js --ignore-path .gitignore .",
"lint": "tsc -p tsconfig.json --noEmit && tsc -p server/tsconfig.json --noEmit && eslint --ext .tsx,.jsx.ts,.js --ignore-path .gitignore .",
"format": "prettier --write \"**/*.ts\" \"**/*.tsx\" \"**/*.js\" \"**/*.jsx\"",
"test": "echo \"Error: no test specified\" && exit 0"
},
......@@ -58,10 +60,11 @@
"query-string": "6.11.1",
"react": "16.13.0",
"react-dom": "16.13.0",
"react-hooks-worker": "0.9.0",
"react-i18next": "11.3.3",
"react-input-range": "1.3.0",
"react-is": "16.13.0",
"react-spinners": "0.8.0",
"react-spinners": "0.8.1",
"save-svg-as-png": "1.4.17",
"styled-components": "5.0.1",
"swr": "0.1.18",
......@@ -76,7 +79,7 @@
"@types/express": "4.17.3",
"@types/faker": "4.1.10",
"@types/lodash": "4.14.149",
"@types/node": "13.7.7",
"@types/node": "13.9.0",
"@types/nprogress": "0.2.0",
"@types/react": "16.9.23",
"@types/react-dom": "16.9.5",
......@@ -85,14 +88,16 @@
"@types/yargs": "15.0.4",
"@typescript-eslint/eslint-plugin": "2.22.0",
"@typescript-eslint/parser": "2.22.0",
"@wasm-tool/wasm-pack-plugin": "1.1.0",
"babel-plugin-emotion": "10.0.29",
"babel-plugin-styled-components": "1.10.7",
"babel-plugin-typescript-to-proptypes": "1.3.0",
"core-js": "3",
"cross-env": "7.0.2",
"eslint": "6.8.0",
"eslint-config-prettier": "6.10.0",
"eslint-plugin-prettier": "3.1.2",
"eslint-plugin-react": "7.18.3",
"eslint-plugin-react": "7.19.0",
"eslint-plugin-react-hooks": "2.5.0",
"faker": "4.1.0",
"husky": "4.2.3",
......@@ -101,7 +106,8 @@
"prettier": "1.19.1",
"ts-node": "8.6.2",
"typescript": "3.8.3",
"yarn": "1.22.0"
"worker-plugin": "3.2.0",
"yarn": "1.22.1"
},
"engines": {
"node": ">=10",
......
......@@ -9,9 +9,9 @@ import Content from '~/components/Content';
import Title from '~/components/Title';
import Field from '~/components/Field';
import {useTranslation, NextI18NextPage} from '~/utils/i18n';
import NodeInfo, {NodeInfoProps} from '~/components/GraphPage/NodeInfo';
import NodeInfo, {NodeInfoProps} from '~/components/GraphsPage/NodeInfo';
import Preloader from '~/components/Preloader';
import {Graph, collectDagFacts} from '~/resource/graph';
import {Graph, collectDagFacts} from '~/resource/graphs';
// eslint-disable-next-line @typescript-eslint/no-empty-function
const dumbFn = () => {};
......
import React, {useState, useEffect, useMemo} from 'react';
import React, {useState, useEffect} from 'react';
import styled from 'styled-components';
import useSWR from 'swr';
import queryString from 'query-string';
import {useRouter} from 'next/router';
import useSearchValue from '~/hooks/useSearchValue';
import {rem, em} from '~/utils/style';
......@@ -15,11 +14,11 @@ import Checkbox from '~/components/Checkbox';
import RadioGroup from '~/components/RadioGroup';
import RadioButton from '~/components/RadioButton';
import RunningToggle from '~/components/RunningToggle';
import ScatterChart from '~/components/ScatterChart';
import Select, {SelectValueType} from '~/components/Select';
import AsideDivider from '~/components/AsideDivider';
import Preloader from '~/components/Preloader';
import {Dimension} from '~/types';
import HighDimensionalChart from '~/components/HighDimensionalPage/HighDimensionalChart';
import {Dimension, Reduction} from '~/resource/high-dimensional';
const dimensions = ['2d', '3d'];
const reductions = ['pca', 'tsne'];
......@@ -36,15 +35,6 @@ const AsideTitle = styled.div`
margin-bottom: ${rem(10)};
`;
const StyledScatterChart = styled(ScatterChart)`
height: ${rem(600)};
`;
type Data = {
embedding: ([number, number] | [number, number, number])[];
labels: string[];
};
const HighDimensional: NextI18NextPage = () => {
const {t} = useTranslation(['high-dimensional', 'common']);
......@@ -59,36 +49,10 @@ const HighDimensional: NextI18NextPage = () => {
const [search, setSearch] = useState('');
const debouncedSearch = useSearchValue(search);
const [dimension, setDimension] = useState(dimensions[0] as Dimension);
const [reduction, setReduction] = useState(reductions[0]);
const [reduction, setReduction] = useState(reductions[0] as Reduction);
const [running, setRunning] = useState(true);
const [labelVisibility, setLabelVisibility] = useState(true);
const {data, error} = useSWR<Data>(
`/embeddings/embedding?${queryString.stringify({
run: run ?? '',
dimension: Number.parseInt(dimension),
reduction
})}`,
{
refreshInterval: running ? 15 * 1000 : 0
}
);
const points = useMemo(() => {
if (!data) {
return [];
}
const {embedding, labels} = data;
return embedding.map((value, i) => {
const name = labels[i] || '';
return {
name,
showing: labelVisibility,
value
};
});
}, [data, labelVisibility]);
const aside = (
<section>
<AsideTitle>{t('common:select-runs')}</AsideTitle>
......@@ -126,7 +90,7 @@ const HighDimensional: NextI18NextPage = () => {
{t('reduction-method')}
</AsideTitle>
<Field>
<RadioGroup value={reduction} onChange={value => setReduction(value as string)}>
<RadioGroup value={reduction} onChange={value => setReduction(value as Reduction)}>
{reductions.map(item => (
<RadioButton key={item} value={item}>
{t(item)}
......@@ -143,11 +107,13 @@ const HighDimensional: NextI18NextPage = () => {
<Preloader url="/runs" />
<Title>{t('common:high-dimensional')}</Title>
<Content aside={aside} loading={!runs}>
<StyledScatterChart
points={points}
<HighDimensionalChart
dimension={dimension}
loading={!data && !error}
keyword={debouncedSearch}
run={run ?? ''}
running={running}
labelVisibility={labelVisibility}
reduction={reduction}
/>
</Content>
</>
......
......@@ -13,7 +13,7 @@ import Checkbox from '~/components/Checkbox';
import RunningToggle from '~/components/RunningToggle';
import AsideDivider from '~/components/AsideDivider';
import ChartPage from '~/components/ChartPage';
import SampleChart from '~/components/SampleChart';
import SampleChart from '~/components/SamplesPage/SampleChart';
import Preloader from '~/components/Preloader';
const StyledIcon = styled(Icon)`
......
......@@ -9,12 +9,13 @@ import TagFilter from '~/components/TagFilter';
import Select, {SelectValueType} from '~/components/Select';
import Field from '~/components/Field';
import Checkbox from '~/components/Checkbox';
import SmoothingSlider from '~/components/SmoothingSlider';
import RunningToggle from '~/components/RunningToggle';
import AsideDivider from '~/components/AsideDivider';
import ChartPage from '~/components/ChartPage';
import ScalarChart, {xAxisMap, sortingMethodMap} from '~/components/ScalarChart';
import SmoothingSlider from '~/components/ScalarsPage/SmoothingSlider';
import ScalarChart from '~/components/ScalarsPage/ScalarChart';
import Preloader from '~/components/Preloader';
import {xAxisMap, sortingMethodMap} from '~/resource/scalars';
import {Tag} from '~/types';
type XAxis = keyof typeof xAxisMap;
......
import {Point, DivideParams} from './types';
export * from './types';
const dividePoints = (points: Point[], keyword?: string) => {
if (!keyword) {
return [[], points];
}
const matched: Point[] = [];
const missing: Point[] = [];
points.forEach(point => {
if (point.name.includes(keyword)) {
matched.push(point);
return;
}
missing.push(point);
});
return [matched, missing];
};
const combineLabel = (points: Point['value'][], labels: string[], visibility?: boolean) =>
points.map((value, i) => {
const name = labels[i] || '';
return {
name,
showing: !!visibility,
value
};
});
export const divide = ({points, keyword, labels, visibility}: DivideParams) =>
dividePoints(combineLabel(points, labels, visibility), keyword);
export type Dimension = '2d' | '3d';
export type Reduction = 'pca' | 'tsne';
export type Point = {
name: string;
value: [number, number] | [number, number, number];
showing: boolean;
};
export type DivideParams = {
points: Point['value'][];
keyword?: string;
labels: string[];
visibility?: boolean;
};
......@@ -2,89 +2,124 @@ import cloneDeep from 'lodash/cloneDeep';
import minBy from 'lodash/minBy';
import maxBy from 'lodash/maxBy';
import sortBy from 'lodash/sortBy';
import moment from 'moment';
import compact from 'lodash/compact';
import {formatTime, quantile} from '~/utils';
import * as chart from '~/utils/chart';
import {I18n} from '~/utils/i18next/types';
import {xAxisMap, TooltipData, TransformParams, ChartDataParams, RangeParams} from './types';
// https://en.wikipedia.org/wiki/Moving_average
export const transform = (seriesData: number[][], smoothingWeight: number) => {
const data: number[][] = cloneDeep(seriesData);
let last = data.length > 0 ? 0 : Number.NaN;
let numAccum = 0;
let startValue = 0;
data.forEach((d, i) => {
const nextVal = 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 (!isFinite(nextVal)) {
d[3] = nextVal;
} else {
last = last * smoothingWeight + (1 - smoothingWeight) * nextVal;
numAccum++;
let debiasWeight = 1;
if (smoothingWeight !== 1.0) {
debiasWeight = 1.0 - Math.pow(smoothingWeight, numAccum);
export * from './types';
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 = data.length > 0 ? 0 : Number.NaN;
let numAccum = 0;
let startValue = 0;
data.forEach((d, i) => {
const nextVal = d[2];
// second to millisecond.
const millisecond = (d[0] = Math.floor(d[0] * 1000));
if (i === 0) {
startValue = millisecond;
}
d[3] = last / debiasWeight;
}
// Relative time, millisecond to hours.
d[4] = Math.floor(millisecond - startValue) / (60 * 60 * 1000);
if (!isFinite(nextVal)) {
d[3] = nextVal;
} else {
last = last * smoothing + (1 - smoothing) * nextVal;
numAccum++;
let debiasWeight = 1;
if (smoothing !== 1.0) {
debiasWeight = 1.0 - Math.pow(smoothing, numAccum);
}
d[3] = last / debiasWeight;
}
});
return data;
});
return data;
};
export const quantile = (
values: number[][],
p: number,
valueOf: (value: number[], index: number, values: number[][]) => number
) => {
const n = values.length;
if (!n) {
return NaN;
}
if ((p = +p) <= 0 || n < 2) {
return valueOf(values[0], 0, values);
}
if (p >= 1) {
return valueOf(values[n - 1], n - 1, values);
}
const i = (n - 1) * p;
const i0 = Math.floor(i);
const value0 = valueOf(values[i0], i0, values);
const value1 = valueOf(values[i0 + 1], i0 + 1, values);
return value0 + (value1 - value0) * (i - i0);
};
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];
return [
{
name,
z: i,
lineStyle: {
width: chart.series.lineStyle.width,
opacity: 0.5
},
data: dataset,
encode: {
x: [xAxisMap[xAxis]],
y: [2]
},
smooth
},
{
name,
z: runs.length + i,
data: dataset,
encode: {
x: [xAxisMap[xAxis]],
y: [3]
},
smooth
}
];
})
.flat();
export const range = (seriesData: number[][], outlier = false) => {
if (seriesData.length == 0) return;
if (!outlier) {
// Get the orgin data range.
return {
min: minBy(seriesData, items => items[2])?.[2] ?? 0,
max: maxBy(seriesData, items => items[2])?.[2] ?? 0
};
} else {
// Get the quantile range.
const sorted = sortBy(seriesData, [item => item[2]]);
export const range = ({datasets, outlier}: RangeParams) => {
const ranges = compact(
datasets?.map(dataset => {
if (dataset.length == 0) return;
if (!outlier) {
// Get the orgin data range.
return {
min: minBy(dataset, items => items[2])?.[2] ?? 0,
max: maxBy(dataset, items => items[2])?.[2] ?? 0
};
} else {
// Get the quantile range.
const sorted = sortBy(dataset, [item => item[2]]);
return {
min: quantile(sorted, 0.05, item => item[2]),
max: quantile(dataset, 0.95, item => item[2])
};
}
})
);
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: quantile(sorted, 0.05, item => item[2]),
max: quantile(seriesData, 0.95, item => item[2])
min: min > 0 ? min * 0.9 : min * 1.1,
max: max > 0 ? max * 1.1 : max * 0.9
};
}
};
export type TooltipData = {
run: string;
item: number[];
};
export const formatTime = (value: number, language: string, formatter = 'L LTS') =>
moment(Math.floor(value), 'x')
.locale(language)
.format(formatter);
// TODO: make it better, don't concat html
export const tooltip = (data: TooltipData[], i18n: I18n) => {
const indexPropMap = {
......
export type Dataset = number[][];
export type Range = {
min: number;
max: number;
};
export const xAxisMap = {
step: 1,
relative: 4,
wall: 0
};
export type TooltipData = {
run: string;
item: number[];
};
export type TransformParams = {
datasets: Dataset[];
smoothing: number;
};
export type ChartDataParams = {
data: Dataset[];
runs: string[];
smooth: boolean;
xAxis: keyof typeof xAxisMap;
};
export type RangeParams = {
datasets: Dataset[];
outlier: boolean;
};
......@@ -37,6 +37,11 @@ const handle = app.getRequestHandler();
await nextI18next.initPromise;
server.use(nextI18NextMiddleware(nextI18next));
server.get(/\.wasm/, (_req, res, next) => {
res.type('application/wasm');
next();
});
server.get('*', (req, res) => handle(req, res));
server.listen(port);
......
{
"extend": "./tsconfig.json",
"extend": "../tsconfig.json",
"compilerOptions": {
"jsx": "react",
"module": "commonjs",
"target": "ES2018",
"esModuleInterop": true,
"typeRoots": ["types"],
"outDir": "dist"
"typeRoots": ["../types"],
"outDir": "../dist"
},
"include": [
"server/**/*"
"**/*"
]
}
......@@ -52,3 +52,8 @@ declare module '*.module.sass' {
const classes: {readonly [key: string]: string};
export default classes;
}
declare module '*.wasm' {
const wasm: Record<string, Function>;
export default wasm;
}
......@@ -2,5 +2,3 @@ export interface Tag {
runs: string[];
label: string;
}
export type Dimension = '2d' | '3d';
declare module '*.worker.ts' {
class WebpackWorker extends Worker {
constructor();
}
export default WebpackWorker;
}
......@@ -44,19 +44,19 @@ export const consoleMessage = function(
}
/*
Temporarily set the stacktrace to 0 or errorStackTraceLimit,
in order to only display a message
*/
Temporarily set the stacktrace to 0 or errorStackTraceLimit,
in order to only display a message
*/
Error.stackTraceLimit = errorStackTraceLimit || 0;
/*
Make room for new message
*/
Make room for new message
*/
console.log();
/*
Make sure the message is a string
*/
Make sure the message is a string
*/
if (typeof message !== 'string') {
const metaError = new Error();
metaError.name = 'Meta';
......@@ -74,12 +74,12 @@ export const consoleMessage = function(
}
/*
Log the message to console
*/
Log the message to console
*/
logMessage(messageType, message);
/*
Reset stack limit
*/
Reset stack limit
*/
Error.stackTraceLimit = prevStackLimit;
};
import moment from 'moment';
export const formatTime = (value: number, language: string, formatter = 'L LTS') =>
moment(Math.floor(value), 'x')
.locale(language)
.format(formatter);
export const quantile = (
values: number[][],
p: number,
valueOf: (value: number[], index: number, values: number[][]) => number
) => {
const n = values.length;
if (!n) {
return NaN;
}
if ((p = +p) <= 0 || n < 2) {
return valueOf(values[0], 0, values);
}
if (p >= 1) {
return valueOf(values[n - 1], n - 1, values);
}
const i = (n - 1) * p;
const i0 = Math.floor(i);
const value0 = valueOf(values[i0], i0, values);
const value1 = valueOf(values[i0 + 1], i0 + 1, values);
return value0 + (value1 - value0) * (i - i0);
};
export const enabled = () => process.env.NODE_ENV !== 'development' || !!process.env.WITH_WASM;
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "bumpalo"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "console_error_panic_hook"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]]
name = "futures"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
[[package]]
name = "itoa"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
[[package]]
name = "js-sys"
version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cb931d43e71f560c81badb0191596562bafad2be06a3f9025b845c847c60df5"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
dependencies = [
"cfg-if",
]
[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
dependencies = [
"unicode-xid 0.1.0",
]
[[package]]
name = "proc-macro2"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
dependencies = [
"unicode-xid 0.2.0",
]
[[package]]
name = "quote"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
dependencies = [
"proc-macro2 0.4.30",
]
[[package]]
name = "quote"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
dependencies = [
"proc-macro2 1.0.9",
]
[[package]]
name = "ryu"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]]
name = "serde"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
[[package]]
name = "serde_derive"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
dependencies = [
"proc-macro2 1.0.9",
"quote 1.0.3",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
dependencies = [
"proc-macro2 1.0.9",
"quote 1.0.3",
"unicode-xid 0.2.0",
]
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "visualdl"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"futures",
"js-sys",
"serde",
"serde_derive",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test",
"web-sys",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3557c397ab5a8e347d434782bcd31fc1483d927a6826804cec05cc792ee2519d"
dependencies = [
"cfg-if",
"serde",
"serde_json",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0da9c9a19850d3af6df1cb9574970b566d617ecfaf36eb0b706b6f3ef9bd2f8"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2 1.0.9",
"quote 1.0.3",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83420b37346c311b9ed822af41ec2e82839bfe99867ec6c54e2da43b7538771c"
dependencies = [
"cfg-if",
"futures",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f6fde1d36e75a714b5fe0cffbb78978f222ea6baebb726af13c78869fdb4205"
dependencies = [
"quote 1.0.3",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25bda4168030a6412ea8a047e27238cadf56f0e53516e1e83fec0a8b7c786f6d"
dependencies = [
"proc-macro2 1.0.9",
"quote 1.0.3",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc9f36ad51f25b0219a3d4d13b90eb44cd075dff8b6280cca015775d7acaddd8"
[[package]]
name = "wasm-bindgen-test"
version = "0.2.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2d9693b63a742d481c7f80587e057920e568317b2806988c59cd71618bc26c1"
dependencies = [
"console_error_panic_hook",
"futures",
"js-sys",
"scoped-tls",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.2.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0789dac148a8840bbcf9efe13905463b733fa96543bfbf263790535c11af7ba5"
dependencies = [
"proc-macro2 0.4.30",
"quote 0.6.13",
]
[[package]]
name = "web-sys"
version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "721c6263e2c66fd44501cc5efbfa2b7dfa775d13e4ea38c46299646ed1f9c70a"
dependencies = [
"js-sys",
"wasm-bindgen",
]
# You must change these to your own details.
[package]
name = "visualdl"
description = "VisualDL WASM"
version = "0.1.0"
publish = false
repository = "https://github.com/PaddlePaddle/VisualDL.git"
authors = ["PeterPanZH <littlepanzh@gmail.com>"]
categories = ["wasm"]
readme = "README.md"
license = "Apache 2.0"
edition = "2018"
[lib]
path = "src/main.rs"
crate-type = ["cdylib"]
[profile.release]
# This makes the compiled code faster and smaller, but it makes compiling slower,
# so it's only enabled in release mode.
lto = true
[features]
# If you uncomment this line, it will enable `wee_alloc`:
# default = ["wee_alloc"]
[dependencies]
serde = "^1.0.59"
serde_derive = "^1.0.59"
# The `wasm-bindgen` crate provides the bare minimum functionality needed
# to interact with JavaScript.
[dependencies.wasm-bindgen]
version = "0.2.45"
features = ["serde-serialize"]
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. However, it is slower than the default
# allocator, so it's not enabled by default.
# wee_alloc = { version = "0.4.2", optional = true }
# The `web-sys` crate allows you to interact with the various browser APIs,
# like the DOM.
[dependencies.web-sys]
version = "0.3.22"
features = ["console"]
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so it's only enabled
# in debug mode.
[target."cfg(debug_assertions)".dependencies]
console_error_panic_hook = "0.1.5"
# These crates are used for running unit tests.
[dev-dependencies]
wasm-bindgen-test = "0.2.45"
futures = "0.1.27"
js-sys = "0.3.22"
wasm-bindgen-futures = "0.3.22"
mod utils;
use wasm_bindgen::prelude::*;
#[macro_use]
extern crate serde_derive;
#[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 {
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);
}
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 {
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);
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,
}
}
}
impl DividedPoints {
pub fn new(matched: Vec<Point>, missing: Vec<Point>) -> Self {
DividedPoints(matched, missing)
}
}
#[wasm_bindgen]
pub fn 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);
return JsValue::from_serde(&result).unwrap();
}
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
import {exposeWorker} from 'react-hooks-worker';
import {divide, DivideParams} from '~/resource/high-dimensional';
// exposeWorker can only handle Promise
exposeWorker((data: DivideParams) => Promise.resolve(divide(data)));
import {exposeWorker} from 'react-hooks-worker';
import {range, RangeParams} from '~/resource/scalars';
// exposeWorker can only handle Promise
exposeWorker((data: RangeParams) => Promise.resolve(range(data)));
import {exposeWorker} from 'react-hooks-worker';
import {transform, TransformParams} from '~/resource/scalars';
// exposeWorker can only handle Promise
exposeWorker((data: TransformParams) => Promise.resolve(transform(data)));
......@@ -876,6 +876,14 @@
core-js "^2.6.5"
regenerator-runtime "^0.13.2"
"@babel/runtime-corejs3@^7.8.3":
version "7.8.7"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.8.7.tgz#8209d9dff2f33aa2616cb319c83fe159ffb07b8c"
integrity sha512-sc7A+H4I8kTd7S61dgB9RomXu/C+F4IrRr4Ytze4dnfx7AXEpCrejSNpjx7vq6y/Bak9S6Kbk65a/WgMLtg43Q==
dependencies:
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4"
"@babel/runtime@7.7.2":
version "7.7.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.2.tgz#111a78002a5c25fc8e3361bedc9529c696b85a6a"
......@@ -883,7 +891,7 @@
dependencies:
regenerator-runtime "^0.13.2"
"@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4":
"@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.8.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.7.tgz#8fefce9802db54881ba59f90bb28719b4996324d"
integrity sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==
......@@ -1344,10 +1352,10 @@
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
"@types/node@*", "@types/node@13.7.7":
version "13.7.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.7.tgz#1628e6461ba8cc9b53196dfeaeec7b07fa6eea99"
integrity sha512-Uo4chgKbnPNlxQwoFmYIwctkQVkMMmsAoGGU4JKwLuvBefF0pCq4FybNSnfkfRCpC7ZW7kttcC/TrRtAJsvGtg==
"@types/node@*", "@types/node@13.9.0":
version "13.9.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.0.tgz#5b6ee7a77faacddd7de719017d0bc12f52f81589"
integrity sha512-0ARSQootUG1RljH2HncpsY2TJBfGQIKOOi7kxzUY6z54ePu/ZD+wJA8zI2Q6v8rol2qpG/rvqsReco8zNMPvhQ==
"@types/nprogress@0.2.0":
version "0.2.0"
......@@ -1507,6 +1515,15 @@
semver "^6.3.0"
tsutils "^3.17.1"
"@wasm-tool/wasm-pack-plugin@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@wasm-tool/wasm-pack-plugin/-/wasm-pack-plugin-1.1.0.tgz#94016deba0f59306d1a9f0cb3b15144d8cd9ab34"
integrity sha512-44vbq7MyZzavE7g5Q7RKlnFtI35BhUkNiUANTeOivbpRfsRw0d0n9lA2ytmiVS4O+AVRsjjPLVSv35kPvL+OWg==
dependencies:
chalk "^2.4.1"
command-exists "^1.2.7"
watchpack "^1.6.0"
"@webassemblyjs/ast@1.8.5":
version "1.8.5"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
......@@ -2665,6 +2682,11 @@ colors@1.1.2:
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
command-exists@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291"
integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw==
commander@2, commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
......@@ -2846,6 +2868,16 @@ core-js-compat@^3.1.1:
browserslist "^4.8.3"
semver "7.0.0"
core-js-pure@^3.0.0:
version "3.6.4"
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a"
integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==
core-js@3:
version "3.6.4"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647"
integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==
core-js@^2.4.0, core-js@^2.6.5:
version "2.6.11"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
......@@ -3620,9 +3652,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.322, electron-to-chromium@^1.3.363:
version "1.3.370"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.370.tgz#420fba483d30ba3f7965b30ecf850fdb5f08a0bc"
integrity sha512-399cXDE9C7qoVF2CUgCA/MLflfvxbo1F0kB/pkB94426freL/JgZ0HNaloomsOfnE+VC/qgTFZqzmivSdaNfPQ==
version "1.3.372"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.372.tgz#fb61b6dfe06f3278a384d084ebef75d463ec7580"
integrity sha512-77a4jYC52OdisHM+Tne7dgWEvQT1FoNu/jYl279pP88ZtG4ZRIPyhQwAKxj6C2rzsyC1OwsOds9JlZtNncSz6g==
elegant-spinner@^1.0.1:
version "1.0.1"
......@@ -3793,10 +3825,10 @@ eslint-plugin-react-hooks@2.5.0:
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.0.tgz#c50ab7ca5945ce6d1cf8248d9e185c80b54171b6"
integrity sha512-bzvdX47Jx847bgAYf0FPX3u1oxU+mKU8tqrpj4UX9A96SbAmj/HVEefEy6rJUog5u8QIlOPTKZcBpGn5kkKfAQ==
eslint-plugin-react@7.18.3:
version "7.18.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.18.3.tgz#8be671b7f6be095098e79d27ac32f9580f599bc8"
integrity sha512-Bt56LNHAQCoou88s8ViKRjMB2+36XRejCQ1VoLj716KI1MoE99HpTVvIThJ0rvFmG4E4Gsq+UgToEjn+j044Bg==
eslint-plugin-react@7.19.0:
version "7.19.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz#6d08f9673628aa69c5559d33489e855d83551666"
integrity sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==
dependencies:
array-includes "^3.1.1"
doctrine "^2.1.0"
......@@ -3806,8 +3838,10 @@ eslint-plugin-react@7.18.3:
object.fromentries "^2.0.2"
object.values "^1.1.1"
prop-types "^15.7.2"
resolve "^1.14.2"
resolve "^1.15.1"
semver "^6.3.0"
string.prototype.matchall "^4.0.2"
xregexp "^4.3.0"
eslint-scope@^4.0.3:
version "4.0.3"
......@@ -7394,6 +7428,11 @@ react-error-overlay@5.1.6:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.6.tgz#0cd73407c5d141f9638ae1e0c63e7b2bf7e9929d"
integrity sha512-X1Y+0jR47ImDVr54Ab6V9eGk0Hnu7fVWGeHQSOXHf/C2pF9c6uy3gef8QUeuUiWlNb0i08InPSE5a/KJzNzw1Q==
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.3.3:
version "11.3.3"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.3.3.tgz#a84dcc32e3ad013012964d836790d8c6afac8e88"
......@@ -7420,10 +7459,10 @@ react-is@16.8.6:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
react-spinners@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.8.0.tgz#a64f5bc4c301f463841cf75ed67d6f64a365997c"
integrity sha512-xeoRNn4YlsqKaqBm0SgCrRt3sI99CGFraZbw9noD9RLKwumEJ55e9dkXjdEbZgJUmPymQCDrsazQe79DoFd9IQ==
react-spinners@0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.8.1.tgz#210521e9d5b8be70e4c447d547919c1afb609209"
integrity sha512-jWp9gTurv6A1saXyqaqWx3dp2ZCi5eMaa0Y/0h8KdN+3QC7n7aITFKtT4BSX/ufVbvumtGLvheZDdczbgPPLtA==
dependencies:
"@emotion/core" "^10.0.15"
......@@ -7655,7 +7694,7 @@ resolve-url@^0.2.1:
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.3.2, resolve@^1.8.1:
resolve@^1.10.0, resolve@^1.12.0, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.8.1:
version "1.15.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8"
integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==
......@@ -9043,6 +9082,13 @@ worker-farm@^1.7.0:
dependencies:
errno "~0.1.7"
worker-plugin@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/worker-plugin/-/worker-plugin-3.2.0.tgz#ddae9f161b76fcbaacf8f54ecd037844584e43e7"
integrity sha512-W5nRkw7+HlbsEt3qRP6MczwDDISjiRj2GYt9+bpe8A2La00TmJdwzG5bpdMXhRt1qcWmwAvl1TiKaHRa+XDS9Q==
dependencies:
loader-utils "^1.1.0"
worker-rpc@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5"
......@@ -9103,6 +9149,13 @@ xdg-basedir@^3.0.0:
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
xregexp@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50"
integrity sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==
dependencies:
"@babel/runtime-corejs3" "^7.8.3"
xtend@^4.0.0, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
......@@ -9124,11 +9177,11 @@ yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yaml@^1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.7.2.tgz#f26aabf738590ab61efaca502358e48dc9f348b2"
integrity sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==
version "1.8.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.8.0.tgz#169fbcfa2081302dc9441d02b0b6fe667e4f74c9"
integrity sha512-6qI/tTx7OVtA4qNqD0OyutbM6Z9EKu4rxWm/2Y3FDEBQ4/2X2XAnyuRXMzAE2+1BPyqzksJZtrIwblOHg0IEzA==
dependencies:
"@babel/runtime" "^7.6.3"
"@babel/runtime" "^7.8.7"
yargs-parser@^16.1.0:
version "16.1.0"
......@@ -9155,10 +9208,10 @@ yargs@15.1.0:
y18n "^4.0.0"
yargs-parser "^16.1.0"
yarn@1.22.0:
version "1.22.0"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.0.tgz#acf82906e36bcccd1ccab1cfb73b87509667c881"
integrity sha512-KMHP/Jq53jZKTY9iTUt3dIVl/be6UPs2INo96+BnZHLKxYNTfwMmlgHTaMWyGZoO74RI4AIFvnWhYrXq2USJkg==
yarn@1.22.1:
version "1.22.1"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.1.tgz#888d8b01ac8547669b3bda5fe4b5b8b88c909e8c"
integrity sha512-hL7Fd2Psv4fOiE/JjVULDg+LckG0Ql7IPUh5wWC/4UEr6W+eF+VVEWfRu6Q7qbOnqk7+qY7CXYSSLBfJIyElXA==
yn@3.1.1:
version "3.1.1"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册