fix: 图表展示 bugifx & 优化

This commit is contained in:
GraceWalk
2022-09-13 14:09:03 +08:00
parent 863b765e0d
commit 12b82c1395
9 changed files with 182 additions and 142 deletions

View File

@@ -46,30 +46,42 @@ export const supplementaryPoints = (
extraCallback?: (point: [number, 0]) => any[] extraCallback?: (point: [number, 0]) => any[]
) => { ) => {
lines.forEach(({ data }) => { lines.forEach(({ data }) => {
// 获取未补点前线条的点的个数
let len = data.length; let len = data.length;
for (let i = 0; i < len; i++) { // 记录当前处理到的点的下标值
const timestamp = data[i][0] as number; let i = 0;
// 数组第一个点和最后一个点单独处理
for (; i < len; i++) {
if (i === 0) { if (i === 0) {
let firstPointTimestamp = data[0][0] as number; let firstPointTimestamp = data[0][0] as number;
while (firstPointTimestamp - interval > timeRange[0]) { while (firstPointTimestamp - interval > timeRange[0]) {
const prePointTimestamp = firstPointTimestamp - interval; const prevPointTimestamp = firstPointTimestamp - interval;
data.unshift(extraCallback ? extraCallback([prePointTimestamp, 0]) : [prePointTimestamp, 0]); data.unshift(extraCallback ? extraCallback([prevPointTimestamp, 0]) : [prevPointTimestamp, 0]);
firstPointTimestamp = prevPointTimestamp;
len++; len++;
i++; i++;
firstPointTimestamp = prePointTimestamp;
} }
} }
if (i === len - 1) { if (i === len - 1) {
let lastPointTimestamp = data[len - 1][0] as number; let lastPointTimestamp = data[i][0] as number;
while (lastPointTimestamp + interval < timeRange[1]) { while (lastPointTimestamp + interval < timeRange[1]) {
const next = lastPointTimestamp + interval; const nextPointTimestamp = lastPointTimestamp + interval;
data.push(extraCallback ? extraCallback([next, 0]) : [next, 0]); data.push(extraCallback ? extraCallback([nextPointTimestamp, 0]) : [nextPointTimestamp, 0]);
lastPointTimestamp = next; lastPointTimestamp = nextPointTimestamp;
}
break;
}
{
let timestamp = data[i][0] as number;
while (timestamp + interval < data[i + 1][0]) {
const nextPointTimestamp = timestamp + interval;
data.splice(i + 1, 0, extraCallback ? extraCallback([nextPointTimestamp, 0]) : [nextPointTimestamp, 0]);
timestamp = nextPointTimestamp;
len++;
i++;
} }
} else if (timestamp + interval < data[i + 1][0]) {
data.splice(i + 1, 0, extraCallback ? extraCallback([timestamp + interval, 0]) : [timestamp + interval, 0]);
len++;
} }
} }
}); });
@@ -135,18 +147,37 @@ export const formatChartData = (
}; };
const seriesCallback = (lines: { name: string; data: [number, string | number][] }[]) => { const seriesCallback = (lines: { name: string; data: [number, string | number][] }[]) => {
const len = CHART_COLOR_LIST.length;
// series 配置 // series 配置
return lines.map((line) => { return lines.map((line, i) => {
return { return {
...line, ...line,
lineStyle: { lineStyle: {
width: 1.5, width: 1.5,
}, },
connectNulls: false,
symbol: 'emptyCircle', symbol: 'emptyCircle',
symbolSize: 4, symbolSize: 4,
smooth: 0.25, smooth: 0.25,
areaStyle: { areaStyle: {
opacity: 0.02, color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: CHART_COLOR_LIST[i % len] + '10',
},
{
offset: 1,
color: 'rgba(255,255,255,0)', // 100% 处的颜色
},
],
global: false, // 缺省为 false
},
}, },
}; };
}); });
@@ -189,6 +220,7 @@ export const getDetailChartConfig = (title: string, sliderPos: readonly [number,
startValue: sliderPos[0], startValue: sliderPos[0],
endValue: sliderPos[1], endValue: sliderPos[1],
zoomOnMouseWheel: false, zoomOnMouseWheel: false,
minValueSpan: 10 * 60 * 1000,
}, },
{ {
start: 0, start: 0,

View File

@@ -63,56 +63,63 @@
} }
} }
} }
.overview-chart-detail-drawer {
.chart-detail-modal-container { .dcloud-spin-nested-loading > div > .dcloud-spin.dcloud-spin-spinning {
position: relative; height: 300px;
.expand-icon-box { }
position: absolute; &.dcloud-drawer .dcloud-drawer-body {
z-index: 1000; padding: 0 20px;
top: 14px; }
right: 44px; .detail-header {
width: 24px; display: flex;
height: 24px; align-items: flex-end;
cursor: pointer; font-weight: normal;
font-size: 16px; .title {
text-align: center; font-family: @font-family-bold;
border-radius: 50%; font-size: 18px;
transition: background-color 0.3s ease; color: #495057;
.expand-icon { letter-spacing: 0;
color: #adb5bc; .unit {
line-height: 24px; font-family: @font-family-bold;
} font-size: 14px;
&:hover { letter-spacing: 0.5px;
background: rgba(33, 37, 41, 0.04);
.expand-icon {
color: #74788d;
} }
} }
.slider-info {
margin-left: 10px;
font-size: 12px;
font-family: @font-family;
color: #303a51;
}
} }
.detail-title { .chart-detail-modal-container {
display: flex; position: relative;
justify-content: space-between; overflow: hidden;
align-items: center; .expand-icon-box {
.left { position: absolute;
display: flex; z-index: 1000;
align-items: flex-end; top: 14px;
.title { right: 44px;
font-family: @font-family-bold; width: 24px;
font-size: 18px; height: 24px;
color: #495057; cursor: pointer;
letter-spacing: 0; font-size: 16px;
.unit { text-align: center;
font-family: @font-family-bold; border-radius: 50%;
font-size: 14px; transition: background-color 0.3s ease;
letter-spacing: 0.5px; .expand-icon {
color: #adb5bc;
line-height: 24px;
}
&:hover {
background: rgba(33, 37, 41, 0.04);
.expand-icon {
color: #74788d;
} }
} }
.info { }
margin-left: 10px; .detail-table {
} margin-top: 16px;
} }
} }
.detail-table {
margin-top: 16px;
}
} }

