Skip to content

Commit ed85e42

Browse files
committed
feat: enhance canvas UI with navigator controls and layout improvements
- Added navigator mode selector with auto/heatmap/detail options in canvas navigator - Reorganized legend items into cleaner grid layout with grouped path indicators - Added centerOn method to renderer for programmatic viewport control - Fixed PNG export to properly handle WebGL canvas snapshots - Improved button and status indicator styling for better visual consistency - Added viewport change event dispatching for better component
1 parent 4d43b5e commit ed85e42

File tree

10 files changed

+603
-109
lines changed

10 files changed

+603
-109
lines changed

index.html

Lines changed: 83 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,16 @@ <h2>可视化面板</h2>
255255

256256
<!-- 缩略图导航 -->
257257
<div class="canvas-navigator">
258+
<div class="navigator-controls">
259+
<label for="nav-mode-select" class="sr-only"
260+
>Navigator Mode</label
261+
>
262+
<select id="nav-mode-select" title="缩略图模式">
263+
<option value="auto" selected>自动</option>
264+
<option value="heatmap">热力</option>
265+
<option value="detail">细节</option>
266+
</select>
267+
</div>
258268
<div class="navigator-viewport">
259269
<canvas id="navigator-canvas"></canvas>
260270
</div>
@@ -280,42 +290,70 @@ <h2>数据解读</h2>
280290
<h3>图例</h3>
281291
<div class="legend-grid">
282292
<div class="legend-item-new" data-layer="obstacles">
283-
<button class="legend-eye" aria-pressed="true" title="显示/隐藏"></button>
284-
<span class="legend-icon obstacle-icon" aria-hidden="true"></span>
293+
<button
294+
class="legend-eye"
295+
aria-pressed="true"
296+
title="显示/隐藏"
297+
></button>
298+
<span
299+
class="legend-icon obstacle-icon"
300+
aria-hidden="true"
301+
></span>
285302
<span class="legend-label">障碍物</span>
286303
</div>
287304
<div class="legend-item-new" data-layer="networkNodes">
288-
<button class="legend-eye" aria-pressed="true" title="显示/隐藏"></button>
305+
<button
306+
class="legend-eye"
307+
aria-pressed="true"
308+
title="显示/隐藏"
309+
></button>
289310
<span class="legend-icon node-icon" aria-hidden="true"></span>
290311
<span class="legend-label">网络节点</span>
291312
</div>
292313
<div class="legend-item-new" data-layer="networkEdges">
293-
<button class="legend-eye" aria-pressed="true" title="显示/隐藏"></button>
314+
<button
315+
class="legend-eye"
316+
aria-pressed="true"
317+
title="显示/隐藏"
318+
></button>
294319
<span class="legend-icon edge-icon" aria-hidden="true"></span>
295320
<span class="legend-label">网络边</span>
296321
</div>
297322
<div class="legend-item-new" data-layer="baseTriangulation">
298-
<button class="legend-eye" aria-pressed="true" title="显示/隐藏"></button>
323+
<button
324+
class="legend-eye"
325+
aria-pressed="true"
326+
title="显示/隐藏"
327+
></button>
299328
<span class="legend-icon base-icon" aria-hidden="true"></span>
300329
<span class="legend-label">基础三角化</span>
301330
</div>
302331
<div class="legend-item-new" data-layer="voronoi">
303-
<button class="legend-eye" aria-pressed="true" title="显示/隐藏"></button>
304-
<span class="legend-icon voronoi-icon" aria-hidden="true"></span>
332+
<button
333+
class="legend-eye"
334+
aria-pressed="true"
335+
title="显示/隐藏"
336+
></button>
337+
<span
338+
class="legend-icon voronoi-icon"
339+
aria-hidden="true"
340+
></span>
305341
<span class="legend-label">Voronoi 骨架</span>
306342
</div>
307343
<div class="legend-sep"></div>
308-
<div class="legend-item-new" aria-hidden="true">
309-
<span class="legend-icon start-icon"></span>
310-
<span class="legend-label">起点</span>
311-
</div>
312-
<div class="legend-item-new" aria-hidden="true">
313-
<span class="legend-icon end-icon"></span>
314-
<span class="legend-label">终点</span>
315-
</div>
316-
<div class="legend-item-new" aria-hidden="true">
317-
<span class="legend-icon path-icon"></span>
318-
<span class="legend-label">路径</span>
344+
<div class="legend-item-new-wrap">
345+
<div class="legend-item-new" aria-hidden="true">
346+
<span class="legend-icon start-icon"></span>
347+
<span class="legend-label">起点</span>
348+
</div>
349+
<div class="legend-item-new" aria-hidden="true">
350+
<span class="legend-icon end-icon"></span>
351+
<span class="legend-label">终点</span>
352+
</div>
353+
<div class="legend-item-new" aria-hidden="true">
354+
<span class="legend-icon path-icon"></span>
355+
<span class="legend-label">路径</span>
356+
</div>
319357
</div>
320358
</div>
321359
</div>
@@ -325,9 +363,28 @@ <h3>图例</h3>
325363
<div class="card-header-row">
326364
<h3>路径统计</h3>
327365
<div class="card-actions">
328-
<button id="path-refresh-btn" class="btn-secondary btn-compact" title="刷新上次路径">刷新</button>
329-
<button id="path-clear-btn" class="btn-secondary btn-compact" title="清空当前路径">清空</button>
330-
<button id="path-collapse-btn" class="btn-secondary btn-icon" title="折叠/展开" aria-expanded="true"></button>
366+
<button
367+
id="path-refresh-btn"
368+
class="btn-secondary btn-compact"
369+
title="刷新上次路径"
370+
>
371+
刷新
372+
</button>
373+
<button
374+
id="path-clear-btn"
375+
class="btn-secondary btn-compact"
376+
title="清空当前路径"
377+
>
378+
清空
379+
</button>
380+
<button
381+
id="path-collapse-btn"
382+
class="btn-secondary btn-icon"
383+
title="折叠/展开"
384+
aria-expanded="true"
385+
>
386+
387+
</button>
331388
</div>
332389
</div>
333390
<div id="path-stats" class="stats-grid">
@@ -357,7 +414,11 @@ <h3>路径统计</h3>
357414
<div class="interpretation-card" id="perf-card">
358415
<div class="card-header-row">
359416
<h3>性能数据</h3>
360-
<span id="perf-status-dot" class="status-dot ok" title="系统状态"></span>
417+
<span
418+
id="perf-status-dot"
419+
class="status-dot ok"
420+
title="系统状态"
421+
></span>
361422
</div>
362423
<div class="stats-grid">
363424
<div class="stat-item">

