This commit is contained in:
wintsa 2026-05-22 09:12:38 +08:00
parent 1d12668e9b
commit c413072afc
6 changed files with 393 additions and 129 deletions

View File

@ -0,0 +1,2 @@
[ 221ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://127.0.0.1:5179/node_modules/.vite/deps/react-dom_client.js?v=eccfa23d:20102
[ 1056ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://127.0.0.1:5179/favicon.ico:0

View File

@ -0,0 +1,83 @@
- main [ref=e3]:
- generic:
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- generic: 众为数字化管理平台
- region "年度费用模板" [ref=e4]:
- generic "筛选条件" [ref=e5]:
- button "省市区" [ref=e6] [cursor=pointer]:
- img [ref=e7]
- generic [ref=e11]: 省市区
- button "自然地理区位" [ref=e12] [cursor=pointer]:
- img [ref=e13]
- generic [ref=e16]: 自然地理区位
- button "设施类别" [ref=e17] [cursor=pointer]:
- img [ref=e18]
- generic [ref=e22]: 设施类别
- button "建设阶段" [ref=e23] [cursor=pointer]:
- img [ref=e24]
- generic [ref=e29]: 建设阶段
- button "规划形式" [ref=e30] [cursor=pointer]:
- img [ref=e31]
- generic [ref=e36]: 规划形式
- region "年度总费用图表" [ref=e37]:
- generic [ref=e38]:
- button "纵坐标:造价(元)" [ref=e40] [cursor=pointer]: 造价(元)
- generic [ref=e41]:
- figure "图表共有0个系列":
- generic [ref=e42]:
- img "interactive chart":
- generic:
- img
- img
- region [ref=e43]
- toolbar "标注" [ref=e44]:
- button "库" [disabled] [ref=e45] [cursor=pointer]:
- generic:
- generic:
- button "指" [disabled] [ref=e46] [cursor=pointer]:
- generic:
- generic:
- button "均" [disabled] [ref=e47] [cursor=pointer]:
- generic:
- button "Line Tool" [disabled] [ref=e48]
- button "Text Tool" [disabled] [ref=e49]
- button "Shape Tool" [disabled] [ref=e50]
- button "Fibonacci Tool" [disabled] [ref=e51]
- button "全屏(F11)" [disabled] [ref=e52] [cursor=pointer]
- button "Clear annotations" [disabled] [ref=e53]
- button "切换到表格" [disabled] [ref=e54] [cursor=pointer]:
- generic:
- generic:
- status:
- generic: 请选择右侧分类项
- toolbar "缩放" [ref=e55]:
- button "缩小" [disabled] [ref=e56]
- button "放大" [ref=e57] [cursor=pointer]
- button "左移" [disabled] [ref=e58]
- button "右移" [disabled] [ref=e59]
- button "重置" [disabled] [ref=e60]
- complementary "选择内容" [ref=e61]:
- tablist "选择内容切换项" [ref=e62]:
- tab "自然地理区位" [selected] [ref=e63] [cursor=pointer]
- tab "设施类别" [ref=e64] [cursor=pointer]
- tab "建设阶段" [ref=e65] [cursor=pointer]
- tab "规划形式" [ref=e66] [cursor=pointer]
- generic [ref=e67]:
- generic [ref=e68]: 自然地理区位
- generic [ref=e69]: Unexpected token '<', "<!doctype "... is not valid JSON

View File

@ -6,7 +6,7 @@
<title>AG Chart Service</title>
</head>
<body>
<div id="sbChart"></div>
<div id="ztChart"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -5,6 +5,7 @@ import {
AllCommunityModule as AgGridAllCommunityModule,
ModuleRegistry as AgGridModuleRegistry,
type ColDef,
type ColGroupDef,
type ValueFormatterParams,
} from 'ag-grid-community';
import type { AgCartesianChartOptions } from 'ag-charts-community';
@ -120,6 +121,7 @@ const defaultTemplateFilterNode = {
filterKey: 'templateLibrary',
label: '默认模板',
} as const;
const overallSummaryKey = 'summary';
// const mockGeoLocationPayload = {
// checkStrictly: true,
@ -161,11 +163,21 @@ type ApiBuildingFunctionStat = {
max_value?: number | null;
avg_value?: number | null;
median_value?: number | null;
threshold_low_value?: number | null;
threshold_center_value?: number | null;
threshold_high_value?: number | null;
stddev_value?: number | null;
standard_deviation?: number | null;
iqr_value?: number | null;
quartile_range?: number | null;
variation_coefficient?: number | null;
coefficient_of_variation?: number | null;
data_count?: number | null;
};
type ApiBuildingFunctionStatBatchItem = {
key?: string;
data?: ApiBuildingFunctionStat[];
summary?: ApiBuildingFunctionStat | null;
};
type ChartDatum = {
groupName: string;
@ -173,6 +185,12 @@ type ChartDatum = {
maxValue: number | null;
avgValue: number | null;
medianValue: number | null;
thresholdLowValue: number | null;
thresholdCenterValue: number | null;
thresholdHighValue: number | null;
standardDeviation: number | null;
interquartileRange: number | null;
coefficientOfVariation: number | null;
dataCount: number | null;
};
type TreeNode = {
@ -201,9 +219,17 @@ type SelectedFilterNode = {
type PivotGridRow = {
year: string;
name: string;
summary: boolean;
lowValue: number | null;
centerValue: number | null;
highValue: number | null;
maxValue: number | null;
minValue: number | null;
avgValue: number | null;
medianValue: number | null;
standardDeviation: number | null;
interquartileRange: number | null;
coefficientOfVariation: number | null;
dataCount: number | null;
};
@ -240,12 +266,22 @@ function formatChartValue(value: number, metricKey: MetricKey) {
}
function normalizeStat(row: ApiBuildingFunctionStat): ChartDatum {
const avgValue = row.avg_value ?? null;
const medianValue = row.median_value ?? null;
const fallbackThresholdLowValue = avgValue == null || medianValue == null ? null : Math.min(avgValue, medianValue);
const fallbackThresholdHighValue = avgValue == null || medianValue == null ? null : Math.max(avgValue, medianValue);
return {
groupName: row.group_name || String(row.group_key ?? '未命名'),
minValue: row.min_value ?? null,
maxValue: row.max_value ?? null,
avgValue: row.avg_value ?? null,
medianValue: row.median_value ?? null,
avgValue,
medianValue,
thresholdLowValue: row.threshold_low_value ?? fallbackThresholdLowValue,
thresholdCenterValue: row.threshold_center_value ?? medianValue,
thresholdHighValue: row.threshold_high_value ?? fallbackThresholdHighValue,
standardDeviation: row.stddev_value ?? row.standard_deviation ?? null,
interquartileRange: row.iqr_value ?? row.quartile_range ?? null,
coefficientOfVariation: row.variation_coefficient ?? row.coefficient_of_variation ?? null,
dataCount: row.data_count ?? null,
};
}
@ -686,6 +722,7 @@ function App() {
const [statisticKey, setStatisticKey] = useState<StatisticKey>('avgValue');
const [metricKey, setMetricKey] = useState<MetricKey>('cost');
const [chartViewKey, setChartViewKey] = useState<ChartViewKey>('trend');
const [workspaceFullscreen, setWorkspaceFullscreen] = useState(false);
const [statisticMenuOpen, setStatisticMenuOpen] = useState(false);
const [metricMenuOpen, setMetricMenuOpen] = useState(false);
const [activeContentKey, setActiveContentKey] = useState<ContentKey>('geoLocation');
@ -709,6 +746,7 @@ function App() {
});
const [selectedContentNodes, setSelectedContentNodes] = useState<SelectedContentNode[]>([]);
const [chartDataBySelection, setChartDataBySelection] = useState<Record<string, ChartDatum[]>>({});
const [chartSummaryBySelection, setChartSummaryBySelection] = useState<Record<string, ChartDatum>>({});
const [chartQueryVersion, setChartQueryVersion] = useState(0);
const [loading, setLoading] = useState(false);
const [loadError, setLoadError] = useState<string | null>(null);
@ -798,6 +836,7 @@ function App() {
const currentViewShortLabel = chartViewKey === 'pivot' ? '表' : '趋';
const pivotToggleActionLabel = chartViewKey === 'pivot' ? '切换到趋势图' : '切换到表格';
const pivotToggleTitle = `${chartViewKey === 'pivot' ? '当前表格' : '当前趋势图'}${pivotToggleActionLabel}`;
const fullscreenToggleLabel = workspaceFullscreen ? '退出全屏' : '全屏';
const activeContent = contentOptions.find((option) => option.key === activeContentKey) ?? contentOptions[0];
const activeTree = treeByContent[activeContentKey];
const activeFilter = filterOptions.find((option) => option.key === filterModalKey);
@ -846,15 +885,49 @@ function App() {
return rows.map((datum) => ({
year: datum.groupName,
name: node.label,
summary: false,
lowValue: datum.thresholdLowValue,
centerValue: datum.thresholdCenterValue,
highValue: datum.thresholdHighValue,
maxValue: datum.maxValue,
minValue: datum.minValue,
avgValue: datum.avgValue,
medianValue: datum.medianValue,
standardDeviation: datum.standardDeviation,
interquartileRange: datum.interquartileRange,
coefficientOfVariation: datum.coefficientOfVariation,
dataCount: datum.dataCount,
}));
}),
[chartDataBySelection, selectedContentNodes],
);
const pivotGridColumnDefs = useMemo<ColDef<PivotGridRow>[]>(
const pivotGridPinnedBottomRowData = useMemo<PivotGridRow[]>(
() => {
const datum = chartSummaryBySelection[overallSummaryKey]
?? selectedContentNodes
.map((node) => chartSummaryBySelection[getSelectionKey(node.contentKey, node.id)])
.find(Boolean);
if (!datum) return [];
return [{
year: '',
name: '统计',
summary: true,
lowValue: datum.thresholdLowValue,
centerValue: datum.thresholdCenterValue,
highValue: datum.thresholdHighValue,
maxValue: datum.maxValue,
minValue: datum.minValue,
avgValue: datum.avgValue,
medianValue: datum.medianValue,
standardDeviation: datum.standardDeviation,
interquartileRange: datum.interquartileRange,
coefficientOfVariation: datum.coefficientOfVariation,
dataCount: datum.dataCount,
}];
},
[chartSummaryBySelection, selectedContentNodes],
);
const pivotGridColumnDefs = useMemo<(ColDef<PivotGridRow> | ColGroupDef<PivotGridRow>)[]>(
() => {
const valueFormatter = ({ value }: ValueFormatterParams<PivotGridRow, number | null>) => (
value == null ? '' : formatChartValue(Number(value), requestMetricKey)
@ -874,34 +947,86 @@ function App() {
minWidth: 108,
},
{
field: 'maxValue',
headerName: '最大值',
type: 'numericColumn',
minWidth: 78,
valueFormatter,
headerName: '基准阀值',
children: [
{
field: 'lowValue',
headerName: '低值',
type: 'numericColumn',
minWidth: 78,
valueFormatter,
},
{
field: 'centerValue',
headerName: '中心值',
type: 'numericColumn',
minWidth: 78,
valueFormatter,
},
{
field: 'highValue',
headerName: '高值',
type: 'numericColumn',
minWidth: 78,
valueFormatter,
},
],
},
{
field: 'minValue',
headerName: '最小值',
type: 'numericColumn',
minWidth: 78,
valueFormatter,
},
{
field: 'avgValue',
headerName: '平均值',
type: 'numericColumn',
minWidth: 78,
valueFormatter,
},
{
field: 'dataCount',
headerName: '数量',
type: 'numericColumn',
minWidth: 70,
valueFormatter: ({ value }: ValueFormatterParams<PivotGridRow, number | null>) => (
value == null ? '' : formatChartValue(Number(value), 'dataCount')
),
headerName: '样本统计值(括号显示样本数量)',
children: [
{
field: 'maxValue',
headerName: '最大值',
type: 'numericColumn',
minWidth: 78,
valueFormatter,
},
{
field: 'minValue',
headerName: '最小值',
type: 'numericColumn',
minWidth: 78,
valueFormatter,
},
{
field: 'avgValue',
headerName: '平均值',
type: 'numericColumn',
minWidth: 78,
valueFormatter,
},
{
field: 'medianValue',
headerName: '中位数',
type: 'numericColumn',
minWidth: 78,
valueFormatter,
},
{
field: 'standardDeviation',
headerName: '标准差',
type: 'numericColumn',
minWidth: 78,
valueFormatter,
},
{
field: 'interquartileRange',
headerName: '四分位距',
type: 'numericColumn',
minWidth: 88,
valueFormatter,
},
{
field: 'coefficientOfVariation',
headerName: '变异系数',
type: 'numericColumn',
minWidth: 88,
valueFormatter: ({ value }: ValueFormatterParams<PivotGridRow, number | null>) => (
value == null ? '' : formatNumber(Number(value), 4)
),
},
],
},
];
},
@ -1172,6 +1297,10 @@ function App() {
const { [selectionKey]: _removed, ...rest } = data;
return rest;
});
setChartSummaryBySelection((data) => {
const { [selectionKey]: _removed, ...rest } = data;
return rest;
});
return current.filter((_, index) => index !== existingIndex);
}
@ -1185,6 +1314,7 @@ function App() {
setActiveContentKey(nextContentKey);
setSelectedContentNodes([]);
setChartDataBySelection({});
setChartSummaryBySelection({});
setLoadError(null);
setLoadingHint('');
setLoading(false);
@ -1416,6 +1546,7 @@ function App() {
resetIndicatorTreeState();
}
setChartDataBySelection({});
setChartSummaryBySelection({});
setLoadError(null);
if (selectedContentNodes.length > 0) {
setLoadingHint('正在按筛选条件重新计算');
@ -1448,6 +1579,7 @@ function App() {
resetIndicatorTreeState();
}
setChartDataBySelection({});
setChartSummaryBySelection({});
setLoadError(null);
if (selectedContentNodes.length > 0) {
setLoadingHint('正在按筛选条件重新计算');
@ -1459,6 +1591,7 @@ function App() {
const updateMetricKey = (nextMetricKey: MetricKey) => {
setMetricKey(nextMetricKey);
setChartDataBySelection({});
setChartSummaryBySelection({});
setLoadError(null);
setLoadingHint('正在重新加载数据');
setLoading(true);
@ -1471,12 +1604,33 @@ function App() {
setStatisticMenuOpen(false);
}, []);
const toggleWorkspaceFullscreen = useCallback(() => {
const fullscreenTarget = workspaceRef.current;
if (!fullscreenTarget) return;
if (document.fullscreenElement === fullscreenTarget) {
void document.exitFullscreen();
} else {
void fullscreenTarget.requestFullscreen();
}
}, []);
const openGridFilterModal = (filterKey: 'templateLibrary' | 'indicatorTree') => {
setMetricMenuOpen(false);
setStatisticMenuOpen(false);
openFilterModal(filterKey);
};
useEffect(() => {
const handleFullscreenChange = () => {
setWorkspaceFullscreen(document.fullscreenElement === workspaceRef.current);
};
handleFullscreenChange();
document.addEventListener('fullscreenchange', handleFullscreenChange);
return () => {
document.removeEventListener('fullscreenchange', handleFullscreenChange);
};
}, []);
useEffect(() => {
if (!contentTreeConfigs[activeContentKey]) return;
if (treeByContent[activeContentKey].length > 0 || treeInitialLoadStartedRef.current[activeContentKey]) return;
@ -1515,10 +1669,6 @@ function App() {
async function loadStats() {
if (selectedContentNodes.length === 0) {
setChartDataBySelection({});
setLoading(false);
setLoadingHint('');
setLoadError(null);
return;
}
setLoading(true);
@ -1526,7 +1676,7 @@ function App() {
setLoadError(null);
try {
const hasAnyMissingNode = selectedContentNodes.some((node) => !chartDataBySelection[getSelectionKey(node.contentKey, node.id)]);
if (!hasAnyMissingNode) {
if (selectedContentNodes.length > 0 && !hasAnyMissingNode && chartSummaryBySelection[overallSummaryKey]) {
setLoading(false);
setLoadingHint('');
return;
@ -1557,10 +1707,19 @@ function App() {
const results = (payload.data ?? [])
.filter((item) => item.key)
.map((item) => [item.key as string, (item.data ?? []).map(normalizeStat).slice(0, 36)] as const);
const summaries = (payload.data ?? [])
.filter((item) => item.key && item.summary)
.map((item) => [item.key as string, normalizeStat(item.summary as ApiBuildingFunctionStat)] as const);
const overallSummary = (payload.data ?? []).find((item) => item.summary)?.summary;
setChartDataBySelection((current) => ({
...current,
...Object.fromEntries(results),
}));
setChartSummaryBySelection((current) => ({
...current,
...Object.fromEntries(summaries),
...(overallSummary ? { [overallSummaryKey]: normalizeStat(overallSummary) } : {}),
}));
} catch (error) {
if (controller.signal.aborted) return;
setLoadError(error instanceof Error ? error.message : '接口请求失败');
@ -1654,20 +1813,12 @@ function App() {
}
};
const toggleFullscreen = () => {
if (document.fullscreenElement === fullscreenTarget) {
void document.exitFullscreen();
} else {
void fullscreenTarget.requestFullscreen();
}
};
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key !== 'F11') return;
event.preventDefault();
event.stopPropagation();
toggleFullscreen();
toggleWorkspaceFullscreen();
};
const handleFullscreenChange = () => {
@ -1689,7 +1840,7 @@ function App() {
} else if (button.classList.contains('chart-pivot-button')) {
togglePivotView();
} else {
toggleFullscreen();
toggleWorkspaceFullscreen();
}
};
@ -1762,6 +1913,7 @@ function App() {
selectedContentNodes.length,
statisticMenuOpen,
togglePivotView,
toggleWorkspaceFullscreen,
]);
const chartOptions = useMemo<AgCartesianChartOptions>(() => {
@ -2073,6 +2225,7 @@ function App() {
planningForm: [],
});
setChartDataBySelection({});
setChartSummaryBySelection({});
setLoadError(null);
if (selectedContentNodes.length > 0) {
setLoadingHint('正在按筛选条件重新计算');
@ -2138,9 +2291,23 @@ function App() {
>
{currentViewShortLabel}
</button>
<button
className="chart-grid-tool-button chart-grid-tool-button--fullscreen"
type="button"
title={fullscreenToggleLabel}
aria-label={fullscreenToggleLabel}
aria-pressed={workspaceFullscreen}
onClick={toggleWorkspaceFullscreen}
>
<i
className={`anticon ${workspaceFullscreen ? 'anticon-shrink' : 'anticon-arrow-salt'} ag-charts-myButton-fullScreen ag-charts-diy-button`}
aria-hidden="true"
/>
</button>
{renderMetricSwitcher('grid')}
<AgGridReact<PivotGridRow>
rowData={pivotGridRowData}
pinnedBottomRowData={pivotGridPinnedBottomRowData}
columnDefs={pivotGridColumnDefs}
containerStyle={{ width: '100%', height: '100%' }}
theme="legacy"
@ -2190,83 +2357,83 @@ function App() {
)}
</div>
</aside>
</section>
{filterModalKey && activeFilter ? (
<div className="filter-modal-backdrop" role="presentation" onMouseDown={closeFilterModal}>
<section
className="filter-modal"
role="dialog"
aria-modal="true"
aria-label={`${activeFilter.label}筛选`}
onMouseDown={(event) => event.stopPropagation()}
>
<header className="filter-modal-header">
<h2>{activeFilter.label}</h2>
<button className="filter-modal-close" type="button" aria-label="关闭" onClick={closeFilterModal}>×</button>
</header>
<div className="filter-modal-search">
<input
type="search"
value={filterSearchValue}
placeholder="搜索"
onChange={(event) => {
const nextValue = event.target.value;
setFilterSearchValue(nextValue);
if (!filterSearchComposingRef.current && filterModalKey) {
scheduleFilterSearch(filterModalKey, nextValue);
}
}}
onCompositionStart={() => {
filterSearchComposingRef.current = true;
}}
onCompositionEnd={(event) => {
filterSearchComposingRef.current = false;
const nextValue = event.currentTarget.value;
setFilterSearchValue(nextValue);
if (filterModalKey) {
scheduleFilterSearch(filterModalKey, nextValue);
}
}}
/>
</div>
<div className="filter-modal-selected">
{draftFilterNodes.length > 0 ? `已选 ${draftFilterNodes.length}` : '未选择'}
</div>
<div className="filter-modal-tree">
{activeFilterTreeLoading ? (
<div className="content-tree-empty"></div>
) : activeFilterTreeError ? (
<div className="content-tree-empty">{activeFilterTreeError}</div>
) : activeFilterDisplayTree.length > 0 ? (
renderFilterTreeNodes(activeFilterDisplayTree, filterModalKey, draftFilterNodeKeys, toggleFilterTreeNode, toggleDraftFilterNode)
) : (
<div className="content-tree-empty">{trimmedFilterSearchValue ? '无匹配结果' : '暂无数据'}</div>
)}
</div>
<footer className="filter-modal-actions">
<button
className="filter-modal-clear"
type="button"
onClick={() => setDraftFilterNodes(
isTemplateFilterKey(filterModalKey)
? getDefaultTemplateFilterNodes()
: isIndicatorTreeFilterKey(filterModalKey)
? defaultIndicatorTreeNodes
: [],
{filterModalKey && activeFilter ? (
<div className="filter-modal-backdrop" role="presentation" onMouseDown={closeFilterModal}>
<section
className="filter-modal"
role="dialog"
aria-modal="true"
aria-label={`${activeFilter.label}筛选`}
onMouseDown={(event) => event.stopPropagation()}
>
<header className="filter-modal-header">
<h2>{activeFilter.label}</h2>
<button className="filter-modal-close" type="button" aria-label="关闭" onClick={closeFilterModal}>×</button>
</header>
<div className="filter-modal-search">
<input
type="search"
value={filterSearchValue}
placeholder="搜索"
onChange={(event) => {
const nextValue = event.target.value;
setFilterSearchValue(nextValue);
if (!filterSearchComposingRef.current && filterModalKey) {
scheduleFilterSearch(filterModalKey, nextValue);
}
}}
onCompositionStart={() => {
filterSearchComposingRef.current = true;
}}
onCompositionEnd={(event) => {
filterSearchComposingRef.current = false;
const nextValue = event.currentTarget.value;
setFilterSearchValue(nextValue);
if (filterModalKey) {
scheduleFilterSearch(filterModalKey, nextValue);
}
}}
/>
</div>
<div className="filter-modal-selected">
{draftFilterNodes.length > 0 ? `已选 ${draftFilterNodes.length}` : '未选择'}
</div>
<div className="filter-modal-tree">
{activeFilterTreeLoading ? (
<div className="content-tree-empty"></div>
) : activeFilterTreeError ? (
<div className="content-tree-empty">{activeFilterTreeError}</div>
) : activeFilterDisplayTree.length > 0 ? (
renderFilterTreeNodes(activeFilterDisplayTree, filterModalKey, draftFilterNodeKeys, toggleFilterTreeNode, toggleDraftFilterNode)
) : (
<div className="content-tree-empty">{trimmedFilterSearchValue ? '无匹配结果' : '暂无数据'}</div>
)}
>
</button>
<button className="filter-modal-cancel" type="button" onClick={closeFilterModal}>
</button>
<button className="filter-modal-confirm" type="button" onClick={applyFilterModal}>
</button>
</footer>
</section>
</div>
) : null}
</div>
<footer className="filter-modal-actions">
<button
className="filter-modal-clear"
type="button"
onClick={() => setDraftFilterNodes(
isTemplateFilterKey(filterModalKey)
? getDefaultTemplateFilterNodes()
: isIndicatorTreeFilterKey(filterModalKey)
? defaultIndicatorTreeNodes
: [],
)}
>
</button>
<button className="filter-modal-cancel" type="button" onClick={closeFilterModal}>
</button>
<button className="filter-modal-confirm" type="button" onClick={applyFilterModal}>
</button>
</footer>
</section>
</div>
) : null}
</section>
</main>
);
}

View File

@ -14,7 +14,7 @@ declare global {
}
}
const chartContainerId = 'sbChart';
const chartContainerId = 'ztChart';
const getRootRegistry = () => {
window.__chartReactRoots ??= new WeakMap<Element, ReturnType<typeof createRoot>>();

View File

@ -471,6 +471,10 @@ button {
top: 80px;
}
.chart-grid-tool-button--fullscreen {
top: 114px;
}
.chart-grid-tool-button:hover {
color: #0078a8;
border-color: rgba(0, 120, 168, 0.36);
@ -501,7 +505,7 @@ button {
.metric-switcher--grid {
left: 24px;
top: 114px;
top: 148px;
}
.metric-switcher-button {
@ -661,15 +665,19 @@ button {
box-shadow: 0 1px 5px rgba(69, 54, 36, 0.12);
}
.chart-frame .ag-charts-myButton-fullScreen {
.chart-frame .ag-charts-myButton-fullScreen,
.chart-grid-tool-button .ag-charts-myButton-fullScreen {
position: relative;
display: block;
width: 16px;
height: 16px;
margin: 4px auto;
}
.chart-frame .ag-charts-myButton-fullScreen::before,
.chart-frame .ag-charts-myButton-fullScreen::after {
.chart-frame .ag-charts-myButton-fullScreen::after,
.chart-grid-tool-button .ag-charts-myButton-fullScreen::before,
.chart-grid-tool-button .ag-charts-myButton-fullScreen::after {
position: absolute;
inset: 0;
display: block;
@ -678,19 +686,22 @@ button {
content: "";
}
.chart-frame .anticon-arrow-salt::before {
.chart-frame .anticon-arrow-salt::before,
.chart-grid-tool-button .anticon-arrow-salt::before {
border-top: 1px solid currentColor;
border-right: 1px solid currentColor;
clip-path: polygon(54% 0, 100% 0, 100% 46%, 86% 46%, 86% 14%, 54% 14%);
}
.chart-frame .anticon-arrow-salt::after {
.chart-frame .anticon-arrow-salt::after,
.chart-grid-tool-button .anticon-arrow-salt::after {
border-bottom: 1px solid currentColor;
border-left: 1px solid currentColor;
clip-path: polygon(0 54%, 14% 54%, 14% 86%, 46% 86%, 46% 100%, 0 100%);
}
.chart-frame .anticon-shrink::before {
.chart-frame .anticon-shrink::before,
.chart-grid-tool-button .anticon-shrink::before {
top: 2px;
left: 2px;
width: 6px;
@ -699,7 +710,8 @@ button {
border-bottom: 1px solid currentColor;
}
.chart-frame .anticon-shrink::after {
.chart-frame .anticon-shrink::after,
.chart-grid-tool-button .anticon-shrink::after {
right: 2px;
bottom: 2px;
left: auto;