gis-view-webgl2 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/gis-view.css +1 -1
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.ts +250 -4
  4. package/dist/index.js +1 -1
  5. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +63 -11
  6. package/docs/GisView.md +6 -6
  7. package/docs/ai/06-/344/274/232/350/257/235/347/264/242/345/274/225/344/270/216/345/244/215/346/237/245/346/226/271/346/263/225.md +69 -11
  8. package/docs/demos/api-index.js +6 -2
  9. package/docs/demos/gis-view-demo/demo.css +56 -0
  10. package/docs/demos/gis-view-demo/panels/StylePanels.vue +22 -1
  11. package/docs/demos/gis-view-demo/panels/ViewPanels.vue +2 -1
  12. package/docs/demos/gis-view-demo/useGisViewDemo.js +168 -4
  13. package/docs/index.html +2 -2
  14. package/docs/internals/15-03-/351/200/220/350/275/256/350/256/260/345/275/225-/346/200/247/350/203/275/344/270/216/344/277/256/345/244/215-/344/270/213.md +72 -1
  15. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +58 -4
  16. package/docs/manual/07-/346/214/207/351/222/210/344/270/216/350/246/201/347/264/240/347/274/226/350/276/221.md +1 -1
  17. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +9 -4
  18. package/docs/manual/11-/346/240/267/345/274/217/344/270/216/350/247/204/345/210/231.md +2 -0
  19. package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +219 -1
  20. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +3 -1
  21. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +2 -2
  22. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +2 -1
  23. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +3 -1
  24. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +8 -0
  25. package/docs/steps/selftest-demos.html +3 -3
  26. package/docs/steps/selftest-docs.html +4 -4
  27. package/docs/steps/selftest-features.html +736 -0
  28. package/docs/steps/selftest-perf.html +296 -25
  29. package/docs/steps/selftest-select.html +182 -1
  30. package/package.json +2 -2
@@ -3589,6 +3589,7 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
3589
3589
  cmSend(casView, 'pointerup', screen, { buttons: 0, button: 0, ...extra })
3590
3590
  await settle(60)
3591
3591
  }
3592
+ const casWait = ms => new Promise(resolve => setTimeout(resolve, ms))
3592
3593
  await casClick(casView.project(0, 0))
3593
3594
  const casPointInfo = casView.getCascadeHighlight()