View File

@@ -216,8 +216,8 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
onChange={ksHeaderChange} onChange={ksHeaderChange}
nodeScopeModule={{ nodeScopeModule={{
customScopeList: scopeList, customScopeList: scopeList,
scopeName: `自定义 ${dashboardType === MetricType.Broker ? 'Broker' : 'Topic'} 范围`, scopeName: dashboardType === MetricType.Broker ? 'Broker' : 'Topic',
showSearch: dashboardType === MetricType.Topic, scopeLabel: `自定义 ${dashboardType === MetricType.Broker ? 'Broker' : 'Topic'} 范围`,
}} }}
indicatorSelectModule={{ indicatorSelectModule={{
hide: false, hide: false,

View File

@@ -26,8 +26,8 @@ const OptionsDefault = [
const NodeScope = ({ nodeScopeModule, change }: propsType) => { const NodeScope = ({ nodeScopeModule, change }: propsType) => {
const { const {
customScopeList: customList, customScopeList: customList,
scopeName = '自定义节点范围', scopeName = '',
showSearch = false, scopeLabel = '自定义范围',
searchPlaceholder = '输入内容进行搜索', searchPlaceholder = '输入内容进行搜索',
} = nodeScopeModule; } = nodeScopeModule;
const [topNum, setTopNum] = useState<number>(5); const [topNum, setTopNum] = useState<number>(5);
@@ -70,7 +70,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
change(checkedListTemp, false); change(checkedListTemp, false);
setIsTop(false); setIsTop(false);
setTopNum(null); setTopNum(null);
setInputValue(`已选${checkedListTemp?.length}`); setInputValue(`${checkedListTemp?.length}`);
setPopVisible(false); setPopVisible(false);
} }
}; };
@@ -109,7 +109,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
{/* <span>时间:</span> */} {/* <span>时间:</span> */}
<div className="flx_con"> <div className="flx_con">
<div className="flx_l"> <div className="flx_l">
<h6 className="time_title">top</h6> <h6 className="time_title"> top </h6>
<Radio.Group <Radio.Group
optionType="button" optionType="button"
buttonStyle="solid" buttonStyle="solid"
@@ -128,7 +128,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
</Radio.Group> </Radio.Group>
</div> </div>
<div className="flx_r"> <div className="flx_r">
<h6 className="time_title">{scopeName}</h6> <h6 className="time_title">{scopeLabel}</h6>
<div className="custom-scope"> <div className="custom-scope">
<div className="check-row"> <div className="check-row">
<Checkbox className="check-all" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}> <Checkbox className="check-all" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
@@ -136,9 +136,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
</Checkbox> </Checkbox>
<Input <Input
className="search-input" className="search-input"
suffix={ suffix={<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />}
<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />
}
size="small" size="small"
placeholder={searchPlaceholder} placeholder={searchPlaceholder}
onChange={(e) => setScopeSearchValue(e.target.value)} onChange={(e) => setScopeSearchValue(e.target.value)}
@@ -148,7 +146,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
<Checkbox.Group style={{ width: '100%' }} onChange={checkChange} value={checkedListTemp}> <Checkbox.Group style={{ width: '100%' }} onChange={checkChange} value={checkedListTemp}>
<Row gutter={[10, 12]}> <Row gutter={[10, 12]}>
{customList {customList
.filter((item) => !showSearch || item.label.includes(scopeSearchValue)) .filter((item) => item.label.includes(scopeSearchValue))
.map((item) => ( .map((item) => (
<Col span={12} key={item.value}> <Col span={12} key={item.value}>
<Checkbox value={item.value}>{item.label}</Checkbox> <Checkbox value={item.value}>{item.label}</Checkbox>
@@ -180,6 +178,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
return ( return (
<> <>
<div id="d-node-scope"> <div id="d-node-scope">
<div className="scope-title">{scopeName}</div>
<Popover <Popover
trigger={['click']} trigger={['click']}
visible={popVisible} visible={popVisible}

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Tooltip, Select, IconFont, Utils, Divider } from 'knowdesign'; import { Tooltip, Select, IconFont, Utils, Divider, Button } from 'knowdesign';
import moment from 'moment'; import moment from 'moment';
import { DRangeTime } from 'knowdesign'; import { DRangeTime } from 'knowdesign';
import IndicatorDrawer from './IndicatorDrawer'; import IndicatorDrawer from './IndicatorDrawer';
@@ -48,7 +48,7 @@ export interface IcustomScope {
export interface InodeScopeModule { export interface InodeScopeModule {
customScopeList: IcustomScope[]; customScopeList: IcustomScope[];
scopeName?: string; scopeName?: string;
showSearch?: boolean; scopeLabel?: string;
searchPlaceholder?: string; searchPlaceholder?: string;
change?: () => void; change?: () => void;
} }
@@ -138,9 +138,13 @@ const SingleChartHeader = ({
}; };
const reloadRangeTime = () => { const reloadRangeTime = () => {
const timeLen = rangeTime[1] - rangeTime[0] || 0; if (isRelativeRangeTime) {
const curTimeStamp = moment().valueOf(); const timeLen = rangeTime[1] - rangeTime[0] || 0;
setRangeTime([curTimeStamp - timeLen, curTimeStamp]); const curTimeStamp = moment().valueOf();
setRangeTime([curTimeStamp - timeLen, curTimeStamp]);
} else {
setRangeTime([...rangeTime]);
}
}; };
const openIndicatorDrawer = () => { const openIndicatorDrawer = () => {
@@ -174,12 +178,10 @@ const SingleChartHeader = ({
{!hideGridSelect && ( {!hideGridSelect && (
<Select className="grid-select" style={{ width: 70 }} value={gridNum} options={GRID_SIZE_OPTIONS} onChange={sizeChange} /> <Select className="grid-select" style={{ width: 70 }} value={gridNum} options={GRID_SIZE_OPTIONS} onChange={sizeChange} />
)} )}
<Divider type="vertical" style={{ height: 20, top: 0 }} /> {(!hideNodeScope || !hideGridSelect) && <Divider type="vertical" style={{ height: 20, top: 0 }} />}
<Tooltip title="点击指标筛选,可选择指标" placement="bottomRight"> <Button type="primary" onClick={openIndicatorDrawer}>
<div className="icon-box" onClick={openIndicatorDrawer}>
<IconFont className="icon" type="icon-shezhi1" /> </Button>
</div>
</Tooltip>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,8 +3,13 @@
@import '~knowdesign/es/basic/style/mixins/index'; @import '~knowdesign/es/basic/style/mixins/index';
#d-node-scope { #d-node-scope {
display: flex;
align-items: center;
position: relative; position: relative;
display: inline-block; .scope-title {
font-size: 14px;
color: #74788d;
}
.input-span { .input-span {
cursor: pointer; cursor: pointer;
} }
@@ -29,10 +34,10 @@
box-shadow: none; box-shadow: none;
} }
&.relativeTime { &.relativeTime {
width: 160px; width: 200px;
} }
&.absoluteTime { &.absoluteTime {
width: 300px; width: 200px;
} }
input { input {

View File

@@ -1,29 +1,19 @@
import moment from 'moment'; import moment from 'moment';
export const CHART_COLOR_LIST = [ export const CHART_COLOR_LIST = [
'#657DFC', '#556ee6',
'#A7B1EB',
'#2AC8E4',
'#9DDEEB',
'#3991FF',
'#94BEF2', '#94BEF2',
'#95e7ff',
'#9DDEEB',
'#A7B1EB',
'#C2D0E3', '#C2D0E3',
'#F5B6B3',
'#85C80D',
'#C9E795',
'#A76CEC',
'#CCABF1', '#CCABF1',
'#FF9C1B',
'#F5C993',
'#FFC300',
'#F9D77B', '#F9D77B',
'#12CA7A', '#F5C993',
'#8BA3C4',
'#FF7066',
'#A7E6C7', '#A7E6C7',
'#F19FC9', '#F19FC9',
'#AEAEAE', '#F5B6B3',
'#D1D1D1', '#C9E795',
]; ];
export const UNIT_MAP = { export const UNIT_MAP = {

View File

@@ -38,15 +38,17 @@
&-main { &-main {
.header-chart-container { .header-chart-container {
&-loading {
display: flex;
justify-content: center;
align-items: center;
}
width: 100%; width: 100%;
height: 244px; height: 244px;
margin-bottom: 12px; margin-bottom: 12px;
.cluster-container-border(); .cluster-container-border();
.dcloud-spin.dcloud-spin-spinning {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 244px;
}
} }
.content { .content {

View File

@@ -14,6 +14,7 @@ import { MetricType } from '@src/api';
import { getDataNumberUnit, getUnit } from '@src/constants/chartConfig'; import { getDataNumberUnit, getUnit } from '@src/constants/chartConfig';
import SingleChartHeader, { KsHeaderOptions } from '@src/components/SingleChartHeader'; import SingleChartHeader, { KsHeaderOptions } from '@src/components/SingleChartHeader';
import { MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL } from '@src/constants/common'; import { MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL } from '@src/constants/common';
import RenderEmpty from '@src/components/RenderEmpty';
type ChartFilterOptions = Omit<KsHeaderOptions, 'gridNum'>; type ChartFilterOptions = Omit<KsHeaderOptions, 'gridNum'>;
interface MetricInfo { interface MetricInfo {
@@ -64,7 +65,7 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
const [messagesInMetricData, setMessagesInMetricData] = useState<MessagesInMetric>({ const [messagesInMetricData, setMessagesInMetricData] = useState<MessagesInMetric>({
name: 'MessagesIn', name: 'MessagesIn',
unit: '', unit: '',
data: [], data: undefined,
}); });
const [curHeaderOptions, setCurHeaderOptions] = useState<ChartFilterOptions>(); const [curHeaderOptions, setCurHeaderOptions] = useState<ChartFilterOptions>();
const [defaultChartLoading, setDefaultChartLoading] = useState<boolean>(true); const [defaultChartLoading, setDefaultChartLoading] = useState<boolean>(true);
@@ -234,17 +235,19 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
result.forEach((point) => ((point[1] as number) /= unitSize)); result.forEach((point) => ((point[1] as number) /= unitSize));
} }
// 补充缺少的图表点 if (result.length) {
const extraMetrics = result[0][2].map((info) => ({ // 补充缺少的图表点
...info, const extraMetrics = result[0][2].map((info) => ({
value: 0, ...info,
})); value: 0,
const supplementaryInterval = }));
(curHeaderOptions.rangeTime[1] - curHeaderOptions.rangeTime[0] > MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL ? 10 : 1) * 60 * 1000; const supplementaryInterval =
supplementaryPoints([line], curHeaderOptions.rangeTime, supplementaryInterval, (point) => { (curHeaderOptions.rangeTime[1] - curHeaderOptions.rangeTime[0] > MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL ? 10 : 1) * 60 * 1000;
point.push(extraMetrics as any); supplementaryPoints([line], curHeaderOptions.rangeTime, supplementaryInterval, (point) => {
return point; point.push(extraMetrics as any);
}); return point;
});
}
setMessagesInMetricData(line); setMessagesInMetricData(line);
setDefaultChartLoading(false); setDefaultChartLoading(false);
@@ -299,10 +302,9 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
<div className="cluster-detail-container-main"> <div className="cluster-detail-container-main">
{/* MessageIn 图表 */} {/* MessageIn 图表 */}
<div className={`header-chart-container ${!messagesInMetricData.data.length ? 'header-chart-container-loading' : ''}`}> <div className="header-chart-container">
<Spin spinning={defaultChartLoading}> <Spin spinning={defaultChartLoading}>
{/* TODO: 暂时通过判断是否有图表数据来修复,有时间可以查找下宽度溢出的原因 */} {messagesInMetricData.data && (
{messagesInMetricData.data.length ? (
<> <>
<div className="chart-box-title"> <div className="chart-box-title">
<Tooltip <Tooltip
@@ -322,26 +324,27 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
</span> </span>
</Tooltip> </Tooltip>
</div> </div>
<SingleChart {messagesInMetricData.data.length ? (
chartKey="messagesIn" <SingleChart
chartTypeProp="line" chartKey="messagesIn"
showHeader={false} chartTypeProp="line"
wrapStyle={{ showHeader={false}
width: 'auto', wrapStyle={{
height: 210, width: 'auto',
}} height: 210,
connectEventName="clusterChart" }}
eventBus={busInstance} connectEventName="clusterChart"
propChartData={[messagesInMetricData]} eventBus={busInstance}
{...getChartConfig({ propChartData={[messagesInMetricData]}
// metricName: `${messagesInMetricData.name}{unit|${messagesInMetricData.unit}}`, {...getChartConfig({
lineColor: CHART_LINE_COLORS[0], lineColor: CHART_LINE_COLORS[0],
isDefaultMetric: true, isDefaultMetric: true,
})} })}
/> />
) : (
!defaultChartLoading && <RenderEmpty message="暂无数据" height={200} />
)}
</> </>
) : (
''
)} )}
</Spin> </Spin>
</div> </div>
@@ -408,7 +411,7 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
) : chartLoading ? ( ) : chartLoading ? (
<></> <></>
) : ( ) : (
<Empty description="请先选择指标或刷新" style={{ width: '100%', height: '100%' }} /> <RenderEmpty message="请先选择指标或刷新" />
)} )}
</Row> </Row>
</Spin> </Spin>