src/core/renderer.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,9 @@ class Renderer {
268268

269269
this.drawing.updateTransform(this.transform);
270270
this.interaction.updateTransform(this.transform);
271-
// 缩放后重建基础三角化虚线
271+
// 缩放后按视觉密度重建基础三角化虚线
272272
this.rebuildAllOverlays();
273+
try { window.dispatchEvent(new CustomEvent('renderer-viewport-changed')); } catch (err) {}
273274

274275
// 设置导航图数据并启用交互
275276
this.interaction.setRoadNetData(navGraphData);
@@ -552,6 +553,7 @@ class Renderer {
552553

553554
// 更新最后位置
554555
this.viewState.lastPosition = { x: e.clientX, y: e.clientY };
556+
try { window.dispatchEvent(new CustomEvent('renderer-viewport-changed')); } catch (err) {}
555557
}
556558
});
557559

@@ -639,9 +641,27 @@ class Renderer {
639641
this.interaction.updateTransform(this.transform);
640642
// 重置后重建基础三角化虚线
641643
this.rebuildAllOverlays();
644+
try { window.dispatchEvent(new CustomEvent('renderer-viewport-changed')); } catch (err) {}
642645
console.log('↺ [Renderer] View reset');
643646
}
644647

648+
/**
649+
* 将视图中心移动到指定世界坐标(不改变缩放)
650+
*/
651+
centerOn(worldX, worldY) {
652+
if (!this.app || !this.mainContainer) return;
653+
const scale = this.transform.scale || 1;
654+
const canvasCenterX = this.app.screen.width / 2;
655+
const canvasCenterY = this.app.screen.height / 2;
656+
this.mainContainer.x = canvasCenterX - worldX * scale;
657+
this.mainContainer.y = canvasCenterY - worldY * scale;
658+
this.transform.panX = -this.mainContainer.x / scale; // 仅做记录,无强一致要求
659+
this.transform.panY = -this.mainContainer.y / scale;
660+
this.drawing.updateTransform(this.transform);
661+
this.interaction.updateTransform(this.transform);
662+
try { window.dispatchEvent(new CustomEvent('renderer-viewport-changed')); } catch (err) {}
663+
}
664+
645665
/**
646666
* 重建所有层的基础三角化虚线(根据当前 scale 自适应 dash/gap)
647667
*/

src/css/buttons.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
font-weight: 600;
1010
cursor: pointer;
1111
transition: all 0.2s ease;
12-
margin-top: var(--space-lg);
1312
box-sizing: border-box;
1413
}
1514

src/css/canvas.css

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,33 @@
1111
align-items: center;
1212
}
1313