3594
3595
  check('级联高亮:点节点亮自身与直连线路 / 另一端节点(默认 both)',
@@ -3721,6 +3722,39 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
3721
3722
  casView.selectInBox({ x: casBoxD[0] - 30, y: casBoxD[1] - 30, width: 60, height: 60 }, { selectionKinds: ['point'] })
3722
3723
  check('triggerOnBoxSelect: false(缺省):框选只改选中集并收掉旧级联', casView.getCascadeHighlight() === null, JSON.stringify(casView.getCascadeHighlight()))
3723
3724
 
3725
+ // 取消选中时级联一起收掉:toggleSelect 的取消只发一条 null,接口取消也走同一口径
3726
+ casView.updateOptions({ toggleSelect: true, cascadeHighlight: { scope: 'both', triggerOnClick: true, triggerOnMultiSelect: true } })
3727
+ casView.selectFeatures(null)
3728
+ await settle(120)
3729
+ await casClick(casView.project(0, 0))
3730
+ await casWait(360)
3731
+ casEvents.length = 0
3732
+ casBusEvents.length = 0
3733
+ await casClick(casView.project(0, 0))
3734
+ check('toggleSelect 取消选中:级联同步收掉,且这一击只发一条 null',
3735
+ casView.getSelectionData().count === 0 && casView.getCascadeHighlight() === null && casEvents.length === 1 && casEvents[0] === null,
3736
+ JSON.stringify({ selected: casView.getSelectionData().count, cascade: casView.getCascadeHighlight(), events: casEvents }))
3737
+
3738
+ await casWait(360)
3739
+ await casClick(casView.project(0, 0))
3740
+ casEvents.length = 0
3741
+ casBusEvents.length = 0
3742
+ casView.deselect('cas-a')
3743
+ await settle(60)
3744
+ check('deselect 取消级联来源:级联同步收掉并只发一条 null',
3745
+ casView.getSelectionData().count === 0 && casView.getCascadeHighlight() === null && casEvents.length === 1 && casEvents[0] === null,
3746
+ JSON.stringify({ selected: casView.getSelectionData().count, cascade: casView.getCascadeHighlight(), events: casEvents }))
3747
+
3748
+ await casWait(360)
3749
+ await casClick(casView.project(0, 0))
3750
+ casEvents.length = 0
3751
+ casBusEvents.length = 0
3752
+ casView.selectFeatures('cas-a', { mode: 'remove' })
3753
+ await settle(60)
3754
+ check('selectFeatures remove 取消级联来源:级联同步收掉并只发一条 null',
3755
+ casView.getSelectionData().count === 0 && casView.getCascadeHighlight() === null && casEvents.length === 1 && casEvents[0] === null,
3756
+ JSON.stringify({ selected: casView.getSelectionData().count, cascade: casView.getCascadeHighlight(), events: casEvents }))
3757
+
3724
3758
  casEvents.length = 0
3725
3759
  casView.updateOptions({ cascadeHighlight: false })
3726
3760
  await settle(60)
@@ -6827,6 +6861,708 @@ check('beginAddChannel({ lineData }):草稿按第二段对齐条数(circuits
6827
6861
  interactionPriorityView.destroy()
6828
6862
  interactionPriorityHost.remove()
6829
6863
 
6864
+ /* ---------------- visualMap 图例:连续尺 / 分段色块 / 懒挂载 / 数据值域 ---------------- */
6865
+ const visualMapHost = document.createElement('div')
6866
+ visualMapHost.style.cssText = 'width:520px;height:300px'
6867
+ document.body.append(visualMapHost)
6868
+ const visualMapView = createGisView(visualMapHost, {
6869
+ points: [
6870
+ { id: 'vm-a', x: 0, y: 0, name: '甲', value: 0 },
6871
+ { id: 'vm-b', x: 60, y: 20, name: '乙', value: 10 },
6872
+ { id: 'vm-c', x: 120, y: 0, name: '丙', value: 20 }
6873
+ ],
6874
+ heatmap: { target: 'point', field: 'value', density: false, glow: false, opacity: 1 },
6875
+ visualMap: { target: 'point', title: '负荷', unit: 'kW', tickCount: 3 },
6876
+ label: { content: '{name}' },
6877
+ showGrid: false
6878
+ })
6879
+ await settle(80)
6880
+ const visualMapElement = visualMapView.getVisualMapElement()
6881
+ const visualMapReadout = visualMapView.getVisualMap()
6882
+ check('visualMap:默认跟随 heatmap 的字段与自动值域', visualMapReadout?.field === 'value' && visualMapReadout.domain[0] === 0 && visualMapReadout.domain[1] === 20 && visualMapReadout.type === 'ruler', JSON.stringify(visualMapReadout))
6883
+ check('visualMap:连续尺有渐变、刻度与单位', Boolean(visualMapElement && visualMapHost.contains(visualMapElement)) && visualMapElement.querySelectorAll('.gis-view__visual-map-tick').length === 3 && visualMapElement.querySelector('.gis-view__visual-map-ruler')?.style.background.includes('gradient') && visualMapReadout.ticks[0].label.includes('kW'), visualMapReadout?.ticks?.map(tick => tick.label).join(' / '))
6884
+ const visualMapLabelLayer = visualMapHost.querySelector('.gis-view__labels')
6885
+ let visualMapLabelMutations = 0
6886
+ const visualMapLabelObserver = new MutationObserver(records => { visualMapLabelMutations += records.length })
6887
+ if (visualMapLabelLayer) {
6888
+ visualMapLabelObserver.observe(visualMapLabelLayer, { subtree: true, childList: true, attributes: true, characterData: true })
6889
+ }
6890
+ let visualMapRootMutations = 0
6891
+ const visualMapRootObserver = new MutationObserver(records => { visualMapRootMutations += records.length })
6892
+ visualMapRootObserver.observe(visualMapElement, { subtree: true, childList: true, attributes: true, characterData: true })
6893
+ const visualMapSameConfig = { target: 'point', title: '负荷', unit: 'kW', tickCount: 3 }
6894
+ visualMapView.updateOptions({ visualMap: visualMapSameConfig })
6895
+ visualMapView.updateOptions({ visualMap: { ...visualMapSameConfig } })
6896
+ await settle(80)
6897
+ visualMapRootObserver.disconnect()
6898
+ visualMapLabelObserver.disconnect()
6899
+ check('visualMap:同签名重复更新零 DOM 写入', visualMapRootMutations === 0, `${visualMapRootMutations} 条 mutation`)
6900
+ check('visualMap:只改图例不重排标签层', visualMapLabelMutations === 0 && Boolean(visualMapLabelLayer), `${visualMapLabelMutations} 条 mutation`)
6901
+ visualMapView.updateData([
6902
+ { id: 'vm-a', x: 0, y: 0, name: '甲', value: 1000 },
6903
+ { id: 'vm-b', x: 60, y: 20, name: '乙', value: 1500 },
6904
+ { id: 'vm-c', x: 120, y: 0, name: '丙', value: 2000 }
6905
+ ], [])
6906
+ await settle(80)
6907
+ const visualMapUpdated = visualMapView.getVisualMap()
6908
+ check('visualMap:数据变化后自动值域在图例层内更新', visualMapUpdated?.domain[0] === 1000 && visualMapUpdated.domain[1] === 2000, JSON.stringify(visualMapUpdated?.domain))
6909
+ visualMapView.updateOptions({ heatmap: { target: 'point', field: 'value', density: false, glow: false, colors: ['#111827', '#f8fafc'], stops: [0, 1], opacity: 1 } })
6910
+ await settle(80)
6911
+ const visualMapInheritedGradient = visualMapElement.querySelector('.gis-view__visual-map-ruler')?.style.background
6912
+ visualMapView.updateOptions({ heatmap: { target: 'point', field: 'value', density: false, glow: false, colors: ['#111827', '#f8fafc'], stops: [0.35, 1], opacity: 0.3 } })
6913
+ await settle(80)
6914
+ const visualMapChangedGradient = visualMapElement.querySelector('.gis-view__visual-map-ruler')?.style.background
6915
+ check('visualMap:heatmap 的 stops / opacity 变化也会重画继承色带', Boolean(visualMapInheritedGradient) && visualMapChangedGradient !== visualMapInheritedGradient, `${visualMapInheritedGradient} -> ${visualMapChangedGradient}`)
6916
+ visualMapView.updateOptions({ visualMap: { type: 'piecewise', title: '负荷分档', unit: 'kW', pieces: [{ lt: 1200, label: '低' }, { gte: 1200, color: '#dd4b2e', label: '高' }] } })
6917
+ await settle(80)
6918
+ const piecewiseReadout = visualMapView.getVisualMap()
6919
+ check('visualMap:piecewise 支持 ECharts 的 lt / gte 与自定义色块', piecewiseReadout?.type === 'piecewise' && piecewiseReadout.pieces.length === 2 && piecewiseReadout.pieces[0].label === '低' && piecewiseReadout.pieces[1].color === '#dd4b2e' && visualMapHost.querySelectorAll('.gis-view__visual-map-piece').length === 2, JSON.stringify(piecewiseReadout?.pieces))
6920
+ visualMapView.updateOptions({ visualMap: null })
6921
+ await settle(80)
6922
+ check('visualMap:关掉后不保留任何图例 DOM', visualMapView.getVisualMapElement() === null && visualMapView.getVisualMap() === null && !visualMapHost.querySelector('.gis-view__visual-map'))
6923
+ visualMapView.destroy()
6924
+ visualMapHost.remove()
6925
+
6926
+ const visualMapExternal = document.createElement('div')
6927
+ visualMapExternal.style.cssText = 'width:260px;height:120px'
6928
+ document.body.append(visualMapExternal)
6929
+ const visualMapStandaloneHost = document.createElement('div')
6930
+ visualMapStandaloneHost.style.cssText = 'width:480px;height:260px'
6931
+ document.body.append(visualMapStandaloneHost)
6932
+ const visualMapStandalone = createGisView(visualMapStandaloneHost, {
6933
+ points: [{ id: 'vm-x', x: 0, y: 0, score: 12 }, { id: 'vm-y', x: 30, y: 0, score: 48 }],
6934
+ visualMap: {
6935
+ field: 'score',
6936
+ type: 'continuous',
6937
+ orient: 'horizontal',
6938
+ position: 'top-right',
6939
+ container: visualMapExternal,
6940
+ colors: ['#2f6fd0', '#dd4b2e'],
6941
+ formatter: '{value}{unit}',
6942
+ unit: '分',
6943
+ title: '评分'
6944
+ },
6945
+ showGrid: false
6946
+ })
6947
+ await settle(80)
6948
+ const standaloneReadout = visualMapStandalone.getVisualMap()
6949
+ check('visualMap:没有 heatmap 时也能按字段自动算独立图例', standaloneReadout?.domain[0] === 12 && standaloneReadout.domain[1] === 48 && standaloneReadout.unit === '分', JSON.stringify(standaloneReadout?.domain))
6950
+ check('visualMap:container / continuous / horizontal / formatter 生效', visualMapStandalone.getVisualMapElement()?.parentElement === visualMapExternal && standaloneReadout.type === 'ruler' && standaloneReadout.orient === 'horizontal' && standaloneReadout.ticks[0].label === '12分', JSON.stringify({ parent: visualMapStandalone.getVisualMapElement()?.parentElement?.className, labels: standaloneReadout?.ticks?.map(tick => tick.label) }))
6951
+ visualMapStandalone.updateOptions({ visualMap: { enabled: false } })
6952
+ await settle(80)
6953
+ check('visualMap:enabled: false 等同不配,外部容器也不留节点', visualMapStandalone.getVisualMapElement() === null && visualMapExternal.childElementCount === 0)
6954
+ visualMapStandalone.destroy()
6955
+ visualMapStandaloneHost.remove()
6956
+ visualMapExternal.remove()
6957
+
6958
+ /* 值域两端都已确定时不应再为了补另一端扫数据;用 getter 直接统计字段读取次数 */
6959
+ const visualMapFixedHost = document.createElement('div')
6960
+ visualMapFixedHost.style.cssText = 'width:420px;height:240px'
6961
+ document.body.append(visualMapFixedHost)
6962
+ let visualMapFixedReads = 0
6963
+ const visualMapFixedPoints = [{ id: 'vm-fixed-a', x: 0, y: 0 }, { id: 'vm-fixed-b', x: 40, y: 0 }]
6964
+ for (const point of visualMapFixedPoints) {
6965
+ Object.defineProperty(point, 'metric', {
6966
+ enumerable: true,
6967
+ get: () => {
6968
+ visualMapFixedReads += 1
6969
+ return 25
6970
+ }
6971
+ })
6972
+ }
6973
+ const visualMapFixed = createGisView(visualMapFixedHost, {
6974
+ points: visualMapFixedPoints,
6975
+ visualMap: { field: 'metric', domain: [0, 100] },
6976
+ showGrid: false
6977
+ })
6978
+ await settle(80)
6979
+ const visualMapFixedDomain = visualMapFixed.getVisualMap()?.domain
6980
+ check('visualMap:显式 domain 不读数据字段', visualMapFixedReads === 0 && visualMapFixedDomain?.[0] === 0 && visualMapFixedDomain?.[1] === 100, `${visualMapFixedReads} 次字段读取 · ${JSON.stringify(visualMapFixedDomain)}`)
6981
+ visualMapFixed.updateOptions({ visualMap: { field: 'metric', min: 0, max: 100 } })
6982
+ await settle(80)
6983
+ const visualMapMinMaxDomain = visualMapFixed.getVisualMap()?.domain
6984
+ check('visualMap:min / max 两端都给定时也不扫数据', visualMapFixedReads === 0 && visualMapMinMaxDomain?.[0] === 0 && visualMapMinMaxDomain?.[1] === 100, `${visualMapFixedReads} 次字段读取 · ${JSON.stringify(visualMapMinMaxDomain)}`)
6985
+ visualMapFixed.destroy()
6986
+ visualMapFixedHost.remove()
6987
+
6988
+ const visualMapReplaceHost = document.createElement('div')
6989
+ visualMapReplaceHost.style.cssText = 'width:420px;height:240px'
6990
+ document.body.append(visualMapReplaceHost)
6991
+ const visualMapReplaceConfig = { target: 'point', title: '替换后图例' }
6992
+ const visualMapReplace = createGisView(visualMapReplaceHost, {
6993
+ points: [{ id: 'vm-r', x: 0, y: 0, value: 5, load: 90 }],
6994
+ heatmap: { target: 'point', field: 'load', density: false, glow: false },
6995
+ visualMap: visualMapReplaceConfig,
6996
+ showGrid: false
6997
+ })
6998
+ await settle(80)
6999
+ const visualMapBeforeReplace = visualMapReplace.getVisualMap()
7000
+ visualMapReplace.updateOptions({
7001
+ points: [{ id: 'vm-r', x: 0, y: 0, value: 5, load: 90 }],
7002
+ visualMap: visualMapReplaceConfig,
7003
+ showGrid: false
7004
+ }, true)
7005
+ await settle(80)
7006
+ const visualMapAfterReplace = visualMapReplace.getVisualMap()
7007
+ check('visualMap:整份替换去掉 heatmap 后,图例立即改读独立字段 / 值域', visualMapBeforeReplace?.field === 'load' && visualMapAfterReplace?.field === 'value' && visualMapAfterReplace.domain[0] === 5 && visualMapAfterReplace.domain[1] === 5, JSON.stringify({ before: visualMapBeforeReplace, after: visualMapAfterReplace }))
7008
+ visualMapReplace.destroy()
7009
+ visualMapReplaceHost.remove()
7010
+
7011
+ /* visualMap:分段点选 / 键盘 / 合帧回调 / 受控选择 */
7012
+ const visualMapInteractionHost = document.createElement('div')
7013
+ visualMapInteractionHost.style.cssText = 'width:520px;height:300px'
7014
+ document.body.append(visualMapInteractionHost)
7015
+ let visualMapChangeCount = 0
7016
+ const visualMapChanges = []
7017
+ const visualMapInteractionConfig = {
7018
+ target: 'point',
7019
+ type: 'piecewise',
7020
+ interactive: true,
7021
+ selectedMode: 'multiple',
7022
+ title: '交互分档',
7023
+ pieces: [{ lt: 5, label: '低' }, { gte: 5, lt: 15, label: '中' }, { gte: 15, label: '高' }],
7024
+ onChange: (payload) => {
7025
+ visualMapChangeCount += 1
7026
+ visualMapChanges.push(payload)
7027
+ }
7028
+ }
7029
+ const visualMapInteraction = createGisView(visualMapInteractionHost, {
7030
+ points: [{ id: 'vmi-a', x: 0, y: 0, value: 1 }, { id: 'vmi-b', x: 40, y: 0, value: 10 }, { id: 'vmi-c', x: 80, y: 0, value: 20 }],
7031
+ visualMap: visualMapInteractionConfig,
7032
+ showGrid: false
7033
+ })
7034
+ await settle(80)
7035
+ const visualMapInteractionElement = visualMapInteraction.getVisualMapElement()
7036
+ const visualMapInteractionRows = [...visualMapInteractionElement.querySelectorAll('.gis-view__visual-map-piece')]
7037
+ const visualMapInitialReadout = visualMapInteraction.getVisualMap()
7038
+ check('visualMap:显式开启交互后分段可点、初始全选', visualMapInteractionElement.classList.contains('gis-view__visual-map--interactive') && getComputedStyle(visualMapInteractionElement).pointerEvents === 'auto' && visualMapInteractionRows.length === 3 && visualMapInteractionRows.every(row => row.getAttribute('role') === 'button' && row.tabIndex === 0 && row.getAttribute('aria-pressed') === 'true') && visualMapInitialReadout.selected.length === 3, JSON.stringify({ pointerEvents: getComputedStyle(visualMapInteractionElement).pointerEvents, selected: visualMapInitialReadout?.selected, roles: visualMapInteractionRows.map(row => row.getAttribute('role')) }))
7039
+ const visualMapRow = index => visualMapInteractionElement.querySelector(`.gis-view__visual-map-piece[data-index="${index}"]`)
7040
+ visualMapRow(0).dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
7041
+ await settle(40)
7042
+ const visualMapAfterClick = visualMapInteraction.getVisualMap()
7043
+ check('visualMap:点击分段切换选中态并回写读数 / onChange', visualMapAfterClick.selected.join(',') === '1,2' && visualMapRow(0).classList.contains('is-unselected') && visualMapRow(0).getAttribute('aria-pressed') === 'false' && visualMapChangeCount === 1 && visualMapChanges[0].source === 'user' && visualMapChanges[0].reason === 'piece', JSON.stringify({ selected: visualMapAfterClick?.selected, count: visualMapChangeCount, payload: visualMapChanges[0] }))
7044
+ visualMapRow(1).dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }))
7045
+ await settle(40)
7046
+ const visualMapAfterKeyboard = visualMapInteraction.getVisualMap()
7047
+ check('visualMap:分段支持 Enter / 空格键盘操作', visualMapAfterKeyboard.selected.join(',') === '2' && visualMapChangeCount === 2, JSON.stringify({ selected: visualMapAfterKeyboard?.selected, count: visualMapChangeCount }))
7048
+ const visualMapBatchStart = visualMapChangeCount
7049
+ visualMapRow(1).dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
7050
+ visualMapRow(2).dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
7051
+ visualMapRow(1).dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
7052
+ await settle(40)
7053
+ const visualMapAfterBatch = visualMapInteraction.getVisualMap()
7054
+ check('visualMap:同一帧多次点击合并成一次 onChange', visualMapAfterBatch.selected.length === 0 && visualMapChangeCount === visualMapBatchStart + 1, JSON.stringify({ selected: visualMapAfterBatch?.selected, count: visualMapChangeCount - visualMapBatchStart }))
7055
+ visualMapInteraction.updateOptions({ visualMap: { ...visualMapInteractionConfig, selected: [0] } })
7056
+ await settle(60)
7057
+ const visualMapControlled = visualMapInteraction.getVisualMap()
7058
+ check('visualMap:受控 selected 更新会重绘且不回声 onChange', visualMapControlled.selected.join(',') === '0' && visualMapRow(0).getAttribute('aria-pressed') === 'true' && visualMapRow(1).getAttribute('aria-pressed') === 'false' && visualMapChangeCount === visualMapBatchStart + 1, JSON.stringify({ selected: visualMapControlled?.selected, count: visualMapChangeCount }))
7059
+ visualMapInteraction.updateOptions({ visualMap: { ...visualMapInteractionConfig, selectedMode: 'single', selected: [0] } })
7060
+ await settle(60)
7061
+ visualMapRow(1).dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
7062
+ await settle(40)
7063
+ const visualMapSingle = visualMapInteraction.getVisualMap()
7064
+ const visualMapSingleCount = visualMapChangeCount
7065
+ visualMapRow(1).dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
7066
+ await settle(40)
7067
+ const visualMapSingleCleared = visualMapInteraction.getVisualMap()
7068
+ check('visualMap:selectedMode single 只保留一段,再点可清空', visualMapSingle.selected.join(',') === '1' && visualMapSingleCleared.selected.length === 0 && visualMapChangeCount === visualMapSingleCount + 1, JSON.stringify({ single: visualMapSingle?.selected, cleared: visualMapSingleCleared?.selected }))
7069
+ visualMapInteraction.destroy()
7070
+ visualMapInteractionHost.remove()
7071
+
7072
+ /* visualMap:连续尺区间手柄 / 拖动 / 受控 range */
7073
+ const visualMapRangeHost = document.createElement('div')
7074
+ visualMapRangeHost.style.cssText = 'width:520px;height:300px'
7075
+ document.body.append(visualMapRangeHost)
7076
+ let visualMapRangeChanges = 0
7077
+ const visualMapRangePayloads = []
7078
+ const visualMapRangeConfig = {
7079
+ target: 'point',
7080
+ type: 'ruler',
7081
+ interactive: true,
7082
+ orient: 'horizontal',
7083
+ title: '区间过滤',
7084
+ unit: 'kW',
7085
+ onChange: (payload) => {
7086
+ visualMapRangeChanges += 1
7087
+ visualMapRangePayloads.push(payload)
7088
+ }
7089
+ }
7090
+ const visualMapRangeView = createGisView(visualMapRangeHost, {
7091
+ points: [{ id: 'vmr-a', x: 0, y: 0, value: 0 }, { id: 'vmr-b', x: 40, y: 0, value: 50 }, { id: 'vmr-c', x: 80, y: 0, value: 100 }],
7092
+ visualMap: visualMapRangeConfig,
7093
+ showGrid: false
7094
+ })
7095
+ await settle(80)
7096
+ const visualMapRangeElement = visualMapRangeView.getVisualMapElement()
7097
+ const visualMapRangeTrack = visualMapRangeElement.querySelector('.gis-view__visual-map-ruler')
7098
+ const visualMapRangeHandles = [...visualMapRangeElement.querySelectorAll('.gis-view__visual-map-range-handle')]
7099
+ const visualMapRangeInitial = visualMapRangeView.getVisualMap()
7100
+ check('visualMap:interactive 连续尺默认 calculable 并显示两端手柄', visualMapRangeInitial.calculable === true && visualMapRangeInitial.range[0] === 0 && visualMapRangeInitial.range[1] === 100 && visualMapRangeInitial.rangeRatios.join(',') === '0,1' && visualMapRangeHandles.length === 2 && visualMapRangeHandles.every(handle => handle.getAttribute('role') === 'slider'), JSON.stringify({ range: visualMapRangeInitial?.range, ratios: visualMapRangeInitial?.rangeRatios }))
7101
+ visualMapRangeHandles[1].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true, cancelable: true }))
7102
+ await settle(40)
7103
+ const visualMapRangeKeyboard = visualMapRangeView.getVisualMap()
7104
+ check('visualMap:区间手柄支持键盘步进并触发 range 回调', Math.abs(visualMapRangeKeyboard.range[0]) < 1e-6 && Math.abs(visualMapRangeKeyboard.range[1] - 98) < 1e-6 && visualMapRangeChanges === 1 && visualMapRangePayloads[0].reason === 'range', JSON.stringify({ range: visualMapRangeKeyboard?.range, count: visualMapRangeChanges }))
7105
+ const visualMapRangeRect = visualMapRangeTrack.getBoundingClientRect()
7106
+ const visualMapPointer = (type, ratio, buttons) => visualMapRangeHandles[1].dispatchEvent(new PointerEvent(type, {
7107
+ bubbles: true,
7108
+ cancelable: true,
7109
+ pointerId: 71,
7110
+ pointerType: 'mouse',
7111
+ button: 0,
7112
+ buttons,
7113
+ clientX: visualMapRangeRect.left + visualMapRangeRect.width * ratio,
7114
+ clientY: visualMapRangeRect.top + visualMapRangeRect.height / 2
7115
+ }))
7116
+ const visualMapDragStart = visualMapRangeChanges
7117
+ visualMapPointer('pointerdown', 0.98, 1)
7118
+ visualMapPointer('pointermove', 0.8, 1)
7119
+ visualMapPointer('pointermove', 0.65, 1)
7120
+ visualMapPointer('pointermove', 0.5, 1)
7121
+ visualMapPointer('pointerup', 0.5, 0)
7122
+ await settle(50)
7123
+ const visualMapRangeDragged = visualMapRangeView.getVisualMap()
7124
+ check('visualMap:拖动区间手柄更新 range,连续事件只回调一次', Math.abs(visualMapRangeDragged.range[0]) < 1e-6 && Math.abs(visualMapRangeDragged.range[1] - 50) < 1e-6 && visualMapRangeChanges === visualMapDragStart + 1, JSON.stringify({ range: visualMapRangeDragged?.range, callbacks: visualMapRangeChanges - visualMapDragStart }))
7125
+ visualMapRangeView.updateOptions({ visualMap: { ...visualMapRangeConfig, range: [25, 75] } })
7126
+ await settle(60)
7127
+ const visualMapRangeControlled = visualMapRangeView.getVisualMap()
7128
+ const visualMapRangeWindow = visualMapRangeElement.querySelector('.gis-view__visual-map-range')
7129
+ check('visualMap:受控 range 更新手柄位置且不回声 onChange', visualMapRangeControlled.range.join(',') === '25,75' && Math.abs(visualMapRangeControlled.rangeRatios[0] - 0.25) < 1e-6 && Math.abs(visualMapRangeControlled.rangeRatios[1] - 0.75) < 1e-6 && visualMapRangeWindow.style.left === '25%' && visualMapRangeWindow.style.width === '50%' && visualMapRangeChanges === visualMapDragStart + 1, JSON.stringify({ range: visualMapRangeControlled?.range, ratios: visualMapRangeControlled?.rangeRatios, style: [visualMapRangeWindow.style.left, visualMapRangeWindow.style.width] }))
7130
+ const visualMapRangeCopy = visualMapRangeView.getVisualMap()
7131
+ visualMapRangeCopy.range[0] = -999
7132
+ visualMapRangeCopy.rangeRatios[1] = 9
7133
+ const visualMapRangeFresh = visualMapRangeView.getVisualMap()
7134
+ check('visualMap:读数里的 range / rangeRatios 是独立副本', visualMapRangeFresh.range[0] === 25 && visualMapRangeFresh.rangeRatios[1] === 0.75, JSON.stringify({ range: visualMapRangeFresh.range, ratios: visualMapRangeFresh.rangeRatios }))
7135
+ visualMapRangeView.destroy()
7136
+ visualMapRangeHost.remove()
7137
+
7138
+ /* visualMap:选区真正进入热力上色(分段 / 连续区间都门控,而不只是回显读数) */
7139
+ const visualMapGateHost = document.createElement('div')
7140
+ visualMapGateHost.style.cssText = 'width:520px;height:300px'
7141
+ document.body.append(visualMapGateHost)
7142
+ const visualMapGateHeatmap = {
7143
+ enabled: true,
7144
+ target: 'point',
7145
+ field: 'value',
7146
+ domain: [0, 20],
7147
+ colors: ['#ff0000', '#ff0000'],
7148
+ opacity: 1,
7149
+ density: false,
7150
+ glow: false
7151
+ }
7152
+ const visualMapGateConfig = {
7153
+ target: 'point',
7154
+ type: 'piecewise',
7155
+ interactive: true,
7156
+ selectedMode: 'multiple',
7157
+ pieces: [{ lt: 5 }, { gte: 5, lt: 15 }, { gte: 15 }]
7158
+ }
7159
+ const visualMapGateView = createGisView(visualMapGateHost, {
7160
+ points: [{ id: 'vmg-a', x: 0, y: 0, value: 1 }, { id: 'vmg-b', x: 40, y: 0, value: 10 }, { id: 'vmg-c', x: 80, y: 0, value: 20 }],
7161
+ pointStyle: { color: '#ffffff', size: 12 },
7162
+ heatmap: visualMapGateHeatmap,
7163
+ visualMap: visualMapGateConfig,
7164
+ minimap: { width: 180, height: 120 },
7165
+ showGrid: false
7166
+ })
7167
+ await settle(100)
7168
+ const visualMapGateFills = () => visualMapGateView.snapshotScene().points.map(point => point.fill)
7169
+ const visualMapGateMiniCanvas = visualMapGateView.getMinimapElement()?.querySelector('.gis-view__minimap-canvas')
7170
+ const visualMapGateMiniRed = () => {
7171
+ const context = visualMapGateMiniCanvas?.getContext('2d')
7172
+ if (!context) {
7173
+ return -1
7174
+ }
7175
+ const data = context.getImageData(0, 0, visualMapGateMiniCanvas.width, visualMapGateMiniCanvas.height).data
7176
+ let count = 0
7177
+ for (let index = 0; index < data.length; index += 4) {
7178
+ if (data[index] > 180 && data[index + 1] < 100 && data[index + 2] < 100) {
7179
+ count += 1
7180
+ }
7181
+ }
7182
+ return count
7183
+ }
7184
+ const visualMapGateInitial = visualMapGateFills()
7185
+ const visualMapGateMiniInitial = visualMapGateMiniRed()
7186
+ visualMapGateView.getVisualMapElement().querySelector('.gis-view__visual-map-piece[data-index="0"]').dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
7187
+ await settle(80)
7188
+ const visualMapGatePiecewise = visualMapGateFills()
7189
+ visualMapGateView.updateOptions({ visualMap: { ...visualMapGateConfig, selected: [2] } })
7190
+ await settle(80)
7191
+ const visualMapGateControlled = visualMapGateFills()
7192
+ const visualMapGateMiniControlled = visualMapGateMiniRed()
7193
+ check('visualMap:取消分段后对应值退出热力上色,再选一段只保留该档',
7194
+ visualMapGateInitial.every(fill => fill === 'rgba(255,0,0,1)') &&
7195
+ visualMapGatePiecewise[0] === 'rgba(255,255,255,1)' && visualMapGatePiecewise[1] === 'rgba(255,0,0,1)' && visualMapGatePiecewise[2] === 'rgba(255,0,0,1)' &&
7196
+ visualMapGateControlled[0] === 'rgba(255,255,255,1)' && visualMapGateControlled[1] === 'rgba(255,255,255,1)' && visualMapGateControlled[2] === 'rgba(255,0,0,1)',
7197
+ JSON.stringify({ initial: visualMapGateInitial, piecewise: visualMapGatePiecewise, controlled: visualMapGateControlled }))
7198
+ check('visualMap:鹰眼图继承主图热力时也按同一选区门控',
7199
+ visualMapGateMiniInitial > 0 && visualMapGateMiniControlled > 0 && visualMapGateMiniControlled < visualMapGateMiniInitial * 0.8,
7200
+ `初始红色 ${visualMapGateMiniInitial} px · 选中高段后 ${visualMapGateMiniControlled} px`)
7201
+ const visualMapGateMiniGreen = () => {
7202
+ const context = visualMapGateMiniCanvas?.getContext('2d')
7203
+ if (!context) {
7204
+ return -1
7205
+ }
7206
+ const data = context.getImageData(0, 0, visualMapGateMiniCanvas.width, visualMapGateMiniCanvas.height).data
7207
+ let count = 0
7208
+ for (let index = 0; index < data.length; index += 4) {
7209
+ if (data[index] < 100 && data[index + 1] > 180 && data[index + 2] < 100) {
7210
+ count += 1
7211
+ }
7212
+ }
7213
+ return count
7214
+ }
7215
+ visualMapGateView.updateOptions({ minimap: { width: 180, height: 120, heatmap: { target: 'point', field: 'value', domain: [0, 20], colors: ['#00ff00', '#00ff00'], opacity: 1, density: false, glow: false } } })
7216
+ await settle(100)
7217
+ const visualMapGateMiniIndependent = visualMapGateMiniGreen()
7218
+ visualMapGateView.updateOptions({ visualMap: { ...visualMapGateConfig, selected: [0] } })
7219
+ await settle(100)
7220
+ const visualMapGateMiniIndependentAfter = visualMapGateMiniGreen()
7221
+ check('visualMap:显式 minimap.heatmap 仍按自己的配置取色,不被主图选区门控',
7222
+ visualMapGateMiniIndependent > 0 && visualMapGateMiniIndependentAfter === visualMapGateMiniIndependent,
7223
+ `绿色 ${visualMapGateMiniIndependent} px → ${visualMapGateMiniIndependentAfter} px`)
7224
+ visualMapGateView.destroy()
7225
+ visualMapGateHost.remove()
7226
+
7227
+ const visualMapDensityGateHost = document.createElement('div')
7228
+ visualMapDensityGateHost.style.cssText = 'width:520px;height:300px'
7229
+ document.body.append(visualMapDensityGateHost)
7230
+ const visualMapDensityGateConfig = { target: 'point', type: 'piecewise', interactive: true, selectedMode: 'multiple', pieces: [{ lt: 5 }, { gte: 5, lt: 15 }, { gte: 15 }], selected: [2] }
7231
+ const visualMapDensityGateView = createGisView(visualMapDensityGateHost, {
7232
+ points: [{ id: 'vmdg-a', x: 0, y: 0, value: 1 }, { id: 'vmdg-b', x: 40, y: 0, value: 10 }, { id: 'vmdg-c', x: 80, y: 0, value: 20 }],
7233
+ heatmap: { ...visualMapGateHeatmap, density: { resolution: 96, radiusRatio: 0.12, threshold: 0 } },
7234
+ visualMap: visualMapDensityGateConfig,
7235
+ showGrid: false
7236
+ })
7237
+ await settle(140)
7238
+ const visualMapDensityGated = visualMapDensityGateView.getStats().heatDensity
7239
+ visualMapDensityGateView.updateOptions({ visualMap: { ...visualMapDensityGateConfig, selected: [0, 1, 2] } })
7240
+ await settle(140)
7241
+ const visualMapDensityExpanded = visualMapDensityGateView.getStats().heatDensity
7242
+ check('visualMap:密度场也按选区重新采样,落选值不再贡献密度',
7243
+ visualMapDensityGated?.samples === 1 && visualMapDensityExpanded?.samples === 3,
7244
+ JSON.stringify({ gated: visualMapDensityGated, expanded: visualMapDensityExpanded }))
7245
+ visualMapDensityGateView.destroy()
7246
+ visualMapDensityGateHost.remove()
7247
+
7248
+ const visualMapRangeGateHost = document.createElement('div')
7249
+ visualMapRangeGateHost.style.cssText = 'width:520px;height:300px'
7250
+ document.body.append(visualMapRangeGateHost)
7251
+ const visualMapRangeGateConfig = { target: 'point', type: 'ruler', interactive: true, range: [25, 75] }
7252
+ const visualMapRangeGateView = createGisView(visualMapRangeGateHost, {
7253
+ points: [{ id: 'vmrg-a', x: 0, y: 0, value: 0 }, { id: 'vmrg-b', x: 40, y: 0, value: 50 }, { id: 'vmrg-c', x: 80, y: 0, value: 100 }],
7254
+ pointStyle: { color: '#ffffff', size: 12 },
7255
+ heatmap: { ...visualMapGateHeatmap, domain: [0, 100] },
7256
+ visualMap: visualMapRangeGateConfig,
7257
+ showGrid: false
7258
+ })
7259
+ await settle(100)
7260
+ const visualMapRangeGateFills = () => visualMapRangeGateView.snapshotScene().points.map(point => point.fill)
7261
+ const visualMapRangeGateInitial = visualMapRangeGateFills()
7262
+ visualMapRangeGateView.updateOptions({ visualMap: { ...visualMapRangeGateConfig, range: [0, 100] } })
7263
+ await settle(80)
7264
+ const visualMapRangeGateExpanded = visualMapRangeGateFills()
7265
+ check('visualMap:连续尺区间外不参与热力上色,拖动扩回整段后全部恢复',
7266
+ visualMapRangeGateInitial[0] === 'rgba(255,255,255,1)' && visualMapRangeGateInitial[1] === 'rgba(255,0,0,1)' && visualMapRangeGateInitial[2] === 'rgba(255,255,255,1)' &&
7267
+ visualMapRangeGateExpanded.every(fill => fill === 'rgba(255,0,0,1)'),
7268
+ JSON.stringify({ initial: visualMapRangeGateInitial, expanded: visualMapRangeGateExpanded }))
7269
+ visualMapRangeGateView.destroy()
7270
+ visualMapRangeGateHost.remove()
7271
+
7272
+ /* visualMap:render(context) 完全自定义 + 上下文交互 */
7273
+ const visualMapCustomHost = document.createElement('div')
7274
+ visualMapCustomHost.style.cssText = 'width:520px;height:300px'
7275
+ document.body.append(visualMapCustomHost)
7276
+ let visualMapCustomCalls = 0
7277
+ let visualMapCustomContext = null
7278
+ let visualMapCustomChanges = 0
7279
+ const visualMapCustomRender = (context) => {
7280
+ visualMapCustomCalls += 1
7281
+ visualMapCustomContext = context
7282
+ const wrapper = document.createElement('div')
7283
+ wrapper.className = 'vm-custom-content'
7284
+ const readout = document.createElement('span')
7285
+ readout.className = 'vm-custom-readout'
7286
+ readout.textContent = `${context.title}:${context.selected.join('/')}`
7287
+ const button = document.createElement('button')
7288
+ button.type = 'button'
7289
+ button.className = 'vm-custom-select-high'
7290
+ button.textContent = '只选高档'
7291
+ button.addEventListener('click', () => context.setSelection([2]))
7292
+ wrapper.append(readout, button)
7293
+ return wrapper
7294
+ }
7295
+ const visualMapCustomConfig = {
7296
+ target: 'point',
7297
+ type: 'piecewise',
7298
+ interactive: true,
7299
+ selectedMode: 'multiple',
7300
+ title: '自定义图例',
7301
+ pieces: [{ lt: 5, label: '低' }, { gte: 5, lt: 15, label: '中' }, { gte: 15, label: '高' }],
7302
+ render: visualMapCustomRender,
7303
+ onChange: () => { visualMapCustomChanges += 1 }
7304
+ }
7305
+ const visualMapCustomView = createGisView(visualMapCustomHost, {
7306
+ points: [{ id: 'vmc-a', x: 0, y: 0, value: 1 }, { id: 'vmc-b', x: 40, y: 0, value: 10 }, { id: 'vmc-c', x: 80, y: 0, value: 20 }],
7307
+ visualMap: visualMapCustomConfig,
7308
+ showGrid: false
7309
+ })
7310
+ await settle(80)
7311
+ const visualMapCustomElement = visualMapCustomView.getVisualMapElement()
7312
+ const visualMapCustomInitial = visualMapCustomView.getVisualMap()
7313
+ check('visualMap:自定义 render 返回 DOM、隐藏内置标题并提供上下文读数', visualMapCustomCalls === 1 && visualMapCustomElement.querySelector('.vm-custom-content') !== null && visualMapCustomElement.querySelector('.gis-view__visual-map-title').hidden === true && visualMapCustomContext.domain.join(',') === '1,20' && visualMapCustomContext.selected.join(',') === '0,1,2' && visualMapCustomInitial.selected.length === 3, JSON.stringify({ calls: visualMapCustomCalls, hidden: visualMapCustomElement.querySelector('.gis-view__visual-map-title')?.hidden, context: visualMapCustomContext && { domain: visualMapCustomContext.domain, selected: visualMapCustomContext.selected } }))
7314
+ visualMapCustomElement.querySelector('.vm-custom-select-high').dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
7315
+ await settle(50)
7316
+ const visualMapCustomSelected = visualMapCustomView.getVisualMap()
7317
+ check('visualMap:自定义上下文 setSelection 更新读数并回调 api 来源', visualMapCustomSelected.selected.join(',') === '2' && visualMapCustomChanges === 1 && visualMapCustomCalls === 1, JSON.stringify({ selected: visualMapCustomSelected?.selected, changes: visualMapCustomChanges, calls: visualMapCustomCalls }))
7318
+ visualMapCustomView.updateOptions({ visualMap: { ...visualMapCustomConfig, title: '自定义图例 2' } })
7319
+ await settle(60)
7320
+ check('visualMap:自定义 render 的配置签名变化会重绘,并保留用户选区', visualMapCustomCalls === 2 && visualMapCustomElement.querySelector('.vm-custom-readout')?.textContent === '自定义图例 2:2', JSON.stringify({ calls: visualMapCustomCalls, text: visualMapCustomElement.querySelector('.vm-custom-readout')?.textContent }))
7321
+ visualMapCustomView.updateOptions({ visualMap: { ...visualMapCustomConfig, title: '字符串图例', render: context => `<strong class="vm-custom-string">${context.domain[0]} ~ ${context.domain[1]}</strong>` } })
7322
+ await settle(60)
7323
+ check('visualMap:自定义 render 也可返回 HTML 字符串', visualMapCustomElement.querySelector('.vm-custom-string')?.textContent === '1 ~ 20', visualMapCustomElement.innerHTML)
7324
+ visualMapCustomView.destroy()
7325
+ visualMapCustomHost.remove()
7326
+
7327
+ /* visualMap:outOfRange 三种落选策略;network 只做视觉门控,不改数据 / 筛选 / 通道声明 */
7328
+ const visualMapOutOfRangeHost = document.createElement('div')
7329
+ visualMapOutOfRangeHost.style.cssText = 'width:520px;height:300px'
7330
+ document.body.append(visualMapOutOfRangeHost)
7331
+ const visualMapOutOfRangeChanges = []
7332
+ const visualMapOutOfRangeHeatmap = {
7333
+ enabled: true,
7334
+ target: 'point',
7335
+ field: 'value',
7336
+ domain: [0, 20],
7337
+ colors: ['#ff0000', '#ff0000'],
7338
+ opacity: 1,
7339
+ density: false,
7340
+ glow: false
7341
+ }
7342
+ const visualMapOutOfRangeConfig = {
7343
+ target: 'point',
7344
+ field: 'value',
7345
+ type: 'piecewise',
7346
+ interactive: true,
7347
+ selectedMode: 'multiple',
7348
+ pieces: [{ lt: 5 }, { gte: 5, lt: 15 }, { gte: 15 }],
7349
+ selected: [1],
7350
+ onChange: payload => visualMapOutOfRangeChanges.push(payload)
7351
+ }
7352
+ const visualMapOutOfRangeView = createGisView(visualMapOutOfRangeHost, {
7353
+ points: [
7354
+ { id: 'vmor-a', x: -60, y: 0, value: 1 },
7355
+ { id: 'vmor-b', x: 0, y: 0, value: 10 },
7356
+ { id: 'vmor-c', x: 60, y: 0, value: 20 }
7357
+ ],
7358
+ pointStyle: { color: '#ffffff', size: 14 },
7359
+ heatmap: visualMapOutOfRangeHeatmap,
7360
+ visualMap: visualMapOutOfRangeConfig,
7361
+ minimap: {
7362
+ width: 180,
7363
+ height: 120,
7364
+ heatmap: {
7365
+ target: 'point',
7366
+ field: 'value',
7367
+ domain: [0, 20],
7368
+ colors: ['#00ff00', '#00ff00'],
7369
+ opacity: 1,
7370
+ density: false,
7371
+ glow: false
7372
+ }
7373
+ },
7374
+ showGrid: false
7375
+ })
7376
+ await settle()
7377
+ const visualMapOutOfRangeScenePoints = () => {
7378
+ const scene = visualMapOutOfRangeView.snapshotScene()
7379
+ return [...scene.points, ...scene.raised]
7380
+ }
7381
+ const visualMapOutOfRangeCanvas = visualMapOutOfRangeView.getMinimapElement()?.querySelector('.gis-view__minimap-canvas')
7382
+ const visualMapOutOfRangeMiniGreen = () => {
7383
+ const context = visualMapOutOfRangeCanvas?.getContext('2d')
7384
+ if (!context) {
7385
+ return -1
7386
+ }
7387
+ const data = context.getImageData(0, 0, visualMapOutOfRangeCanvas.width, visualMapOutOfRangeCanvas.height).data
7388
+ let count = 0
7389
+ for (let index = 0; index < data.length; index += 4) {
7390
+ if (data[index] < 100 && data[index + 1] > 180 && data[index + 2] < 100) {
7391
+ count += 1
7392
+ }
7393
+ }
7394
+ return count
7395
+ }
7396
+ const visualMapOutOfRangeInitialFills = visualMapOutOfRangeScenePoints().map(point => point.fill)
7397
+ const visualMapOutOfRangeMiniInitial = visualMapOutOfRangeMiniGreen()
7398
+ check('visualMap:outOfRange 缺省 heatmap,只退热力,落选节点仍在画布上',
7399
+ visualMapOutOfRangeView.getVisualMap()?.outOfRange === 'heatmap' &&
7400
+ visualMapOutOfRangeInitialFills.length === 3 && visualMapOutOfRangeInitialFills[0] === 'rgba(255,255,255,1)' && visualMapOutOfRangeInitialFills[1] === 'rgba(255,0,0,1)' && visualMapOutOfRangeInitialFills[2] === 'rgba(255,255,255,1)',
7401
+ JSON.stringify({ mode: visualMapOutOfRangeView.getVisualMap()?.outOfRange, fills: visualMapOutOfRangeInitialFills }))
7402
+ visualMapOutOfRangeView.selectFeatures('vmor-a')
7403
+ const visualMapOutOfRangeFilterBefore = visualMapOutOfRangeView.getFilterResult()
7404
+ visualMapOutOfRangeView.updateOptions({ visualMap: { ...visualMapOutOfRangeConfig, outOfRange: 'network' } })
7405
+ await settle()
7406
+ const visualMapOutOfRangeHiddenIds = visualMapOutOfRangeView.listPoints().map(point => point.item.id)
7407
+ const visualMapOutOfRangeMiniHidden = visualMapOutOfRangeMiniGreen()
7408
+ check('visualMap:network 让落选点退出画布与可见列表,数据 / 筛选 / 选中集原样保留',
7409
+ visualMapOutOfRangeHiddenIds.join(',') === 'vmor-b' &&
7410
+ visualMapOutOfRangeScenePoints().length === 1 &&
7411
+ visualMapOutOfRangeView.getData().points.length === 3 &&
7412
+ visualMapOutOfRangeView.getVisibleData().points.length === 3 &&
7413
+ visualMapOutOfRangeView.getFilterResult() === visualMapOutOfRangeFilterBefore &&
7414
+ visualMapOutOfRangeView.getSelectionData().ids.join(',') === 'vmor-a',
7415
+ JSON.stringify({ visible: visualMapOutOfRangeHiddenIds, data: visualMapOutOfRangeView.getData().points.length, selection: visualMapOutOfRangeView.getSelectionData().ids }))
7416
+ check('visualMap:network 的隐藏集合同步到使用独立热力配置的鹰眼图',
7417
+ visualMapOutOfRangeMiniInitial > 0 && visualMapOutOfRangeMiniHidden > 0 && visualMapOutOfRangeMiniHidden < visualMapOutOfRangeMiniInitial * 0.75,
7418
+ `初始绿色 ${visualMapOutOfRangeMiniInitial} px · 隐藏后 ${visualMapOutOfRangeMiniHidden} px`)
7419
+ visualMapOutOfRangeView.updateOptions({ visualMap: { ...visualMapOutOfRangeConfig, outOfRange: 'network', selected: [0, 1, 2] } })
7420
+ await settleMs(300)
7421
+ const visualMapOutOfRangeMiniRestored = visualMapOutOfRangeMiniGreen()
7422
+ check('visualMap:network 恢复全选后落选点回到画布,原选中集仍在',
7423
+ visualMapOutOfRangeView.listPoints().length === 3 && visualMapOutOfRangeScenePoints().length === 3 &&
7424
+ visualMapOutOfRangeView.getSelectionData().ids.join(',') === 'vmor-a' && visualMapOutOfRangeMiniRestored >= visualMapOutOfRangeMiniInitial * 0.8,
7425
+ JSON.stringify({ visible: visualMapOutOfRangeView.listPoints().map(point => point.item.id), selection: visualMapOutOfRangeView.getSelectionData().ids, mini: [visualMapOutOfRangeMiniInitial, visualMapOutOfRangeMiniHidden, visualMapOutOfRangeMiniRestored] }))
7426
+ visualMapOutOfRangeView.updateOptions({ visualMap: { ...visualMapOutOfRangeConfig, outOfRange: 'none', selected: [0, 1, 2] } })
7427
+ await settle()
7428
+ const visualMapOutOfRangeNoneChanges = visualMapOutOfRangeChanges.length
7429
+ visualMapOutOfRangeView.getVisualMapElement().querySelectorAll('.gis-view__visual-map-piece')[1].dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
7430
+ await settle()
7431
+ const visualMapOutOfRangeNoneFills = visualMapOutOfRangeScenePoints().map(point => point.fill)
7432
+ check('visualMap:outOfRange none 不做事先门控,选区变化只通过 onChange 交给业务',
7433
+ visualMapOutOfRangeNoneFills.length === 3 && visualMapOutOfRangeNoneFills.every(fill => fill === 'rgba(255,0,0,1)') &&
7434
+ visualMapOutOfRangeView.getVisualMap().selected.join(',') === '0,2' &&
7435
+ visualMapOutOfRangeChanges.length === visualMapOutOfRangeNoneChanges + 1 && visualMapOutOfRangeChanges.at(-1)?.outOfRange === 'none',
7436
+ JSON.stringify({ fills: visualMapOutOfRangeNoneFills, selected: visualMapOutOfRangeView.getVisualMap().selected, changes: visualMapOutOfRangeChanges.length - visualMapOutOfRangeNoneChanges }))
7437
+ visualMapOutOfRangeView.destroy()
7438
+ visualMapOutOfRangeHost.remove()
7439
+
7440
+ const visualMapLineOutOfRangeHost = document.createElement('div')
7441
+ visualMapLineOutOfRangeHost.style.cssText = 'width:520px;height:300px'
7442
+ document.body.append(visualMapLineOutOfRangeHost)
7443
+ const visualMapLineOutOfRangeConfig = {
7444
+ target: 'line',
7445
+ field: 'value',
7446
+ type: 'piecewise',
7447
+ interactive: true,
7448
+ selectedMode: 'multiple',
7449
+ pieces: [{ lt: 5 }, { gte: 5, lt: 15 }, { gte: 15 }],
7450
+ selected: [0, 1],
7451
+ outOfRange: 'network'
7452
+ }
7453
+ const visualMapLineOutOfRangeView = createGisView(visualMapLineOutOfRangeHost, {
7454
+ points: [
7455
+ { id: 'vml-a', x: 0, y: 0, size: 12 },
7456
+ { id: 'vml-b', x: 180, y: 60, size: 12 }
7457
+ ],
7458
+ lines: [
7459
+ { id: 'vml-solo-low', path: [[0, 130], [180, 130]], value: 1, width: 6 },
7460
+ { id: 'vml-solo-high', path: [[0, 160], [180, 160]], value: 10, width: 6 },
7461
+ { id: 'vml-ch-low', path: [[0, 0], [90, 30], [180, 60]], value: 1, width: 6 },
7462
+ { id: 'vml-ch-high', path: [[0, 10], [90, 40], [180, 70]], value: 10, width: 6 }
7463
+ ],
7464
+ channels: [
7465
+ { id: 'vml-ch-low', from: 'vml-a', to: 'vml-b', lineIds: ['vml-ch-low'], value: 1 },
7466
+ { id: 'vml-ch-high', from: 'vml-a', to: 'vml-b', lineIds: ['vml-ch-high'], value: 10 }
7467
+ ],
7468
+ visualMap: visualMapLineOutOfRangeConfig,
7469
+ showGrid: false
7470
+ })
7471
+ await settle()
7472
+ const visualMapLineSceneCount = () => {
7473
+ const scene = visualMapLineOutOfRangeView.snapshotScene()
7474
+ return scene.lines.length + scene.raisedLines.length
7475
+ }
7476
+ const visualMapLineChannelsBefore = JSON.stringify(visualMapLineOutOfRangeView.getChannels())
7477
+ const visualMapLineChannelLowBefore = JSON.stringify(visualMapLineOutOfRangeView.getChannel('vml-ch-low'))
7478
+ const visualMapLineFilterBefore = visualMapLineOutOfRangeView.getFilterResult()
7479
+ visualMapLineOutOfRangeView.selectFeatures('vml-solo-low')
7480
+ visualMapLineOutOfRangeView.updateOptions({ visualMap: { ...visualMapLineOutOfRangeConfig, selected: [1] } })
7481
+ await settle()
7482
+ const visualMapLineHiddenIds = visualMapLineOutOfRangeView.listLines().map(line => line.item.id)
7483
+ check('visualMap:network + line 只隐藏落选独立线路,不误伤同值的通道回线',
7484
+ !visualMapLineHiddenIds.includes('vml-solo-low') && visualMapLineHiddenIds.includes('vml-solo-high') &&
7485
+ visualMapLineHiddenIds.includes('vml-ch-low') && visualMapLineHiddenIds.includes('vml-ch-high') &&
7486
+ visualMapLineSceneCount() === 3 && visualMapLineOutOfRangeView.getData().lines.length === 4 &&
7487
+ visualMapLineOutOfRangeView.getFilterResult() === visualMapLineFilterBefore && visualMapLineOutOfRangeView.getSelectionData().ids.join(',') === 'vml-solo-low',
7488
+ JSON.stringify({ visible: visualMapLineHiddenIds, data: visualMapLineOutOfRangeView.getData().lines.length, selection: visualMapLineOutOfRangeView.getSelectionData().ids }))
7489
+ visualMapLineOutOfRangeView.updateOptions({ visualMap: { ...visualMapLineOutOfRangeConfig, target: 'channel', selected: [1] } })
7490
+ await settle()
7491
+ const visualMapChannelHiddenIds = visualMapLineOutOfRangeView.listLines().map(line => line.item.id)
7492
+ check('visualMap:network + channel 按声明隐藏整条落选通道,通道声明与数据不变',
7493
+ visualMapChannelHiddenIds.includes('vml-solo-low') && visualMapChannelHiddenIds.includes('vml-solo-high') &&
7494
+ !visualMapChannelHiddenIds.includes('vml-ch-low') && visualMapChannelHiddenIds.includes('vml-ch-high') &&
7495
+ visualMapLineSceneCount() === 3 &&
7496
+ JSON.stringify(visualMapLineOutOfRangeView.getChannels()) === visualMapLineChannelsBefore &&
7497
+ JSON.stringify(visualMapLineOutOfRangeView.getChannel('vml-ch-low')) === visualMapLineChannelLowBefore,
7498
+ JSON.stringify({ visible: visualMapChannelHiddenIds, channels: visualMapLineOutOfRangeView.getChannels().map(channel => channel.channelId) }))
7499
+ visualMapLineOutOfRangeView.updateOptions({ visualMap: { ...visualMapLineOutOfRangeConfig, target: 'channel', selected: [0, 1] } })
7500
+ await settle()
7501
+ check('visualMap:channel 恢复全选后所有通道回线回到画布',
7502
+ visualMapLineOutOfRangeView.listLines().length === 4 && visualMapLineSceneCount() === 4,
7503
+ JSON.stringify({ visible: visualMapLineOutOfRangeView.listLines().map(line => line.item.id) }))
7504
+ visualMapLineOutOfRangeView.destroy()
7505
+ visualMapLineOutOfRangeHost.remove()
7506
+
7507
+ const visualMapMultiTargetHost = document.createElement('div')
7508
+ visualMapMultiTargetHost.style.cssText = 'width:520px;height:300px'
7509
+ document.body.append(visualMapMultiTargetHost)
7510
+ const visualMapMultiTargetConfig = {
7511
+ field: 'value',
7512
+ type: 'piecewise',
7513
+ interactive: true,
7514
+ selectedMode: 'multiple',
7515
+ pieces: [{ lt: 5 }, { gte: 5 }],
7516
+ selected: [1],
7517
+ outOfRange: 'network'
7518
+ }
7519
+ const visualMapMultiTargetView = createGisView(visualMapMultiTargetHost, {
7520
+ points: [
7521
+ { id: 'vmmt-low', x: 0, y: 0, size: 12 },
7522
+ { id: 'vmmt-high', x: 180, y: 60, size: 12 }
7523
+ ],
7524
+ lines: [
7525
+ { id: 'vmmt-solo-low', path: [[0, 130], [180, 130]], value: 1, width: 6 },
7526
+ { id: 'vmmt-solo-high', path: [[0, 160], [180, 160]], value: 10, width: 6 },
7527
+ { id: 'vmmt-ch-low', path: [[0, 0], [90, 30], [180, 60]], value: 1, width: 6 },
7528
+ { id: 'vmmt-ch-high', path: [[0, 10], [90, 40], [180, 70]], value: 10, width: 6 }
7529
+ ],
7530
+ channels: [
7531
+ { id: 'vmmt-ch-low', from: 'vmmt-low', to: 'vmmt-high', lineIds: ['vmmt-ch-low'], value: 1 },
7532
+ { id: 'vmmt-ch-high', from: 'vmmt-low', to: 'vmmt-high', lineIds: ['vmmt-ch-high'], value: 10 }
7533
+ ],
7534
+ heatmap: { target: ['point', 'line', 'channel'], field: 'value', density: false, glow: false, opacity: 1 },
7535
+ visualMap: visualMapMultiTargetConfig,
7536
+ showGrid: false
7537
+ })
7538
+ await settle()
7539
+ const visualMapMultiTargetInitial = visualMapMultiTargetView.getVisualMap()
7540
+ const visualMapMultiTargetInitialLines = visualMapMultiTargetView.listLines().map(line => line.item.id)
7541
+ check('visualMap:不写 target 时跳过缺字段的节点选展示值域,门控仍覆盖全部热力目标',
7542
+ visualMapMultiTargetInitial?.target === 'line' && visualMapMultiTargetInitial.domain.join(',') === '1,10' &&
7543
+ visualMapMultiTargetView.listPoints().length === 2 &&
7544
+ visualMapMultiTargetInitialLines.join(',') === 'vmmt-solo-high,vmmt-ch-high',
7545
+ JSON.stringify({ readout: visualMapMultiTargetInitial && { target: visualMapMultiTargetInitial.target, domain: visualMapMultiTargetInitial.domain }, points: visualMapMultiTargetView.listPoints().map(point => point.item.id), lines: visualMapMultiTargetInitialLines }))
7546
+ visualMapMultiTargetView.updatePoint('vmmt-low', { value: 1 })
7547
+ visualMapMultiTargetView.updatePoint('vmmt-high', { value: 10 })
7548
+ await settle()
7549
+ const visualMapMultiTargetPoints = visualMapMultiTargetView.listPoints().map(point => point.item.id)
7550
+ const visualMapMultiTargetLines = visualMapMultiTargetView.listLines().map(line => line.item.id)
7551
+ check('visualMap:多目标 network 同时隐藏落选节点、独立线路与整条落选通道',
7552
+ visualMapMultiTargetView.getVisualMap()?.target === 'point' &&
7553
+ visualMapMultiTargetPoints.join(',') === 'vmmt-high' &&
7554
+ visualMapMultiTargetLines.join(',') === 'vmmt-solo-high,vmmt-ch-high' &&
7555
+ visualMapMultiTargetView.getData().points.length === 2 && visualMapMultiTargetView.getData().lines.length === 4 &&
7556
+ visualMapMultiTargetView.getChannels().length === 2,
7557
+ JSON.stringify({ target: visualMapMultiTargetView.getVisualMap()?.target, points: visualMapMultiTargetPoints, lines: visualMapMultiTargetLines }))
7558
+ visualMapMultiTargetView.updateOptions({ visualMap: { ...visualMapMultiTargetConfig, selected: [0, 1] } })
7559
+ await settle()
7560
+ check('visualMap:多目标恢复全选后节点、独立线路与通道成员全部回到画布',
7561
+ visualMapMultiTargetView.listPoints().length === 2 && visualMapMultiTargetView.listLines().length === 4,
7562
+ JSON.stringify({ points: visualMapMultiTargetView.listPoints().map(point => point.item.id), lines: visualMapMultiTargetView.listLines().map(line => line.item.id) }))
7563
+ visualMapMultiTargetView.destroy()
7564
+ visualMapMultiTargetHost.remove()
7565
+
6830
7566
  lineOnlyView.destroy()
6831
7567
  lineOnlyHost.remove()
6832
7568
  splitView.destroy()