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

frontend 2.0.0-beta.25: user experience improvement of scalars page (#608)

* chore: update dependencies

* build: update frontend build from source

* fix: use bignumber.js to keep data precision

* feat: if there is only one point, place it in the middle

* style: update lint staged config

* style: fit prettier 2.0

* fix: globalize bignumber config
上级 cdb82390
......@@ -26,16 +26,20 @@ const Wrapper = styled.div`
}
`;
type Range = {
min: EChartOption.BasicComponents.CartesianAxis['min'];
max: EChartOption.BasicComponents.CartesianAxis['max'];
};
type LineChartProps = {
title?: string;
legend?: string[];
data?: Partial<NonNullable<EChartOption<EChartOption.SeriesLine>['series']>>;
xAxis?: string;
yAxis?: string;
type?: EChartOption.BasicComponents.CartesianAxis.Type;
yRange?: {
min: number;
max: number;
};
xRange?: Range;
yRange?: Range;
tooltip?: string | EChartOption.Tooltip.Formatter;
loading?: boolean;
};
......@@ -45,7 +49,9 @@ const LineChart: FunctionComponent<LineChartProps & WithStyled> = ({
legend,
data,
xAxis,
yAxis,
type,
xRange,
yRange,
tooltip,
loading,
......@@ -93,21 +99,25 @@ const LineChart: FunctionComponent<LineChartProps & WithStyled> = ({
axisLabel: {
...chart.xAxis.axisLabel,
formatter: xAxisFormatter
}
},
...(xRange || {})
},
yAxis: {
...chart.yAxis,
name: yAxis || '',
...(yRange || {})
},
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}
);
}
}, [data, title, legend, xAxis, type, xAxisFormatter, yRange, tooltip, echart]);
}, [data, title, legend, xAxis, yAxis, type, xAxisFormatter, xRange, yRange, tooltip, echart]);
return (
<Wrapper className={className}>
......
......@@ -5,6 +5,7 @@ import {
TransformParams,
chartData,
range,
singlePointRange,
sortingMethodMap,
tooltip,
transform,
......@@ -101,6 +102,20 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
);
const yRange = useHeavyWork(rangeWasm, rangeWorker, range, rangeParams);
const ranges: Record<'x' | 'y', Range | undefined> = useMemo(() => {
let x: Range | undefined = undefined;
let y: Range | undefined = yRange;
// if there is only one point, place it in the middle
if (smoothedDatasets.length === 1 && smoothedDatasets[0].length === 1) {
if (['value', 'log'].includes(type)) {
x = singlePointRange(smoothedDatasets[0][0][xAxisMap[xAxis]]);
}
y = singlePointRange(smoothedDatasets[0][0][2]);
}
return {x, y};
}, [smoothedDatasets, yRange, type, xAxis]);
const data = useMemo(
() =>
chartData({
......@@ -157,7 +172,8 @@ const ScalarChart: FunctionComponent<ScalarChartProps> = ({
<StyledLineChart
title={tag}
xAxis={xAxisLabel}
yRange={yRange}
xRange={ranges.x}
yRange={ranges.y}
type={type}
tooltip={formatter}
data={data}
......
module.exports = {
'**/*.ts?(x)': filenames => [
'tsc -p tsconfig.json --noEmit',
'tsc -p server/tsconfig.json --noEmit',
`eslint ${filenames.join(' ')}`
],
'**/*.js?(x)': filenames => `eslint ${filenames.join(' ')}`
'**/*.ts?(x)': () => ['tsc -p tsconfig.json --noEmit', 'tsc -p server/tsconfig.json --noEmit'],
'**/*.(j|t)s?(x)': filenames => `eslint ${filenames.join(' ')}`
};
{
"name": "visualdl",
"version": "2.0.0-beta.24",
"version": "2.0.0-beta.25",
"title": "VisualDL",
"description": "A platform to visualize the deep learning process and result.",
"keywords": [
......@@ -33,7 +33,7 @@
"export": "next export -o serverless",
"start": "pm2-runtime ecosystem.config.js",
"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\"",
"format": "prettier --write \"**/*.ts\" \"**/*.tsx\" \"**/*.js\"",
"test": "echo \"Error: no test specified\" && exit 0",
"prepublishOnly": "yarn lint && yarn test && ./scripts/build.sh",
"preversion": "yarn lint",
......@@ -51,9 +51,10 @@
"ecosystem.config.js"
],
"dependencies": {
"bignumber.js": "9.0.0",
"dagre-d3": "0.6.4",
"detect-node": "2.0.4",
"echarts": "4.6.0",
"echarts": "4.7.0",
"echarts-gl": "1.1.1",
"express": "4.17.1",
"hoist-non-react-statics": "3.3.2",
......@@ -72,15 +73,15 @@
"ora": "4.0.3",
"path-match": "1.2.4",
"pm2": "4.2.3",
"polished": "3.4.4",
"polished": "3.5.1",
"prop-types": "15.7.2",
"query-string": "6.11.1",
"react": "16.13.0",
"react-dom": "16.13.0",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-hooks-worker": "0.9.0",
"react-i18next": "11.3.3",
"react-i18next": "11.3.4",
"react-input-range": "1.3.0",
"react-is": "16.13.0",
"react-is": "16.13.1",
"react-spinners": "0.8.1",
"save-svg-as-png": "1.4.17",
"styled-components": "5.0.1",
......@@ -89,22 +90,22 @@
"yargs": "15.3.1"
},
"devDependencies": {
"@babel/core": "7.8.7",
"@babel/core": "7.9.0",
"@types/d3": "5.7.2",
"@types/dagre": "0.7.42",
"@types/dagre-d3": "^0.4.39",
"@types/echarts": "4.4.3",
"@types/express": "4.17.3",
"@types/faker": "4.1.10",
"@types/lodash": "4.14.149",
"@types/node": "13.9.1",
"@types/node": "13.9.3",
"@types/nprogress": "0.2.0",
"@types/react": "16.9.23",
"@types/react": "16.9.25",
"@types/react-dom": "16.9.5",
"@types/styled-components": "5.0.1",
"@types/webpack": "4.41.7",
"@types/webpack": "4.41.8",
"@types/yargs": "15.0.4",
"@typescript-eslint/eslint-plugin": "2.24.0",
"@typescript-eslint/parser": "2.24.0",
"@typescript-eslint/eslint-plugin": "2.25.0",
"@typescript-eslint/parser": "2.25.0",
"@wasm-tool/wasm-pack-plugin": "1.2.0",
"babel-plugin-emotion": "10.0.29",
"babel-plugin-styled-components": "1.10.7",
......@@ -112,16 +113,16 @@
"core-js": "3",
"cross-env": "7.0.2",
"eslint": "6.8.0",
"eslint-config-prettier": "6.10.0",
"eslint-config-prettier": "6.10.1",
"eslint-plugin-prettier": "3.1.2",
"eslint-plugin-react": "7.19.0",
"eslint-plugin-react-hooks": "2.5.0",
"eslint-plugin-react-hooks": "2.5.1",
"faker": "4.1.0",
"husky": "4.2.3",
"lint-staged": "10.0.8",
"lint-staged": "10.0.9",
"nodemon": "2.0.2",
"prettier": "1.19.1",
"ts-node": "8.6.2",
"prettier": "2.0.2",
"ts-node": "8.8.1",
"typescript": "3.8.3",
"worker-plugin": "4.0.2",
"yarn": "1.22.4"
......
import {Graph, collectDagFacts} from '~/resource/graphs';
import {Graph, NodeType, TypedNode, collectDagFacts} from '~/resource/graphs';
import {NextI18NextPage, useTranslation} from '~/utils/i18n';
import NodeInfo, {NodeInfoProps} from '~/components/GraphsPage/NodeInfo';
import React, {useEffect, useMemo, useState} from 'react';
......@@ -154,7 +154,7 @@ const useDagreD3 = (graph?: Graph) => {
return;
}
const g = new dagre.graphlib.Graph();
const g = new dagre.graphlib.Graph<{type: NodeType; elem: HTMLElement}>();
g.setGraph({}).setDefaultEdgeLabel(() => ({}));
dagInfo.nodes.forEach(n => g.setNode(n.key, n));
......@@ -172,7 +172,7 @@ const useDagreD3 = (graph?: Graph) => {
const zoom = d3
.zoom<HTMLElement, any>() // eslint-disable-line @typescript-eslint/no-explicit-any
.scaleExtent([MIN_SCALE, MAX_SCALE])
.on('zoom', function() {
.on('zoom', function () {
setScaleValue(d3.event.transform.k / scaleFactor);
inner.attr('transform', d3.event.transform);
})
......@@ -196,7 +196,7 @@ const useDagreD3 = (graph?: Graph) => {
return;
}
setCurrentNode({...node, type});
setCurrentNode({...node, type} as TypedNode);
});
const fitScreen = () => {
......
......@@ -3,6 +3,7 @@ import * as chart from '~/utils/chart';
import {ChartDataParams, RangeParams, TooltipData, TransformParams, xAxisMap} from './types';
import {formatTime, quantile} from '~/utils';
import BigNumber from 'bignumber.js';
import {I18n} from '~/utils/i18next/types';
import cloneDeep from 'lodash/cloneDeep';
import compact from 'lodash/compact';
......@@ -10,6 +11,8 @@ import maxBy from 'lodash/maxBy';
import minBy from 'lodash/minBy';
import sortBy from 'lodash/sortBy';
BigNumber.config({EXPONENTIAL_AT: [-6, 7]});
export * from './types';
export const sortingMethodMap = {
......@@ -24,11 +27,12 @@ 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 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 = d[2];
const nextVal = new BigNumber(d[2]);
// second to millisecond.
const millisecond = (d[0] = Math.floor(d[0] * 1000));
if (i === 0) {
......@@ -36,16 +40,19 @@ export const transform = ({datasets, smoothing}: TransformParams) =>
}
// Relative time, millisecond to hours.
d[4] = Math.floor(millisecond - startValue) / (60 * 60 * 1000);
if (!isFinite(nextVal)) {
d[3] = nextVal;
if (!nextVal.isFinite()) {
d[3] = nextVal.toNumber();
} else {
last = last * smoothing + (1 - smoothing) * nextVal;
// last = last * smoothing + (1 - smoothing) * nextVal;
last = last.multipliedBy(bigSmoothing).plus(bigSmoothing.minus(1).negated().multipliedBy(nextVal));
numAccum++;
let debiasWeight = 1;
if (smoothing !== 1.0) {
debiasWeight = 1.0 - Math.pow(smoothing, 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 / debiasWeight;
d[3] = last.dividedBy(debiasWeight).toNumber();
}
});
return data;
......@@ -95,22 +102,28 @@ export const chartData = ({data, runs, smooth, xAxis}: ChartDataParams) =>
})
.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: minBy(dataset, items => items[2])?.[2] ?? 0,
max: maxBy(dataset, items => items[2])?.[2] ?? 0
min: Math.min(...values) ?? 0,
max: Math.max(...values) ?? 0
};
} else {
// Get the quantile range.
const sorted = sortBy(dataset, [item => item[2]]);
const sorted = dataset.map(v => v[2]).sort();
return {
min: quantile(sorted, 0.05, item => item[2]),
max: quantile(dataset, 0.95, item => item[2])
min: quantile(sorted, 0.05),
max: quantile(values, 0.95)
};
}
})
......@@ -140,7 +153,7 @@ export const tooltip = (data: TooltipData[], i18n: I18n) => {
Run: 60,
Time: 120,
Step: 40,
Value: 50,
Value: 60,
Smoothed: 60,
Relative: 60
};
......@@ -156,9 +169,9 @@ export const tooltip = (data: TooltipData[], i18n: I18n) => {
const data = item.item;
return {
Run: item.run,
// Keep six number for easy-read.
Smoothed: data[indexPropMap.Smoothed]?.toString().slice(0, 6),
Value: data[indexPropMap.Value]?.toString().slice(0, 6),
// use precision then toString to remove trailling 0
Smoothed: new BigNumber(data[indexPropMap.Smoothed] ?? Number.NaN).precision(5).toString(),
Value: new BigNumber(data[indexPropMap.Smoothed] ?? 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.
......
#!/bin/bash
set -e
set -ex
# rust toolchain
# https://rustup.rs/
curl https://sh.rustup.rs -sSf | sh -s -- --no-modify-path --default-toolchain nightly -y
source $HOME/.cargo/env
if ! hash rustup 2>/dev/null; then
curl https://sh.rustup.rs -sSf | sh -s -- --no-modify-path --default-toolchain nightly -y
source $HOME/.cargo/env
fi
# wasm-pack
# https://rustwasm.github.io/wasm-pack/installer/
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
if ! hash wasm-pack 2>/dev/null; then
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
fi
# yarn install
yarn install --frozen-lockfile
......@@ -134,6 +134,7 @@ export const xAxis = {
export const yAxis = {
type: 'value',
name: '',
splitNumber: 4,
axisLine: {
lineStyle: {
......@@ -146,7 +147,7 @@ export const yAxis = {
axisLabel: {
fontSize: 12,
color: '#666',
formatter: (v: number) => v.toString().slice(0, 5)
formatter: (v: number) => Number.parseFloat(v.toPrecision(5))
},
splitLine: {
lineStyle: {
......@@ -157,7 +158,6 @@ export const yAxis = {
export const series = {
type: 'line',
showSymbol: false,
hoverAnimation: false,
animationDuration: 100,
lineStyle: {
......
......@@ -38,7 +38,7 @@ type I18nRes = {
};
};
export const appWithTranslation = function(this: NextI18Next, WrappedComponent: any) {
export const appWithTranslation = function (this: NextI18Next, WrappedComponent: any) {
const WrappedComponentWithSSR = withSSR()(WrappedComponent);
const {config, i18n} = this;
const consoleMessage = this.consoleMessage.bind(this);
......@@ -140,10 +140,9 @@ export const appWithTranslation = function(this: NextI18Next, WrappedComponent:
} else {
consoleMessage(
'warn',
`You have not declared a namespacesRequired array on your page-level component: ${ctx.Component
.displayName ||
ctx.Component.name ||
'Component'}. This will cause all namespaces to be sent down to the client, possibly negatively impacting the performance of your app. For more info, see: https://github.com/isaachinman/next-i18next#4-declaring-namespace-dependencies`
`You have not declared a namespacesRequired array on your page-level component: ${
ctx.Component.displayName || ctx.Component.name || 'Component'
}. This will cause all namespaces to be sent down to the client, possibly negatively impacting the performance of your app. For more info, see: https://github.com/isaachinman/next-i18next#4-declaring-namespace-dependencies`
);
}
......
......@@ -5,9 +5,9 @@ import React from 'react';
export const withInternals = (WrappedComponent: any, config: NextI18NextInternals) => {
class WithInternals extends React.Component {
static displayName = `withNextI18NextInternals(${WrappedComponent.displayName ||
WrappedComponent.name ||
'Component'})`;
static displayName = `withNextI18NextInternals(${
WrappedComponent.displayName || WrappedComponent.name || 'Component'
})`;
render() {
return <WrappedComponent {...this.props} nextI18NextInternals={config} />;
......
......@@ -15,7 +15,7 @@ import pathMatch from 'path-match';
const route = pathMatch();
export default function(nexti18next: NextI18Next) {
export default function (nexti18next: NextI18Next) {
const {config, i18n} = nexti18next;
const {allLanguages, ignoreRoutes, localeSubpaths} = config;
......
......@@ -21,7 +21,7 @@ const logMessage = (messageType: MessageType, message: string) => {
}
};
export const consoleMessage = function(
export const consoleMessage = function (
this: NextI18Next | void,
messageType: MessageType,
message: string,
......
import BigNumber from 'bignumber.js';
import moment from 'moment';
export const formatTime = (value: number, language: string, formatter = 'L LTS') =>
moment(Math.floor(value), 'x')
.locale(language)
.format(formatter);
moment(Math.floor(value), 'x').locale(language).format(formatter);
export const quantile = (
values: number[][],
p: number,
valueOf: (value: number[], index: number, values: number[][]) => number
) => {
export const quantile = (values: number[], p: number) => {
const n = values.length;
if (!n) {
return NaN;
}
if ((p = +p) <= 0 || n < 2) {
return valueOf(values[0], 0, values);
return values[0];
}
if (p >= 1) {
return valueOf(values[n - 1], n - 1, values);
return values[n - 1];
}
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);
const i = new BigNumber(p).multipliedBy(n - 1);
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();
};
此差异已折叠。
......@@ -8,9 +8,19 @@ BUILD_DIR=$TOP_DIR/build
mkdir -p $BUILD_DIR
build_frontend_fake() {
mkdir -p "$BUILD_DIR/package/serverless"
}
build_frontend_from_source() {
build_frontend_fake
cd $FRONTEND_DIR
PUBLIC_PATH="/app" API_URL="/api" ./scripts/build.sh
./scripts/install.sh
./scripts/build.sh
# extract
tar zxf "$FRONTEND_DIR/output/serverless.tar.gz" -C "$BUILD_DIR/package/serverless"
}
build_frontend() {
......@@ -70,10 +80,6 @@ build_frontend() {
tar zxf "$BUILD_DIR/$FILENAME" -C "$BUILD_DIR"
}
build_frontend_fake() {
mkdir -p "$BUILD_DIR/package/serverless"
}
build_backend() {
cd $BUILD_DIR
if [[ $WITH_PYTHON3 ]]; then
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册