14+
.navigator-controls {
15+
position: absolute;
16+
top: 6px;
17+
right: 6px;
18+
z-index: 2;
19+
}
20+
21+
#nav-mode-select {
22+
appearance: none;
23+
-webkit-appearance: none;
24+
font-size: 11px;
25+
padding: 4px 20px 4px 8px;
26+
background: rgba(255, 255, 255, 0.85);
27+
border: 1px solid var(--border-light);
28+
border-radius: var(--radius-full);
29+
color: var(--text-secondary);
30+
box-shadow: var(--shadow-sm);
31+
cursor: pointer;
32+
}
33+
34+
#nav-mode-select:hover {
35+
background: rgba(255, 255, 255, 0.95);
36+
border-color: var(--primary-color);
37+
}
38+
39+
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
40+
1441
.visualization-header .header-content h2 {
1542
margin: 0 0 var(--space-xs) 0;
1643
font-size: 20px;
@@ -150,7 +177,7 @@
150177
/* 画布提示信息(底部中央) */
151178
.canvas-hint {
152179
position: absolute;
153-
bottom: var(--space-lg);
180+
top: var(--space-lg);
154181
left: 50%;
155182
transform: translateX(-50%);
156183
z-index: 10;
@@ -259,7 +286,7 @@
259286
background: var(--bg-input);
260287
}
261288

262-
.layer-item input[type="checkbox"] {
289+
.layer-item input[type='checkbox'] {
263290
width: 16px;
264291
height: 16px;
265292
cursor: pointer;

src/css/index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
@import "./progress.css";
1010
@import "./canvas.css";
1111
@import "./legend.css";
12+
@import "./interpretation-panel.css";
1213
@import "./utilities.css";
1314

1415
/* 暗色主题已禁用,使用默认亮色主题 */

src/css/interpretation-panel.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,21 @@
5151
letter-spacing: 0.5px;
5252
}
5353

54+
/* 修复:在带操作区的头部行中,取消 h3 外边距以便 align-items:center 生效 */
55+
.card-header-row h3 {
56+
margin: 0; /* 覆盖上面通用的 bottom margin */
57+
}
58+
5459
/* 图例网格布局 */
5560
.legend-grid {
5661
display: grid;
5762
grid-template-columns: 1fr;
5863
gap: var(--space-md);
5964
}
65+
.legend-item-new-wrap {
66+
display: flex;
67+
gap: var(--space-md);
68+
}
6069

6170
.legend-item-new {
6271
display: flex;

src/css/legend.css

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@
5959
} */
6060

6161
/* 图例容器 */
62-
.legend { /* 旧样式保留 */
62+
.legend {
63+
/* 旧样式保留 */
6364
margin-top: var(--space-md);
6465
display: flex;
6566
gap: var(--space-sm);
@@ -103,7 +104,7 @@
103104
display: inline-block;
104105
}
105106
.legend-item-new.off .legend-eye::before,
106-
.legend-eye[aria-pressed="false"]::before {
107+
.legend-eye[aria-pressed='false']::before {
107108
content: '🙈';
108109
}
109110

@@ -258,14 +259,22 @@
258259
align-items: center;
259260
justify-content: space-between;
260261
gap: var(--space-md);
262+
padding-bottom: var(--space-sm);
263+
}
264+
.card-actions {
265+
display: inline-flex;
266+
gap: var(--space-sm);
261267
}
262-
.card-actions { display: inline-flex; gap: var(--space-sm); }
263268
.btn-compact {
264269
padding: 4px 8px;
265270
font-size: 12px;
266271
border-radius: var(--radius-sm);
267272
}
268-
.btn-icon { width: 28px; height: 28px; padding: 0; }
273+
.btn-icon {
274+
width: 28px;
275+
height: 28px;
276+
padding: 0;
277+
}
269278

270279
/* 折叠状态 */
271280
#path-card.collapsed #path-stats,
@@ -275,10 +284,17 @@
275284

276285
/* 性能状态点 */
277286
.status-dot {
278-
width: 10px; height: 10px;
287+
width: 10px;
288+
height: 10px;
279289
border-radius: 50%;
280290
display: inline-block;
281291
}
282-
.status-dot.ok { background: var(--success-color); }
283-
.status-dot.warn { background: var(--warning-color); }
284-
.status-dot.err { background: var(--error-color); }
292+
.status-dot.ok {
293+
background: var(--success-color);
294+
}
295+
.status-dot.warn {
296+
background: var(--warning-color);
297+
}
298+
.status-dot.err {
299+
background: var(--error-color);
300+
}

0 commit comments

Comments
 (0)