mirror of
https://github.com/didi/KnowStreaming.git
synced 2025-12-24 11:52:08 +08:00
fix: 图表展示 bugifx & 优化
This commit is contained in:
@@ -46,30 +46,42 @@ export const supplementaryPoints = (
|
||||
extraCallback?: (point: [number, 0]) => any[]
|
||||
) => {
|
||||
lines.forEach(({ data }) => {
|
||||
// 获取未补点前线条的点的个数
|
||||
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) {
|
||||
let firstPointTimestamp = data[0][0] as number;
|
||||
while (firstPointTimestamp - interval > timeRange[0]) {
|
||||
const prePointTimestamp = firstPointTimestamp - interval;
|
||||
data.unshift(extraCallback ? extraCallback([prePointTimestamp, 0]) : [prePointTimestamp, 0]);
|
||||
const prevPointTimestamp = firstPointTimestamp - interval;
|
||||
data.unshift(extraCallback ? extraCallback([prevPointTimestamp, 0]) : [prevPointTimestamp, 0]);
|
||||
firstPointTimestamp = prevPointTimestamp;
|
||||
len++;
|
||||
i++;
|
||||
firstPointTimestamp = prePointTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
if (i === len - 1) {
|
||||
let lastPointTimestamp = data[len - 1][0] as number;
|
||||
let lastPointTimestamp = data[i][0] as number;
|
||||
while (lastPointTimestamp + interval < timeRange[1]) {
|
||||
const next = lastPointTimestamp + interval;
|
||||
data.push(extraCallback ? extraCallback([next, 0]) : [next, 0]);
|
||||
lastPointTimestamp = next;
|
||||
const nextPointTimestamp = lastPointTimestamp + interval;
|
||||
data.push(extraCallback ? extraCallback([nextPointTimestamp, 0]) : [nextPointTimestamp, 0]);
|
||||
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 len = CHART_COLOR_LIST.length;
|
||||
// series 配置
|
||||
return lines.map((line) => {
|
||||
return lines.map((line, i) => {
|
||||
return {
|
||||
...line,
|
||||
lineStyle: {
|
||||
width: 1.5,
|
||||
},
|
||||
connectNulls: false,
|
||||
symbol: 'emptyCircle',
|
||||
symbolSize: 4,
|
||||
smooth: 0.25,
|
||||
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],
|
||||
endValue: sliderPos[1],
|
||||
zoomOnMouseWheel: false,
|
||||
minValueSpan: 10 * 60 * 1000,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
|
||||
@@ -63,56 +63,63 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-detail-modal-container {
|
||||
position: relative;
|
||||
.expand-icon-box {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
top: 14px;
|
||||
right: 44px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
transition: background-color 0.3s ease;
|
||||
.expand-icon {
|
||||
color: #adb5bc;
|
||||
line-height: 24px;
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(33, 37, 41, 0.04);
|
||||
.expand-icon {
|
||||
color: #74788d;
|
||||
.overview-chart-detail-drawer {
|
||||
.dcloud-spin-nested-loading > div > .dcloud-spin.dcloud-spin-spinning {
|
||||
height: 300px;
|
||||
}
|
||||
&.dcloud-drawer .dcloud-drawer-body {
|
||||
padding: 0 20px;
|
||||
}
|
||||
.detail-header {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
font-weight: normal;
|
||||
.title {
|
||||
font-family: @font-family-bold;
|
||||
font-size: 18px;
|
||||
color: #495057;
|
||||
letter-spacing: 0;
|
||||
.unit {
|
||||
font-family: @font-family-bold;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
.slider-info {
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
font-family: @font-family;
|
||||
color: #303a51;
|
||||
}
|
||||
}
|
||||
.detail-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
.title {
|
||||
font-family: @font-family-bold;
|
||||
font-size: 18px;
|
||||
color: #495057;
|
||||
letter-spacing: 0;
|
||||
.unit {
|
||||
font-family: @font-family-bold;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.5px;
|
||||
.chart-detail-modal-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
.expand-icon-box {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
top: 14px;
|
||||
right: 44px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
transition: background-color 0.3s ease;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,8 +216,8 @@ const DashboardDragChart = (props: PropsType): JSX.Element => {
|
||||
onChange={ksHeaderChange}
|
||||
nodeScopeModule={{
|
||||
customScopeList: scopeList,
|
||||
scopeName: `自定义 ${dashboardType === MetricType.Broker ? 'Broker' : 'Topic'} 范围`,
|
||||
showSearch: dashboardType === MetricType.Topic,
|
||||
scopeName: dashboardType === MetricType.Broker ? 'Broker' : 'Topic',
|
||||
scopeLabel: `自定义 ${dashboardType === MetricType.Broker ? 'Broker' : 'Topic'} 范围`,
|
||||
}}
|
||||
indicatorSelectModule={{
|
||||
hide: false,
|
||||
|
||||
@@ -26,8 +26,8 @@ const OptionsDefault = [
|
||||
const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||
const {
|
||||
customScopeList: customList,
|
||||
scopeName = '自定义节点范围',
|
||||
showSearch = false,
|
||||
scopeName = '',
|
||||
scopeLabel = '自定义范围',
|
||||
searchPlaceholder = '输入内容进行搜索',
|
||||
} = nodeScopeModule;
|
||||
const [topNum, setTopNum] = useState<number>(5);
|
||||
@@ -70,7 +70,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||
change(checkedListTemp, false);
|
||||
setIsTop(false);
|
||||
setTopNum(null);
|
||||
setInputValue(`已选${checkedListTemp?.length}项`);
|
||||
setInputValue(`${checkedListTemp?.length}项`);
|
||||
setPopVisible(false);
|
||||
}
|
||||
};
|
||||
@@ -109,7 +109,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||
{/* <span>时间:</span> */}
|
||||
<div className="flx_con">
|
||||
<div className="flx_l">
|
||||
<h6 className="time_title">选择top范围</h6>
|
||||
<h6 className="time_title">选择 top 范围</h6>
|
||||
<Radio.Group
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
@@ -128,7 +128,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<div className="flx_r">
|
||||
<h6 className="time_title">{scopeName}</h6>
|
||||
<h6 className="time_title">{scopeLabel}</h6>
|
||||
<div className="custom-scope">
|
||||
<div className="check-row">
|
||||
<Checkbox className="check-all" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
|
||||
@@ -136,9 +136,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||
</Checkbox>
|
||||
<Input
|
||||
className="search-input"
|
||||
suffix={
|
||||
<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />
|
||||
}
|
||||
suffix={<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />}
|
||||
size="small"
|
||||
placeholder={searchPlaceholder}
|
||||
onChange={(e) => setScopeSearchValue(e.target.value)}
|
||||
@@ -148,7 +146,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||
<Checkbox.Group style={{ width: '100%' }} onChange={checkChange} value={checkedListTemp}>
|
||||
<Row gutter={[10, 12]}>
|
||||
{customList
|
||||
.filter((item) => !showSearch || item.label.includes(scopeSearchValue))
|
||||
.filter((item) => item.label.includes(scopeSearchValue))
|
||||
.map((item) => (
|
||||
<Col span={12} key={item.value}>
|
||||
<Checkbox value={item.value}>{item.label}</Checkbox>
|
||||
@@ -180,6 +178,7 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
|
||||
return (
|
||||
<>
|
||||
<div id="d-node-scope">
|
||||
<div className="scope-title">{scopeName}筛选:</div>
|
||||
<Popover
|
||||
trigger={['click']}
|
||||
visible={popVisible}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { DRangeTime } from 'knowdesign';
|
||||
import IndicatorDrawer from './IndicatorDrawer';
|
||||
@@ -48,7 +48,7 @@ export interface IcustomScope {
|
||||
export interface InodeScopeModule {
|
||||
customScopeList: IcustomScope[];
|
||||
scopeName?: string;
|
||||
showSearch?: boolean;
|
||||
scopeLabel?: string;
|
||||
searchPlaceholder?: string;
|
||||
change?: () => void;
|
||||
}
|
||||
@@ -138,9 +138,13 @@ const SingleChartHeader = ({
|
||||
};
|
||||
|
||||
const reloadRangeTime = () => {
|
||||
const timeLen = rangeTime[1] - rangeTime[0] || 0;
|
||||
const curTimeStamp = moment().valueOf();
|
||||
setRangeTime([curTimeStamp - timeLen, curTimeStamp]);
|
||||
if (isRelativeRangeTime) {
|
||||
const timeLen = rangeTime[1] - rangeTime[0] || 0;
|
||||
const curTimeStamp = moment().valueOf();
|
||||
setRangeTime([curTimeStamp - timeLen, curTimeStamp]);
|
||||
} else {
|
||||
setRangeTime([...rangeTime]);
|
||||
}
|
||||
};
|
||||
|
||||
const openIndicatorDrawer = () => {
|
||||
@@ -174,12 +178,10 @@ const SingleChartHeader = ({
|
||||
{!hideGridSelect && (
|
||||
<Select className="grid-select" style={{ width: 70 }} value={gridNum} options={GRID_SIZE_OPTIONS} onChange={sizeChange} />
|
||||
)}
|
||||
<Divider type="vertical" style={{ height: 20, top: 0 }} />
|
||||
<Tooltip title="点击指标筛选,可选择指标" placement="bottomRight">
|
||||
<div className="icon-box" onClick={openIndicatorDrawer}>
|
||||
<IconFont className="icon" type="icon-shezhi1" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{(!hideNodeScope || !hideGridSelect) && <Divider type="vertical" style={{ height: 20, top: 0 }} />}
|
||||
<Button type="primary" onClick={openIndicatorDrawer}>
|
||||
指标筛选
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,8 +3,13 @@
|
||||
@import '~knowdesign/es/basic/style/mixins/index';
|
||||
|
||||
#d-node-scope {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
.scope-title {
|
||||
font-size: 14px;
|
||||
color: #74788d;
|
||||
}
|
||||
.input-span {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -29,10 +34,10 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
&.relativeTime {
|
||||
width: 160px;
|
||||
width: 200px;
|
||||
}
|
||||
&.absoluteTime {
|
||||
width: 300px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
input {
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
import moment from 'moment';
|
||||
|
||||
export const CHART_COLOR_LIST = [
|
||||
'#657DFC',
|
||||
'#A7B1EB',
|
||||
'#2AC8E4',
|
||||
'#9DDEEB',
|
||||
'#3991FF',
|
||||
'#556ee6',
|
||||
'#94BEF2',
|
||||
'#95e7ff',
|
||||
'#9DDEEB',
|
||||
'#A7B1EB',
|
||||
'#C2D0E3',
|
||||
'#F5B6B3',
|
||||
'#85C80D',
|
||||
'#C9E795',
|
||||
'#A76CEC',
|
||||
'#CCABF1',
|
||||
'#FF9C1B',
|
||||
'#F5C993',
|
||||
'#FFC300',
|
||||
'#F9D77B',
|
||||
'#12CA7A',
|
||||
'#8BA3C4',
|
||||
'#FF7066',
|
||||
'#F5C993',
|
||||
'#A7E6C7',
|
||||
'#F19FC9',
|
||||
'#AEAEAE',
|
||||
'#D1D1D1',
|
||||
'#F5B6B3',
|
||||
'#C9E795',
|
||||
];
|
||||
|
||||
export const UNIT_MAP = {
|
||||
|
||||
@@ -38,15 +38,17 @@
|
||||
|
||||
&-main {
|
||||
.header-chart-container {
|
||||
&-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
width: 100%;
|
||||
height: 244px;
|
||||
margin-bottom: 12px;
|
||||
.cluster-container-border();
|
||||
.dcloud-spin.dcloud-spin-spinning {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 244px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { MetricType } from '@src/api';
|
||||
import { getDataNumberUnit, getUnit } from '@src/constants/chartConfig';
|
||||
import SingleChartHeader, { KsHeaderOptions } from '@src/components/SingleChartHeader';
|
||||
import { MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL } from '@src/constants/common';
|
||||
import RenderEmpty from '@src/components/RenderEmpty';
|
||||
|
||||
type ChartFilterOptions = Omit<KsHeaderOptions, 'gridNum'>;
|
||||
interface MetricInfo {
|
||||
@@ -64,7 +65,7 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
|
||||
const [messagesInMetricData, setMessagesInMetricData] = useState<MessagesInMetric>({
|
||||
name: 'MessagesIn',
|
||||
unit: '',
|
||||
data: [],
|
||||
data: undefined,
|
||||
});
|
||||
const [curHeaderOptions, setCurHeaderOptions] = useState<ChartFilterOptions>();
|
||||
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));
|
||||
}
|
||||
|
||||
// 补充缺少的图表点
|
||||
const extraMetrics = result[0][2].map((info) => ({
|
||||
...info,
|
||||
value: 0,
|
||||
}));
|
||||
const supplementaryInterval =
|
||||
(curHeaderOptions.rangeTime[1] - curHeaderOptions.rangeTime[0] > MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL ? 10 : 1) * 60 * 1000;
|
||||
supplementaryPoints([line], curHeaderOptions.rangeTime, supplementaryInterval, (point) => {
|
||||
point.push(extraMetrics as any);
|
||||
return point;
|
||||
});
|
||||
if (result.length) {
|
||||
// 补充缺少的图表点
|
||||
const extraMetrics = result[0][2].map((info) => ({
|
||||
...info,
|
||||
value: 0,
|
||||
}));
|
||||
const supplementaryInterval =
|
||||
(curHeaderOptions.rangeTime[1] - curHeaderOptions.rangeTime[0] > MAX_TIME_RANGE_WITH_SMALL_POINT_INTERVAL ? 10 : 1) * 60 * 1000;
|
||||
supplementaryPoints([line], curHeaderOptions.rangeTime, supplementaryInterval, (point) => {
|
||||
point.push(extraMetrics as any);
|
||||
return point;
|
||||
});
|
||||
}
|
||||
|
||||
setMessagesInMetricData(line);
|
||||
setDefaultChartLoading(false);
|
||||
@@ -299,10 +302,9 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
|
||||
|
||||
<div className="cluster-detail-container-main">
|
||||
{/* MessageIn 图表 */}
|
||||
<div className={`header-chart-container ${!messagesInMetricData.data.length ? 'header-chart-container-loading' : ''}`}>
|
||||
<div className="header-chart-container">
|
||||
<Spin spinning={defaultChartLoading}>
|
||||
{/* TODO: 暂时通过判断是否有图表数据来修复,有时间可以查找下宽度溢出的原因 */}
|
||||
{messagesInMetricData.data.length ? (
|
||||
{messagesInMetricData.data && (
|
||||
<>
|
||||
<div className="chart-box-title">
|
||||
<Tooltip
|
||||
@@ -322,26 +324,27 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<SingleChart
|
||||
chartKey="messagesIn"
|
||||
chartTypeProp="line"
|
||||
showHeader={false}
|
||||
wrapStyle={{
|
||||
width: 'auto',
|
||||
height: 210,
|
||||
}}
|
||||
connectEventName="clusterChart"
|
||||
eventBus={busInstance}
|
||||
propChartData={[messagesInMetricData]}
|
||||
{...getChartConfig({
|
||||
// metricName: `${messagesInMetricData.name}{unit|(${messagesInMetricData.unit})}`,
|
||||
lineColor: CHART_LINE_COLORS[0],
|
||||
isDefaultMetric: true,
|
||||
})}
|
||||
/>
|
||||
{messagesInMetricData.data.length ? (
|
||||
<SingleChart
|
||||
chartKey="messagesIn"
|
||||
chartTypeProp="line"
|
||||
showHeader={false}
|
||||
wrapStyle={{
|
||||
width: 'auto',
|
||||
height: 210,
|
||||
}}
|
||||
connectEventName="clusterChart"
|
||||
eventBus={busInstance}
|
||||
propChartData={[messagesInMetricData]}
|
||||
{...getChartConfig({
|
||||
lineColor: CHART_LINE_COLORS[0],
|
||||
isDefaultMetric: true,
|
||||
})}
|
||||
/>
|
||||
) : (
|
||||
!defaultChartLoading && <RenderEmpty message="暂无数据" height={200} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</Spin>
|
||||
</div>
|
||||
@@ -408,7 +411,7 @@ const DetailChart = (props: { children: JSX.Element }): JSX.Element => {
|
||||
) : chartLoading ? (
|
||||
<></>
|
||||
) : (
|
||||
<Empty description="请先选择指标或刷新" style={{ width: '100%', height: '100%' }} />
|
||||
<RenderEmpty message="请先选择指标或刷新" />
|
||||
)}
|
||||
</Row>
|
||||
</Spin>
|
||||
|
||||
Reference in New Issue
Block a user