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

feat: simplize histogram chart tooltip (#696)

上级 94849229
# Always validate the PR title AND all the commits
titleAndCommits: true
# Allows use of Merge commits (eg on github: "Merge branch 'master' into feature/ride-unicorns")
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
allowMergeCommits: true
......@@ -38,19 +38,19 @@
"version": "yarn format && git add -A"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "3.4.0",
"@typescript-eslint/parser": "3.4.0",
"@typescript-eslint/eslint-plugin": "3.5.0",
"@typescript-eslint/parser": "3.5.0",
"eslint": "7.3.1",
"eslint-config-prettier": "6.11.0",
"eslint-plugin-prettier": "3.1.4",
"eslint-plugin-react": "7.20.0",
"eslint-plugin-react-hooks": "4.0.0",
"eslint-plugin-react": "7.20.3",
"eslint-plugin-react-hooks": "4.0.5",
"husky": "4.2.5",
"lerna": "3.22.1",
"lint-staged": "10.2.11",
"prettier": "2.0.5",
"rimraf": "3.0.2",
"typescript": "3.9.5",
"typescript": "3.9.6",
"yarn": "1.22.4"
},
"engines": {
......
......@@ -45,7 +45,7 @@
"@types/yargs": "15.0.5",
"cross-env": "7.0.2",
"ts-node": "8.10.2",
"typescript": "3.9.5"
"typescript": "3.9.6"
},
"engines": {
"node": ">=10",
......
......@@ -144,37 +144,61 @@ const HistogramChart: FunctionComponent<HistogramChartProps> = ({cid, run, tag,
if (!data || highlight == null) {
return '';
}
const series = (params as EChartOption.Tooltip.Format[]).filter(
const series = (params as EChartOption.Tooltip.Format[]).find(
s => s.data[1] === (data as OverlayData).data[highlight][0][1]
);
return [
series[0].seriesName,
...series.map(s => `${formatTooltipXValue(s.data[2])}, ${formatTooltipYValue(s.data[3])}`)
].join('<br />');
return series?.seriesName ?? '';
},
[highlight, data]
),
[Modes.Offset]: useCallback(
(step: number, dots: [number, number, number][]) =>
[
`${t('common:time-mode.step')}${step}`,
...dots
.filter(d => d[1] === step)
.map(d => `${formatTooltipXValue(d[0])}, ${formatTooltipYValue(d[2])}`)
].join('<br />'),
[t]
)
[Modes.Offset]: useCallback((dot: [number, number, number]) => dot[2], [])
} as const;
const pointerFormatter = {
[Modes.Overlay]: useCallback((params: {value: number; axisDimension: 'x' | 'y'}) => {
if (params.axisDimension === 'x') {
return formatTooltipXValue(params.value);
}
if (params.axisDimension === 'y') {
return formatTooltipYValue(params.value);
}
return '' as never;
}, []),
[Modes.Offset]: useCallback((params: {axisDimension: 'x' | 'y'}, dot: [number, number, number]) => {
if (params.axisDimension === 'x') {
return formatTooltipXValue(dot?.[0]);
}
if (params.axisDimension === 'y') {
return formatTooltipYValue(dot?.[1]);
}
return '' as never;
}, [])
};
const options = useMemo(
() => ({
...chartOptions[mode],
color: [run.colors[0]],
tooltip: {
formatter: formatter[mode]
},
axisPointer: {
label: {
formatter: pointerFormatter[mode]
}
},
xAxis: {
axisPointer: {
snap: mode === Modes.Overlay
}
},
yAxis: {
axisPointer: {
snap: mode === Modes.Overlay
}
}
}),
[mode, run, formatter]
[mode, run, formatter, pointerFormatter]
);
const mousemove = useCallback((echarts: ECharts, {offsetX, offsetY}: {offsetX: number; offsetY: number}) => {
......
......@@ -16,10 +16,13 @@ import {InitConfig} from '@visualdl/i18n';
import Language from '~/components/Language';
import ee from '~/utils/event';
import {getApiToken} from '~/utils/fetch';
import queryString from 'query-string';
import styled from 'styled-components';
import useNavItems from '~/hooks/useNavItems';
import {useRouter} from 'next/router';
const API_TOKEN_KEY: string = globalThis.__vdl_api_token_key__ || '';
const Nav = styled.nav`
background-color: ${navbarBackgroundColor};
color: ${textInvertColor};
......@@ -107,10 +110,12 @@ const Navbar: FunctionComponent = () => {
path += `/${subpath}`;
}
path += '/index';
if (process.env.API_TOKEN_KEY) {
if (API_TOKEN_KEY) {
const id = getApiToken();
if (id) {
path += `?${process.env.API_TOKEN_KEY}=${encodeURIComponent(id)}`;
path += `?${queryString.stringify({
[API_TOKEN_KEY]: id
})}`;
}
}
return path;
......
......@@ -20,7 +20,7 @@ type PreloaderProps = {
};
const Preloader: FunctionComponent<PreloaderProps> = ({url, as}) =>
process.env.API_TOKEN_KEY ? null : (
process.env.API_TOKEN_KEY || globalThis.__vdl_api_token_key__ ? null : (
<Head>
<link rel="preload" href={process.env.API_URL + url} crossOrigin="anonymous" as={as || 'fetch'} />
</Head>
......
......@@ -120,6 +120,36 @@ const StackChart = React.forwardRef<StackChartRef, StackChartProps & WithStyled>
[makePolyPoints]
);
const [highlight, setHighlight] = useState<number | null>(null);
const [dots, setDots] = useState<[number, number, number][]>([]);
const tooltipRef = useRef<HTMLDivElement | null>(null);
const [tooltip, setTooltip] = useState('');
const highLightRef = useRef(highlight);
const dotsRef = useRef(dots);
useEffect(() => {
highLightRef.current = highlight;
}, [highlight]);
useEffect(() => {
dotsRef.current = dots;
}, [dots]);
const pointerLabelFormatter = options?.axisPointer?.label?.formatter;
// formatter change will cause echarts rerender axis pointer label
// so we need to use 2 refs instead of dots and highlight to get rid of dependencies of these two variables
const axisPointerLabelFormatter = useCallback(
params => {
if (!pointerLabelFormatter || highLightRef.current == null) {
return '';
}
if ('string' === typeof pointerLabelFormatter) {
return pointerLabelFormatter;
}
return pointerLabelFormatter(params, dotsRef.current[highLightRef.current]);
},
[pointerLabelFormatter]
);
const chartOptions = useMemo<EChartOption>(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {color, colorAlt, toolbox, series, ...defaults} = chart;
......@@ -133,9 +163,17 @@ const StackChart = React.forwardRef<StackChartRef, StackChartProps & WithStyled>
min: minY,
max: maxY
},
axisPointer: {
label: {
formatter: axisPointerLabelFormatter
}
},
xAxis: {
min: minX,
max: maxX
max: maxX,
axisPointer: {
type: 'none'
}
},
yAxis: {
inverse: true,
......@@ -147,6 +185,9 @@ const StackChart = React.forwardRef<StackChartRef, StackChartProps & WithStyled>
},
axisLabel: {
formatter: (value: number) => (value < minY ? '' : value + '')
},
axisPointer: {
type: 'none'
}
},
grid: {
......@@ -174,12 +215,7 @@ const StackChart = React.forwardRef<StackChartRef, StackChartProps & WithStyled>
options,
defaults
);
}, [options, title, rawData, minX, maxX, minY, maxY, negativeY, renderItem]);
const [highlight, setHighlight] = useState<number | null>(null);
const [dots, setDots] = useState<[number, number, number][]>([]);
const tooltipRef = useRef<HTMLDivElement | null>(null);
const [tooltip, setTooltip] = useState('');
}, [options, title, rawData, minX, maxX, minY, maxY, negativeY, renderItem, axisPointerLabelFormatter]);
const mouseout = useCallback(() => {
setHighlight(null);
......@@ -220,7 +256,8 @@ const StackChart = React.forwardRef<StackChartRef, StackChartProps & WithStyled>
break;
}
}
setHighlight(step == null ? null : data.findIndex(row => row[1] === step));
const highlight = step == null ? null : data.findIndex(row => row[1] === step);
setHighlight(highlight);
// find nearest x axis point
let dots: [number, number, number][] = [];
......@@ -247,7 +284,7 @@ const StackChart = React.forwardRef<StackChartRef, StackChartProps & WithStyled>
if (chartOptions.tooltip?.formatter) {
setTooltip(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
step == null ? '' : (chartOptions.tooltip?.formatter as any)?.(step, dots)
highlight == null ? '' : (chartOptions.tooltip?.formatter as any)?.(dots[highlight])
);
if (tooltipRef.current) {
if (step == null) {
......
......@@ -31,7 +31,6 @@ module.exports = {
DEFAULT_LANGUAGE,
LOCALE_PATH,
LANGUAGES,
API_TOKEN_KEY: process.env.API_TOKEN_KEY || '',
PUBLIC_PATH: publicPath,
API_URL: apiUrl
},
......
......@@ -64,7 +64,7 @@
"tippy.js": "6.2.3"
},
"devDependencies": {
"@babel/core": "7.10.3",
"@babel/core": "7.10.4",
"@types/d3-format": "1.3.1",
"@types/echarts": "4.6.3",
"@types/enzyme": "3.10.5",
......@@ -82,7 +82,7 @@
"babel-plugin-emotion": "10.0.33",
"babel-plugin-styled-components": "1.10.7",
"babel-plugin-typescript-to-proptypes": "1.3.2",
"copy-webpack-plugin": "6.0.2",
"copy-webpack-plugin": "6.0.3",
"core-js": "3.6.5",
"cross-env": "7.0.2",
"css-loader": "3.6.0",
......@@ -93,7 +93,7 @@
"jest": "26.1.0",
"ora": "4.0.4",
"ts-jest": "26.1.1",
"typescript": "3.9.5",
"typescript": "3.9.6",
"worker-plugin": "4.0.3"
},
"engines": {
......
......@@ -12,12 +12,14 @@ import {ToastContainer} from 'react-toastify';
import queryString from 'query-string';
import {withRouter} from 'next/router';
const API_TOKEN_KEY: string = globalThis.__vdl_api_token_key__ || '';
class VDLApp extends App {
constructor(props: AppProps) {
super(props);
if (process.browser && process.env.API_TOKEN_KEY) {
if (process.browser && API_TOKEN_KEY) {
const query = queryString.parse(window.location.search);
setApiToken(query[process.env.API_TOKEN_KEY]);
setApiToken(query[API_TOKEN_KEY]);
}
}
......@@ -25,16 +27,16 @@ class VDLApp extends App {
Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', (url: string) => {
NProgress.done();
if (process.env.API_TOKEN_KEY) {
if (API_TOKEN_KEY) {
const id = getApiToken();
const parsed = queryString.parseUrl(url);
if (id && !parsed.query[process.env.API_TOKEN_KEY]) {
if (id && !parsed.query[API_TOKEN_KEY]) {
this.props.router.replace(
queryString.stringifyUrl({
url: parsed.url,
query: {
...parsed.query,
[process.env.API_TOKEN_KEY]: id
[API_TOKEN_KEY]: id
}
}),
undefined,
......@@ -82,8 +84,8 @@ class VDLApp extends App {
static async getInitialProps(appContext: AppContext) {
const appProps = await App.getInitialProps(appContext);
if (process.env.API_TOKEN_KEY) {
setApiToken(appContext.router.query[process.env.API_TOKEN_KEY]);
if (API_TOKEN_KEY) {
setApiToken(appContext.router.query[API_TOKEN_KEY]);
}
return {...appProps};
}
......
......@@ -59,6 +59,9 @@ export default class VDLDocument extends Document<VDLDocumentProps> {
<script
dangerouslySetInnerHTML={{__html: `__vdl_public_path__='${process.env.PUBLIC_PATH}'`}}
></script>
<script
dangerouslySetInnerHTML={{__html: `__vdl_api_token_key__='${process.env.API_TOKEN_KEY}'`}}
></script>
<Main />
<NextScript />
</body>
......
declare global {
interface Window {
__visualdl_instance_id__?: string;
__visualdl_instance_id__?: string | string[];
__vdl_api_token_key__?: string;
}
namespace globalThis {
// eslint-disable-next-line no-var
var __visualdl_instance_id__: string | undefined;
var __visualdl_instance_id__: string | string[] | undefined;
// eslint-disable-next-line no-var
var __vdl_api_token_key__: string | undefined;
}
}
declare namespace NodeJS {
interface Global {
__visualdl_instance_id__?: string;
__visualdl_instance_id__?: string | string[];
__vdl_api_token_key__?: string;
}
}
......
......@@ -3,28 +3,33 @@
import fetch from 'isomorphic-unfetch';
const API_TOKEN_KEY: string = globalThis.__vdl_api_token_key__ || '';
const API_TOKEN_HEADER = 'X-VisualDL-Instance-ID';
function addApiToken(options?: RequestInit): RequestInit | undefined {
if (!process.env.API_TOKEN_KEY || !globalThis.__visualdl_instance_id__) {
const id = getApiToken();
if (!API_TOKEN_KEY || !id) {
return options;
}
const {headers, ...rest} = options || {};
const newHeaders = new Headers(headers);
if (Array.isArray(id)) {
id.forEach(value => newHeaders.append(API_TOKEN_HEADER, value));
} else {
newHeaders.append(API_TOKEN_HEADER, id);
}
return {
...rest,
headers: {
...(headers || {}),
[API_TOKEN_HEADER]: globalThis.__visualdl_instance_id__
}
headers: newHeaders
};
}
export function setApiToken(id?: string | string[] | null) {
const instanceId = Array.isArray(id) ? id[0] : id;
globalThis.__visualdl_instance_id__ = instanceId || '';
globalThis.__visualdl_instance_id__ = id || '';
}
export function getApiToken() {
export function getApiToken(): string | string[] | null {
return globalThis.__visualdl_instance_id__ || '';
}
......
......@@ -37,11 +37,11 @@
"dependencies": {
"detect-node": "2.0.4",
"hoist-non-react-statics": "3.3.2",
"i18next": "19.5.1",
"i18next": "19.5.3",
"i18next-browser-languagedetector": "5.0.0",
"i18next-fs-backend": "1.0.6",
"i18next-http-backend": "1.0.15",
"i18next-http-middleware": "3.0.0",
"i18next-fs-backend": "1.0.7",
"i18next-http-backend": "1.0.16",
"i18next-http-middleware": "3.0.2",
"path-match": "1.2.4",
"prop-types": "15.7.2",
"react-i18next": "11.7.0",
......@@ -53,7 +53,7 @@
"@types/node": "14.0.14",
"@types/react": "16.9.41",
"@types/react-dom": "16.9.8",
"typescript": "3.9.5"
"typescript": "3.9.6"
},
"peerDependencies": {
"express": "^4.17.1",
......
......@@ -39,7 +39,7 @@
"@types/express": "4.17.6",
"@types/faker": "4.1.12",
"@types/node": "14.0.14",
"typescript": "3.9.5"
"typescript": "3.9.6"
},
"peerDependencies": {
"express": "^4.17.1"
......
......@@ -41,14 +41,14 @@
},
"devDependencies": {
"autoprefixer": "9.8.4",
"copy-webpack-plugin": "6.0.2",
"copy-webpack-plugin": "6.0.3",
"css-loader": "3.6.0",
"html-webpack-plugin": "4.3.0",
"mini-css-extract-plugin": "0.9.0",
"postcss-loader": "3.0.0",
"rimraf": "3.0.2",
"sass": "1.26.9",
"sass-loader": "8.0.2",
"sass-loader": "9.0.0",
"terser": "4.8.0",
"webpack": "4.43.0"
},
......
......@@ -56,7 +56,7 @@
"shelljs": "0.8.4",
"ts-loader": "7.0.5",
"ts-node": "8.10.2",
"typescript": "3.9.5",
"typescript": "3.9.6",
"webpack": "4.43.0",
"webpack-cli": "3.3.12",
"webpack-dev-middleware": "3.7.2"
......
......@@ -37,7 +37,7 @@
"cross-env": "7.0.2",
"rimraf": "3.0.2",
"ts-node": "8.10.2",
"typescript": "3.9.5"
"typescript": "3.9.6"
},
"engines": {
"node": ">=10",
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册