gis-view-webgl2 0.1.2 → 0.1.3
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.
- package/README.md +12 -8
- package/dist/gis-view.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +309 -59
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +117 -0
- package/docs/GisView.md +2 -2
- 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 +107 -0
- package/docs/api/index.html +1 -1
- package/docs/demos/ApiCoverageDemo.vue +644 -0
- package/docs/demos/ChannelDemo.vue +14 -12
- package/docs/demos/ClusterDemo.vue +251 -0
- package/docs/demos/CopyMergeDemo.vue +3 -4
- package/docs/demos/EventDemo.vue +3 -3
- package/docs/demos/GisViewDemo.vue +64 -8809
- package/docs/demos/api-index.js +48 -41
- package/docs/demos/demos.js +33 -16
- package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +28 -0
- package/docs/demos/gis-view-demo/GisViewDemoDialogs.vue +82 -0
- package/docs/demos/gis-view-demo/GisViewDemoFooter.vue +16 -0
- package/docs/demos/gis-view-demo/GisViewDemoHeading.vue +16 -0
- package/docs/demos/gis-view-demo/GisViewDemoPanelStack.vue +71 -0
- package/docs/demos/gis-view-demo/GisViewDemoStage.vue +219 -0
- package/docs/demos/gis-view-demo/demo.css +1230 -0
- package/docs/demos/gis-view-demo/demoContext.js +12 -0
- package/docs/demos/gis-view-demo/panels/DataPanels.vue +303 -0
- package/docs/demos/gis-view-demo/panels/OpsPanels.vue +174 -0
- package/docs/demos/gis-view-demo/panels/StylePanels.vue +561 -0
- package/docs/demos/gis-view-demo/panels/ViewPanels.vue +396 -0
- package/docs/demos/gis-view-demo/useGisViewDemo.js +6188 -0
- package/docs/format/07-/344/272/213/344/273/266/350/275/275/350/215/267/344/270/216/347/244/272/344/276/213/346/225/260/346/215/256.md +3 -2
- package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +1 -1
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +3 -2
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +87 -14
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +8 -8
- 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 +40 -3
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +26 -0
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +6 -2
- package/docs/manual/10-/351/200/232/351/201/223/347/274/226/350/276/221/344/270/216/346/226/260/345/242/236.md +39 -7
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +13 -4
- package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +3 -0
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +35 -19
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +5 -0
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +12 -6
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +39 -22
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +52 -13
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +25 -1
- package/docs/manual/21-/350/257/264/346/230/216/344/270/216/345/270/270/350/247/201/351/227/256/351/242/230.md +5 -5
- package/docs/steps/README.md +17 -11
- package/docs/steps/selftest-actions.html +3 -5
- package/docs/steps/selftest-demo.html +27 -18
- package/docs/steps/selftest-demos.html +387 -27
- package/docs/steps/selftest-docs.html +4 -4
- package/docs/steps/selftest-effects.html +200 -0
- package/docs/steps/selftest-features.html +512 -11
- package/docs/steps/selftest-labels.html +74 -0
- package/docs/steps/selftest-perf.html +293 -20
- package/docs/steps/selftest-scale.html +52 -29
- package/docs/steps/step-10-full.js +1 -1
- package/docs/steps/step-12-events.js +1 -1
- package/package.json +1 -1
|
@@ -1592,6 +1592,243 @@
|
|
|
1592
1592
|
cwView.destroy()
|
|
1593
1593
|
cwHost.remove()
|
|
1594
1594
|
|
|
1595
|
+
/* 问题 3(本轮):setFilter 只让节点不画,不该改变仍显示的通道宽度 ——
|
|
1596
|
+
从前锚点被筛掉就不在 pointByIndex 里,通道退回默认宽度;只筛掉一端时还会改按另一端尺寸算 */
|
|
1597
|
+
const cwfHost = document.createElement('div')
|
|
1598
|
+
cwfHost.style.cssText = 'width:320px;height:200px'
|
|
1599
|
+
document.body.appendChild(cwfHost)
|
|
1600
|
+
const cwfView = createGisView(cwfHost, {
|
|
1601
|
+
points: [
|
|
1602
|
+
{ id: 'cwf-a', x: -30, y: 0, size: 8, symbol: 'square', level: 1 },
|
|
1603
|
+
{ id: 'cwf-b', x: 30, y: 0, size: 24, symbol: 'square', level: 2 }
|
|
1604
|
+
],
|
|
1605
|
+
lines: [
|
|
1606
|
+
{ id: 'cwf-l1', from: 'cwf-a', to: 'cwf-b', width: 1 },
|
|
1607
|
+
{ id: 'cwf-l2', from: 'cwf-a', to: 'cwf-b', width: 1 }
|
|
1608
|
+
],
|
|
1609
|
+
channels: [{ id: 'cwf-ch', from: 'cwf-a', to: 'cwf-b', lineIds: ['cwf-l1', 'cwf-l2'], tangentInset: 0 }],
|
|
1610
|
+
initialView: { center: [0, 0], scale: 1 },
|
|
1611
|
+
showGrid: false
|
|
1612
|
+
})
|
|
1613
|
+
await settle(150)
|
|
1614
|
+
const cwfGap = () => {
|
|
1615
|
+
const tops = cwfView.listLines().filter(line => String(line.item.id).startsWith('cwf-')).map(line => line.path[0][1])
|
|
1616
|
+
return tops.length === 2 ? Math.abs(tops[0] - tops[1]) : 0
|
|
1617
|
+
}
|
|
1618
|
+
const cwfBase = cwfGap()
|
|
1619
|
+
check('筛选前通道宽度基线:两端取小的尺寸 8(tangentInset: 0)', Math.abs(cwfBase - 8) < 0.01, `间距 ${cwfBase}`)
|
|
1620
|
+
cwfView.setFilter({ points: { level: 2 }, cascade: false })
|
|
1621
|
+
await settle(100)
|
|
1622
|
+
check('setFilter 只筛掉小尺寸锚点:回线仍按两端较小值取宽,不跳到大节点尺寸', Math.abs(cwfGap() - cwfBase) < 0.01, `${cwfBase} → ${cwfGap()}`)
|
|
1623
|
+
cwfView.setFilter({ points: { level: 0 }, cascade: false })
|
|
1624
|
+
await settle(100)
|
|
1625
|
+
check('setFilter 把两端锚点都筛掉:通道回线仍显示,宽度不退到默认 channelWidth', cwfView.listLines().filter(line => String(line.item.id).startsWith('cwf-')).length === 2 && Math.abs(cwfGap() - cwfBase) < 0.01, `间距 ${cwfBase} → ${cwfGap()}`)
|
|
1626
|
+
cwfView.setFilter(null)
|
|
1627
|
+
await settle(100)
|
|
1628
|
+
check('清除筛选:通道宽度与筛选前完全一致', Math.abs(cwfGap() - cwfBase) < 0.01, `间距 ${cwfBase} → ${cwfGap()}`)
|
|
1629
|
+
// 可见锚点若按选中 / 悬停后的尺寸算,筛掉另一端时它会变成唯一锚点,通道宽度就会跳
|
|
1630
|
+
cwfView.updateOptions({ pointSelectStyle: { sizeScale: 2 } })
|
|
1631
|
+
cwfView.selectFeatures('cwf-b')
|
|
1632
|
+
await settle(80)
|
|
1633
|
+
cwfView.setFilter({ points: { level: 1 }, cascade: false })
|
|
1634
|
+
await settle(100)
|
|
1635
|
+
check('筛选叠加选中样式:锚点宽度仍按中性尺寸取,不随选中放大而跳', Math.abs(cwfGap() - cwfBase) < 0.01, `间距 ${cwfBase} → ${cwfGap()}`)
|
|
1636
|
+
cwfView.setFilter({ points: { level: 0 }, cascade: false })
|
|
1637
|
+
await settle(100)
|
|
1638
|
+
check('选中状态下把两端都筛掉:通道宽度仍保持原值', Math.abs(cwfGap() - cwfBase) < 0.01, `间距 ${cwfBase} → ${cwfGap()}`)
|
|
1639
|
+
cwfView.setFilter(null)
|
|
1640
|
+
await settle(80)
|
|
1641
|
+
cwfView.destroy()
|
|
1642
|
+
cwfHost.remove()
|
|
1643
|
+
|
|
1644
|
+
/* 分组通道 + 成员回线被筛:筛选只影响可见性,保留下来的回线仍按原来的车道与宽度走 */
|
|
1645
|
+
const gfwHost = document.createElement('div')
|
|
1646
|
+
gfwHost.style.cssText = 'width:340px;height:220px'
|
|
1647
|
+
document.body.appendChild(gfwHost)
|
|
1648
|
+
const gfwView = createGisView(gfwHost, {
|
|
1649
|
+
points: [
|
|
1650
|
+
{ id: 'gfw-a', x: -30, y: 0, size: 8, symbol: 'square', level: 1 },
|
|
1651
|
+
{ id: 'gfw-b', x: 30, y: 0, size: 24, symbol: 'square', level: 2 }
|
|
1652
|
+
],
|
|
1653
|
+
lines: [
|
|
1654
|
+
{ id: 'gfw-l1', from: 'gfw-a', to: 'gfw-b', color: '#ff0000' },
|
|
1655
|
+
{ id: 'gfw-l2', from: 'gfw-a', to: 'gfw-b', color: '#ff0000' },
|
|
1656
|
+
{ id: 'gfw-l3', from: 'gfw-a', to: 'gfw-b', color: '#0000ff' },
|
|
1657
|
+
{ id: 'gfw-l4', from: 'gfw-a', to: 'gfw-b', color: '#0000ff' }
|
|
1658
|
+
],
|
|
1659
|
+
channels: [{
|
|
1660
|
+
id: 'gfw-ch', from: 'gfw-a', to: 'gfw-b', lineIds: ['gfw-l1', 'gfw-l2', 'gfw-l3', 'gfw-l4'],
|
|
1661
|
+
tangentInset: 0, offsetMode: 'inset', inset: 0.1,
|
|
1662
|
+
groups: [
|
|
1663
|
+
{ id: 'gfw-top', lineIds: ['gfw-l1', 'gfw-l2'], path: [[0, 24]] },
|
|
1664
|
+
{ id: 'gfw-bot', lineIds: ['gfw-l3', 'gfw-l4'], path: [[0, -24]] }
|
|
1665
|
+
]
|
|
1666
|
+
}],
|
|
1667
|
+
initialView: { center: [0, 0], scale: 1 },
|
|
1668
|
+
showGrid: false
|
|
1669
|
+
})
|
|
1670
|
+
await settle(150)
|
|
1671
|
+
const gfwPaths = () => Object.fromEntries(gfwView.listLines()
|
|
1672
|
+
.filter(line => String(line.item.id).startsWith('gfw-'))
|
|
1673
|
+
.map(line => [String(line.item.id), JSON.stringify(line.path)]))
|
|
1674
|
+
const gfwBase = gfwPaths()
|
|
1675
|
+
const gfwGeometry = () => JSON.stringify({
|
|
1676
|
+
channels: gfwView.getChannels(),
|
|
1677
|
+
groups: gfwView.getChannelGroups('gfw-ch')
|
|
1678
|
+
})
|
|
1679
|
+
const gfwGeometryBase = gfwGeometry()
|
|
1680
|
+
gfwView.setFilter({ points: { level: 0 }, cascade: false })
|
|
1681
|
+
await settle(100)
|
|
1682
|
+
const gfwNoPoints = gfwPaths()
|
|
1683
|
+
check('分组通道:两端节点被筛掉时四条回线仍在原车道', Object.keys(gfwNoPoints).length === 4 && Object.keys(gfwBase).every(id => gfwNoPoints[id] === gfwBase[id]), JSON.stringify({ base: gfwBase, current: gfwNoPoints }))
|
|
1684
|
+
gfwView.setFilter({ lines: { color: '#ff0000' }, cascade: false })
|
|
1685
|
+
await settle(100)
|
|
1686
|
+
const gfwFiltered = gfwPaths()
|
|
1687
|
+
check('分组通道:筛掉一半成员回线后,保留回线的几何与筛选前完全一致', Object.keys(gfwFiltered).length === 2 && gfwFiltered['gfw-l1'] === gfwBase['gfw-l1'] && gfwFiltered['gfw-l2'] === gfwBase['gfw-l2'], JSON.stringify({ base: [gfwBase['gfw-l1'], gfwBase['gfw-l2']], current: gfwFiltered }))
|
|
1688
|
+
gfwView.setFilter(null)
|
|
1689
|
+
await settle(80)
|
|
1690
|
+
const gfwRestored = gfwPaths()
|
|
1691
|
+
check('分组通道:清除筛选后四条回线逐顶点回到筛选前的几何', Object.keys(gfwRestored).length === 4 && Object.keys(gfwBase).every(id => gfwRestored[id] === gfwBase[id]), JSON.stringify({ base: gfwBase, current: gfwRestored }))
|
|
1692
|
+
gfwView.setFilter({ lines: { color: '#ff0000' }, cascade: false })
|
|
1693
|
+
await settle(80)
|
|
1694
|
+
gfwView.setFilter(null)
|
|
1695
|
+
await settle(80)
|
|
1696
|
+
const gfwRestoredAgain = gfwPaths()
|
|
1697
|
+
check('分组通道:连续隐藏 / 恢复成员回线后仍按原车道与骨架绘制', Object.keys(gfwRestoredAgain).length === 4 && Object.keys(gfwBase).every(id => gfwRestoredAgain[id] === gfwBase[id]), JSON.stringify({ base: gfwBase, current: gfwRestoredAgain }))
|
|
1698
|
+
// 默认 binding 级联会把两端节点、绑定回线与整条通道一起收起;恢复时引擎必须按当前锚点重新接上,
|
|
1699
|
+
// 不能沿用「隐藏期间没有骨架」的通道状态。这里连通道 / 分组读数一起对,避免只看 listLines 漏掉串线。
|
|
1700
|
+
gfwView.setFilter({ points: { level: 0 } })
|
|
1701
|
+
await settle(100)
|
|
1702
|
+
check('分组通道:默认 binding 级联筛掉两端节点时回线一起收起', gfwView.listLines().filter(line => String(line.item.id).startsWith('gfw-')).length === 0, JSON.stringify({ lines: gfwPaths() }))
|
|
1703
|
+
gfwView.setFilter(null)
|
|
1704
|
+
await settle(100)
|
|
1705
|
+
check('分组通道:默认级联恢复后回线、通道骨架与分组骨架逐项回到原值', Object.keys(gfwPaths()).length === 4 && Object.keys(gfwBase).every(id => gfwPaths()[id] === gfwBase[id]) && gfwGeometry() === gfwGeometryBase, JSON.stringify({ base: gfwGeometryBase, current: gfwGeometry() }))
|
|
1706
|
+
gfwView.hideFeatures('point', ['gfw-a', 'gfw-b'])
|
|
1707
|
+
await settle(100)
|
|
1708
|
+
gfwView.showFeatures('point', ['gfw-a', 'gfw-b'])
|
|
1709
|
+
await settle(100)
|
|
1710
|
+
check('分组通道:visible 隐藏 / 显示两端节点后几何仍逐项回到原值', Object.keys(gfwPaths()).length === 4 && Object.keys(gfwBase).every(id => gfwPaths()[id] === gfwBase[id]) && gfwGeometry() === gfwGeometryBase, JSON.stringify({ base: gfwGeometryBase, current: gfwGeometry() }))
|
|
1711
|
+
gfwView.destroy()
|
|
1712
|
+
gfwHost.remove()
|
|
1713
|
+
|
|
1714
|
+
/* 焦点类型消歧 + 分组选中 + 拐点选中 */
|
|
1715
|
+
const faHost = document.createElement('div')
|
|
1716
|
+
faHost.style.cssText = 'width:420px;height:260px'
|
|
1717
|
+
document.body.appendChild(faHost)
|
|
1718
|
+
const faView = createGisView(faHost, {
|
|
1719
|
+
points: [
|
|
1720
|
+
{ id: 'same', x: 0, y: 0 },
|
|
1721
|
+
{ id: 'fa-low-a', x: -50, y: -40 },
|
|
1722
|
+
{ id: 'fa-low-b', x: 50, y: -40 },
|
|
1723
|
+
{ id: 'fa-high-a', x: -50, y: 40 },
|
|
1724
|
+
{ id: 'fa-high-b', x: 50, y: 40 }
|
|
1725
|
+
],
|
|
1726
|
+
lines: [
|
|
1727
|
+
{ id: 'same', path: [[-100, 0], [-80, 0]] },
|
|
1728
|
+
{ id: 'fa-l1', from: 'fa-low-a', to: 'fa-low-b' },
|
|
1729
|
+
{ id: 'fa-l2', from: 'fa-low-a', to: 'fa-low-b' },
|
|
1730
|
+
{ id: 'fa-l3', from: 'fa-high-a', to: 'fa-high-b' },
|
|
1731
|
+
{ id: 'fa-l4', from: 'fa-high-a', to: 'fa-high-b' },
|
|
1732
|
+
{ id: 'fa-around', path: [[0, 80], [50, 100], [100, 80]] }
|
|
1733
|
+
],
|
|
1734
|
+
channels: [
|
|
1735
|
+
{ id: 'fa-low-ch', from: 'fa-low-a', to: 'fa-low-b', lineIds: ['fa-l1', 'fa-l2'], groups: [{ id: 'fa-low', lineIds: ['fa-l1', 'fa-l2'] }] },
|
|
1736
|
+
{ id: 'fa-high-ch', from: 'fa-high-a', to: 'fa-high-b', lineIds: ['fa-l3', 'fa-l4'], groups: [{ id: 'fa-high', lineIds: ['fa-l3', 'fa-l4'] }] }
|
|
1737
|
+
],
|
|
1738
|
+
initialView: { center: [0, 0], scale: 1 },
|
|
1739
|
+
waypointEditable: true,
|
|
1740
|
+
showGrid: false
|
|
1741
|
+
})
|
|
1742
|
+
await settle(100)
|
|
1743
|
+
faView.focusOn('same')
|
|
1744
|
+
const faStringCenter = faView.getCamera().center
|
|
1745
|
+
faView.focusOn({ type: 'line', id: 'same' })
|
|
1746
|
+
const faLineCenter = faView.getCamera().center
|
|
1747
|
+
faView.focusOn({ type: 'point', id: 'same' })
|
|
1748
|
+
const faPointCenter = faView.getCamera().center
|
|
1749
|
+
faView.focusOn({ type: 'channel', id: 'fa-low-ch' })
|
|
1750
|
+
const faChannelCenter = faView.getCamera().center
|
|
1751
|
+
faView.focusOn({ type: 'group', groupId: 'fa-high' })
|
|
1752
|
+
const faHighCenter = faView.getCamera().center
|
|
1753
|
+
faView.focusOn({ type: 'group', channelId: 'fa-low-ch', groupId: 'fa-low' })
|
|
1754
|
+
const faLowCenter = faView.getCamera().center
|
|
1755
|
+
check('focusOn:字符串同 id 先聚焦节点,显式 type 可聚焦同名线路', Math.abs(faStringCenter[0]) < 0.01 && Math.abs(faPointCenter[0]) < 0.01 && Math.abs(faLineCenter[0] + 90) < 0.01, JSON.stringify({ string: faStringCenter, point: faPointCenter, line: faLineCenter }))
|
|
1756
|
+
check('focusOn:显式通道 / 分组类型按各自范围居中(groupId 唯一时可不写 channelId)', Math.abs(faChannelCenter[0]) < 0.01 && Math.abs(faHighCenter[1] - 40) < 0.01 && Math.abs(faLowCenter[1] + 40) < 0.01, JSON.stringify({ channel: faChannelCenter, high: faHighCenter, low: faLowCenter }))
|
|
1757
|
+
faView.selectFeatures({ type: 'group', channelId: 'fa-low-ch', groupId: 'fa-low' })
|
|
1758
|
+
const faSelectedGroup = faView.getSelections().filter(item => item.type === 'line').map(item => item.item.id).sort()
|
|
1759
|
+
faView.selectFeatures({ type: 'group', groupId: 'fa-high' })
|
|
1760
|
+
const faSelectedUnique = faView.getSelections().filter(item => item.type === 'line').map(item => item.item.id).sort()
|
|
1761
|
+
check('selectFeatures:type=group 与唯一 groupId 都只选中该分组', faSelectedGroup.join(',') === 'fa-l1,fa-l2' && faSelectedUnique.join(',') === 'fa-l3,fa-l4', JSON.stringify({ low: faSelectedGroup, high: faSelectedUnique }))
|
|
1762
|
+
faView.selectWaypoint('fa-around', 1)
|
|
1763
|
+
const faWaypointSelected = faView.getWaypoints().some(item => item.itemId === undefined && item.lineIndex === 5 && item.pathIndex === 1 && item.selected)
|
|
1764
|
+
const faWaypointLineSelected = faView.getSelections().some(item => item.type === 'line' && item.item.id === 'fa-around')
|
|
1765
|
+
faView.selectFeatures('fa-around')
|
|
1766
|
+
const faWaypointCleared = faView.getWaypoints().filter(item => item.lineIndex === 5).every(item => !item.selected)
|
|
1767
|
+
check('selectWaypoint:按线路 + 下标选中拐点并带选线路,selectFeatures 会清掉拐点选中', faWaypointSelected && faWaypointLineSelected && faWaypointCleared, JSON.stringify({ selected: faWaypointSelected, line: faWaypointLineSelected, cleared: faWaypointCleared, all: faView.getWaypoints(), lines: faView.getData().lines }))
|
|
1768
|
+
faView.destroy()
|
|
1769
|
+
faHost.remove()
|
|
1770
|
+
|
|
1771
|
+
/* lineFollowsNode: false:隐藏 / 筛选 / 删除只动节点,回线与通道停在最后坐标 */
|
|
1772
|
+
const lfHost = document.createElement('div')
|
|
1773
|
+
lfHost.style.cssText = 'width:360px;height:220px'
|
|
1774
|
+
document.body.appendChild(lfHost)
|
|
1775
|
+
const lfView = createGisView(lfHost, {
|
|
1776
|
+
points: [
|
|
1777
|
+
{ id: 'lf-a', x: -30, y: 0, size: 16, level: 0 },
|
|
1778
|
+
{ id: 'lf-b', x: 30, y: 0, size: 16, level: 1 }
|
|
1779
|
+
],
|
|
1780
|
+
lines: [
|
|
1781
|
+
{ id: 'lf-l1', from: 'lf-a', to: 'lf-b', color: '#d9480f' },
|
|
1782
|
+
{ id: 'lf-l2', from: 'lf-a', to: 'lf-b', color: '#d9480f' }
|
|
1783
|
+
],
|
|
1784
|
+
channels: [{
|
|
1785
|
+
id: 'lf-ch', from: 'lf-a', to: 'lf-b', lineIds: ['lf-l1', 'lf-l2'],
|
|
1786
|
+
tangentInset: 0, offsetMode: 'inset', inset: 0.12,
|
|
1787
|
+
groups: [{ id: 'lf-g', lineIds: ['lf-l1', 'lf-l2'], path: [[0, 18]] }]
|
|
1788
|
+
}],
|
|
1789
|
+
lineFollowsNode: false,
|
|
1790
|
+
history: true,
|
|
1791
|
+
initialView: { center: [0, 0], scale: 1 },
|
|
1792
|
+
showGrid: false
|
|
1793
|
+
})
|
|
1794
|
+
await settle(120)
|
|
1795
|
+
const lfPaths = () => Object.fromEntries(lfView.listLines()
|
|
1796
|
+
.filter(line => String(line.item.id).startsWith('lf-'))
|
|
1797
|
+
.map(line => [String(line.item.id), JSON.stringify(line.path)]))
|
|
1798
|
+
const lfBase = lfPaths()
|
|
1799
|
+
lfView.setFilter({ points: { level: 0 } })
|
|
1800
|
+
await settle(120)
|
|
1801
|
+
const lfFiltered = lfPaths()
|
|
1802
|
+
check('lineFollowsNode:false:筛选未写 cascade 时只隐藏节点,回线几何不变', lfView.getVisibleData().points.length === 1 && lfView.listLines().filter(line => String(line.item.id).startsWith('lf-')).length === 2 && Object.keys(lfBase).every(id => lfFiltered[id] === lfBase[id]), JSON.stringify({ base: lfBase, current: lfFiltered }))
|
|
1803
|
+
lfView.updateOptions({ lineFollowsNode: true })
|
|
1804
|
+
await settle(120)
|
|
1805
|
+
check('lineFollowsNode:动态切到 true 后按默认 cascade 收起回线', lfView.listLines().filter(line => String(line.item.id).startsWith('lf-')).length === 0, JSON.stringify(lfPaths()))
|
|
1806
|
+
lfView.updateOptions({ lineFollowsNode: false })
|
|
1807
|
+
await settle(120)
|
|
1808
|
+
const lfToggledBack = lfPaths()
|
|
1809
|
+
check('lineFollowsNode:动态切回 false 后按节点最后坐标恢复回线', lfView.getVisibleData().points.length === 1 && Object.keys(lfBase).every(id => lfToggledBack[id] === lfBase[id]), JSON.stringify({ base: lfBase, current: lfToggledBack }))
|
|
1810
|
+
lfView.setFilter({ points: { level: 0 }, cascade: 'binding' })
|
|
1811
|
+
await settle(120)
|
|
1812
|
+
check('lineFollowsNode:false:筛选显式 cascade 仍然优先', lfView.listLines().filter(line => String(line.item.id).startsWith('lf-')).length === 0, JSON.stringify(lfPaths()))
|
|
1813
|
+
lfView.setFilter(null)
|
|
1814
|
+
await settle(120)
|
|
1815
|
+
lfView.hideFeatures('point', 'lf-a')
|
|
1816
|
+
await settle(120)
|
|
1817
|
+
const lfHidden = lfPaths()
|
|
1818
|
+
check('lineFollowsNode:false:visible:false 隐藏节点后回线停在最后坐标', lfView.listLines().filter(line => String(line.item.id).startsWith('lf-')).length === 2 && Object.keys(lfBase).every(id => lfHidden[id] === lfBase[id]), JSON.stringify({ base: lfBase, current: lfHidden }))
|
|
1819
|
+
lfView.showFeatures('point', 'lf-a')
|
|
1820
|
+
await settle(120)
|
|
1821
|
+
const lfRemoved = lfView.removePoints('lf-a')
|
|
1822
|
+
await settle(120)
|
|
1823
|
+
const lfAfterRemove = lfPaths()
|
|
1824
|
+
const lfChannelCount = lfView.getData().channels?.length ?? lfView.getChannels().length
|
|
1825
|
+
check('lineFollowsNode:false:删除节点不删回线 / 通道,几何继续停在最后坐标', lfRemoved.ok && lfRemoved.removedLines === 0 && lfRemoved.removedChannels === 0 && lfView.getData().points.length === 1 && lfView.getData().lines.length === 2 && lfChannelCount === 1 && Object.keys(lfBase).every(id => lfAfterRemove[id] === lfBase[id]), JSON.stringify({ removed: lfRemoved, paths: lfAfterRemove }))
|
|
1826
|
+
lfView.undo()
|
|
1827
|
+
await settle(120)
|
|
1828
|
+
check('lineFollowsNode:false:撤销删除后节点与原有几何一起回来', lfView.getData().points.length === 2 && lfView.getData().lines.length === 2 && Object.keys(lfBase).every(id => lfPaths()[id] === lfBase[id]), JSON.stringify({ base: lfBase, current: lfPaths(), points: lfView.getData().points }))
|
|
1829
|
+
lfView.destroy()
|
|
1830
|
+
lfHost.remove()
|
|
1831
|
+
|
|
1595
1832
|
/* 问题 2(续):图标节点走的永远是「整份重排」那一支 —— 跟随几何与宽度重排从前欠在分支之后,
|
|
1596
1833
|
节点换了大小 / 图标之后回线还铺在旧宽度上,要再点一下画布才追齐(同一份问题.txt 的再一轮)。
|
|
1597
1834
|
这两条用贴图符号(SVG,size 8 / 24)+ tangentInset: 0:间距就是节点外框本身 */
|
|
@@ -2767,13 +3004,12 @@
|
|
|
2767
3004
|
busView.destroy()
|
|
2768
3005
|
busHost.remove()
|
|
2769
3006
|
|
|
2770
|
-
/*
|
|
3007
|
+
/* 右键统一入口:节点 / 线路 / 通道都只走 onContextMenu,通道上下文挂在同一份载荷上 */
|
|
2771
3008
|
const rcHost = document.createElement('div')
|
|
2772
3009
|
rcHost.style.cssText = 'width:420px;height:300px'
|
|
2773
3010
|
document.body.appendChild(rcHost)
|
|
2774
3011
|
const rcBuilt = createChannels([{ id: 'ch-rc', from: 'rc-n1', to: 'rc-n2', lineIds: ['rc-1', 'rc-2'] }])
|
|
2775
3012
|
const rcHits = []
|
|
2776
|
-
const rcChannels = []
|
|
2777
3013
|
const rcView = createGisView(rcHost, {
|
|
2778
3014
|
points: [
|
|
2779
3015
|
{ id: 'rc-n1', x: 0, y: 0, name: '甲站', size: 20 },
|
|
@@ -2783,11 +3019,9 @@
|
|
|
2783
3019
|
channels: rcBuilt.channels,
|
|
2784
3020
|
fitPadding: 60,
|
|
2785
3021
|
showGrid: false,
|
|
2786
|
-
//
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
onContextMenu: payload => rcHits.push(payload),
|
|
2790
|
-
onChannelContextMenu: payload => rcChannels.push(payload)
|
|
3022
|
+
// 右键回调本体是 contextMenu;通道不再需要第二个功能开关
|
|
3023
|
+
features: { contextMenu: true },
|
|
3024
|
+
onContextMenu: payload => rcHits.push(payload)
|
|
2791
3025
|
})
|
|
2792
3026
|
await settle(140)
|
|
2793
3027
|
const rcRightClick = async (screen) => {
|
|
@@ -2802,20 +3036,195 @@
|
|
|
2802
3036
|
}))
|
|
2803
3037
|
await settle(60)
|
|
2804
3038
|
}
|
|
2805
|
-
//
|
|
3039
|
+
// 端点上右键:这个位置同时压着节点与回线,拾取按「点高于线」给节点,不附通道上下文
|
|
2806
3040
|
await rcRightClick(rcView.project(0, 0))
|
|
2807
3041
|
check('右键落在通道端点(节点压着回线):onContextMenu 给的是节点', rcHits[0]?.type === 'point' && rcHits[0]?.item?.id === 'rc-n1', JSON.stringify({ type: rcHits[0]?.type, id: rcHits[0]?.item?.id }))
|
|
2808
|
-
check('
|
|
3042
|
+
check('右键落在节点上:只发一条 onContextMenu,且没有 channelContext', rcHits.length === 1 && rcHits[0]?.channelContext === undefined, JSON.stringify({ count: rcHits.length, channelContext: rcHits[0]?.channelContext ?? null }))
|
|
2809
3043
|
// 回线中段(离两端节点都远):这里才是「通道右键」
|
|
2810
3044
|
const rcLine = rcView.getData().lines.find(item => String(item.id) === 'rc-1')
|
|
2811
3045
|
const rcMidWorld = Array.isArray(rcLine?.path) && rcLine.path.length > 1 ? rcLine.path[Math.floor(rcLine.path.length / 2)] : [60, 0]
|
|
2812
3046
|
diag('右键回线中段', `世界 ${rcMidWorld.map(value => Number(value).toFixed(2)).join(',')} · 回线折点 ${Array.isArray(rcLine?.path) ? rcLine.path.length : 0}`)
|
|
2813
3047
|
await rcRightClick(rcView.project(rcMidWorld[0], rcMidWorld[1]))
|
|
2814
|
-
|
|
2815
|
-
check('
|
|
3048
|
+
const rcContext = rcHits[1]?.channelContext
|
|
3049
|
+
check('右键落在回线中段:同一份 onContextMenu 载荷里带 channelContext(组数 / 当前分组 / 可用交互)', rcHits.length === 2 && rcContext?.channelId === 'ch-rc' && rcHits[1]?.lineId === 'rc-1' && rcContext?.groupCount === 1 && (rcContext?.actions ?? []).length > 0, JSON.stringify({ count: rcHits.length, channel: rcContext?.channelId, line: rcHits[1]?.lineId, groups: rcContext?.groupCount }))
|
|
3050
|
+
check('右键通道只发这一条(不再额外发 onChannelContextMenu),线路下标 / id 也在载荷上', rcHits[1]?.type === 'line' && rcHits[1]?.channelId === 'ch-rc' && rcHits[1]?.lineId === 'rc-1' && Number.isInteger(rcHits[1]?.lineIndex) && rcHits[1]?.action?.target === 'channel', JSON.stringify({ count: rcHits.length, type: rcHits[1]?.type, lineIndex: rcHits[1]?.lineIndex, target: rcHits[1]?.action?.target }))
|
|
2816
3051
|
rcView.destroy()
|
|
2817
3052
|
rcHost.remove()
|
|
2818
3053
|
|
|
3054
|
+
/* 点击级联高亮:节点亮自身 + 直连线路 / 节点,线路 / 通道亮两端;与选中集完全分开 */
|
|
3055
|
+
const casHost = document.createElement('div')
|
|
3056
|
+
casHost.style.cssText = 'width:520px;height:320px'
|
|
3057
|
+
document.body.appendChild(casHost)
|
|
3058
|
+
const casBuilt = createChannels([{ id: 'cas-ch', from: 'cas-b', to: 'cas-c', circuits: 2, lineIds: ['cas-c1', 'cas-c2'] }])
|
|
3059
|
+
const casEvents = []
|
|
3060
|
+
const casBusEvents = []
|
|
3061
|
+
const casView = createGisView(casHost, {
|
|
3062
|
+
points: [
|
|
3063
|
+
{ id: 'cas-a', x: 0, y: 0, size: 14 },
|
|
3064
|
+
{ id: 'cas-b', x: 120, y: 0, size: 14 },
|
|
3065
|
+
{ id: 'cas-c', x: 240, y: 0, size: 14 },
|
|
3066
|
+
{ id: 'cas-d', x: 60, y: 90, size: 14 }
|
|
3067
|
+
],
|
|
3068
|
+
lines: [
|
|
3069
|
+
{ id: 'cas-ab', from: 'cas-a', to: 'cas-b', path: [[0, 0], [120, 0]] },
|
|
3070
|
+
...casBuilt.lines
|
|
3071
|
+
],
|
|
3072
|
+
channels: casBuilt.channels,
|
|
3073
|
+
fitPadding: 60,
|
|
3074
|
+
showGrid: false,
|
|
3075
|
+
cascadeHighlight: true,
|
|
3076
|
+
// 级联高亮本身不依赖 select;这里打开选中,用来验证两套状态互不污染
|
|
3077
|
+
features: { select: true, contextMenu: true },
|
|
3078
|
+
onCascadeHighlight: payload => casEvents.push(payload)
|
|
3079
|
+
})
|
|
3080
|
+
casView.on('cascadeHighlight', payload => casBusEvents.push(payload))
|
|
3081
|
+
await settle(140)
|
|
3082
|
+
const casClick = async (screen, extra = {}) => {
|
|
3083
|
+
cmSend(casView, 'pointerdown', screen, { buttons: 1, ...extra })
|
|
3084
|
+
cmSend(casView, 'pointerup', screen, { buttons: 0, button: 0, ...extra })
|
|
3085
|
+
await settle(60)
|
|
3086
|
+
}
|
|
3087
|
+
await casClick(casView.project(0, 0))
|
|
3088
|
+
const casPointInfo = casView.getCascadeHighlight()
|
|
3089
|
+
check('级联高亮:点节点亮自身与直连线路 / 另一端节点(默认 both)',
|
|
3090
|
+
casPointInfo?.origin?.target === 'point' && casPointInfo?.origin?.item?.id === 'cas-a' &&
|
|
3091
|
+
casPointInfo.points.map(entry => entry.item.id).sort().join(',') === 'cas-a,cas-b' &&
|
|
3092
|
+
casPointInfo.lines.map(entry => entry.item.id).join(',') === 'cas-ab',
|
|
3093
|
+
JSON.stringify({ origin: casPointInfo?.origin?.item?.id, points: casPointInfo?.points?.map(entry => entry.item.id), lines: casPointInfo?.lines?.map(entry => entry.item.id) }))
|
|
3094
|
+
check('级联高亮不写选中集:亮两个节点与一条线路时选中集仍只是这次点中的节点',
|
|
3095
|
+
casPointInfo?.pointIndexes?.length === 2 && casPointInfo?.lineIndexes?.length === 1 && casView.getSelectionData().count === 1,
|
|
3096
|
+
JSON.stringify({ cascade: [casPointInfo?.pointIndexes, casPointInfo?.lineIndexes], selected: casView.getSelectionData().count }))
|
|
3097
|
+
check('级联高亮:onCascadeHighlight 与 cascadeHighlight 事件都收到同一份读数',
|
|
3098
|
+
casEvents.length === 1 && casBusEvents.length === 1 && casEvents[0]?.origin?.item?.id === 'cas-a' && casBusEvents[0]?.pointIndexes?.length === 2,
|
|
3099
|
+
JSON.stringify({ callback: casEvents.length, event: casBusEvents.length }))
|
|
3100
|
+
|
|
3101
|
+
casEvents.length = 0
|
|
3102
|
+
casBusEvents.length = 0
|
|
3103
|
+
await casClick(casView.project(60, 0))
|
|
3104
|
+
const casLineInfo = casView.getCascadeHighlight()
|
|
3105
|
+
check('级联高亮:点线路只亮两端节点(不把线路自身算进去)',
|
|
3106
|
+
casLineInfo?.origin?.target === 'line' && casLineInfo?.origin?.item?.id === 'cas-ab' &&
|
|
3107
|
+
casLineInfo.points.map(entry => entry.item.id).sort().join(',') === 'cas-a,cas-b' && casLineInfo.lines.length === 0 && casEvents.length === 1,
|
|
3108
|
+
JSON.stringify({ origin: casLineInfo?.origin?.item?.id, points: casLineInfo?.points?.map(entry => entry.item.id), lines: casLineInfo?.lines?.length }))
|
|
3109
|
+
|
|
3110
|
+
casEvents.length = 0
|
|
3111
|
+
const casChannelGeometry = casView.getLine('cas-c1')?.path ?? [[120, 0], [240, 0]]
|
|
3112
|
+
const casChannelStart = casChannelGeometry[0]
|
|
3113
|
+
const casChannelEnd = casChannelGeometry[casChannelGeometry.length - 1]
|
|
3114
|
+
await casClick(casView.project((casChannelStart[0] + casChannelEnd[0]) / 2, (casChannelStart[1] + casChannelEnd[1]) / 2))
|
|
3115
|
+
const casChannelInfo = casView.getCascadeHighlight()
|
|
3116
|
+
check('级联高亮:点通道回线亮两端节点,origin 记通道 / 分组(target 为 channel)',
|
|
3117
|
+
casChannelInfo?.origin?.target === 'channel' && casChannelInfo?.origin?.channelId === 'cas-ch' &&
|
|
3118
|
+
casChannelInfo.points.map(entry => entry.item.id).sort().join(',') === 'cas-b,cas-c' && casChannelInfo.lines.length === 0 && casEvents.length === 1,
|
|
3119
|
+
JSON.stringify({ origin: casChannelInfo?.origin, points: casChannelInfo?.points?.map(entry => entry.item.id) }))
|
|
3120
|
+
|
|
3121
|
+
casEvents.length = 0
|
|
3122
|
+
await casClick([4, 4])
|
|
3123
|
+
check('级联高亮:点空白处收掉,getCascadeHighlight 返回 null 并发一条 null',
|
|
3124
|
+
casView.getCascadeHighlight() === null && casEvents.length === 1 && casEvents[0] === null,
|
|
3125
|
+
JSON.stringify({ info: casView.getCascadeHighlight(), events: casEvents.length }))
|
|
3126
|
+
|
|
3127
|
+
casView.updateOptions({ cascadeHighlight: { scope: 'points' } })
|
|
3128
|
+
await settle(60)
|
|
3129
|
+
casEvents.length = 0
|
|
3130
|
+
await casClick(casView.project(240, 0))
|
|
3131
|
+
const casPointsOnly = casView.getCascadeHighlight()
|
|
3132
|
+
check('级联高亮 scope: points:点节点只亮节点,不亮直连线路',
|
|
3133
|
+
casPointsOnly?.scope === 'points' && casPointsOnly.points.map(entry => entry.item.id).sort().join(',') === 'cas-b,cas-c' && casPointsOnly.lines.length === 0,
|
|
3134
|
+
JSON.stringify({ scope: casPointsOnly?.scope, points: casPointsOnly?.points?.map(entry => entry.item.id), lines: casPointsOnly?.lines?.length }))
|
|
3135
|
+
|
|
3136
|
+
casView.updateOptions({
|
|
3137
|
+
pointSelectStyle: { color: '#00cc44', strokeColor: '#006622', strokeWidth: 1, sizeScale: 1 },
|
|
3138
|
+
cascadeHighlight: {
|
|
3139
|
+
scope: 'both',
|
|
3140
|
+
pointStyle: { color: '#112233', strokeColor: '#445566', strokeWidth: 3, sizeScale: 1.4 },
|
|
3141
|
+
lineStyle: { color: '#778899', width: 7, opacity: 1 }
|
|
3142
|
+
}
|
|
3143
|
+
})
|
|
3144
|
+
await settle(60)
|
|
3145
|
+
const casPointPaint = (snapshot, id) => {
|
|
3146
|
+
const item = casView.getData().points.find(point => point.id === id)
|
|
3147
|
+
const [x, y] = casView.project(item.x, item.y)
|
|
3148
|
+
return [...snapshot.points, ...snapshot.raised].find(point => Math.hypot(point.x - x, point.y - y) < 3)
|
|
3149
|
+
}
|
|
3150
|
+
casEvents.length = 0
|
|
3151
|
+
await casClick(casView.project(0, 0))
|
|
3152
|
+
const casStyledSnapshot = casView.snapshotScene({ background: false })
|
|
3153
|
+
const casSelectedPaint = casPointPaint(casStyledSnapshot, 'cas-a')
|
|
3154
|
+
const casCascadePaint = casPointPaint(casStyledSnapshot, 'cas-b')
|
|
3155
|
+
check('级联高亮样式可单独配置:邻接节点与线路按 pointStyle / lineStyle 解析到画面',
|
|
3156
|
+
casCascadePaint?.fill === 'rgba(17,34,51,1)' &&
|
|
3157
|
+
casStyledSnapshot.lines.some(line => line.color === 'rgba(119,136,153,1)' && Math.abs(line.width - 7) < 0.2),
|
|
3158
|
+
JSON.stringify({ points: [...casStyledSnapshot.points, ...casStyledSnapshot.raised].map(point => point.fill), lines: casStyledSnapshot.lines.map(line => [line.color, line.width]) }))
|
|
3159
|
+
check('级联高亮:直接选中的节点仍走 pointSelectStyle,不套级联样式',
|
|
3160
|
+
casSelectedPaint?.fill === 'rgba(0,204,68,1)' && casCascadePaint?.fill !== casSelectedPaint?.fill,
|
|
3161
|
+
JSON.stringify({ selected: casSelectedPaint, cascade: casCascadePaint }))
|
|
3162
|
+
|
|
3163
|
+
casView.updateOptions({ pointSelectStyle: null })
|
|
3164
|
+
await settle(40)
|
|
3165
|
+
await casClick(casView.project(240, 0))
|
|
3166
|
+
const casNoSelectPaint = casPointPaint(casView.snapshotScene({ background: false }), 'cas-c')
|
|
3167
|
+
check('没配选中样式时直接选中项也不回落到级联样式(保持普通外观)',
|
|
3168
|
+
casNoSelectPaint?.fill !== 'rgba(17,34,51,1)' && JSON.stringify(casView.getSelectionData().ids) === JSON.stringify(['cas-c']),
|
|
3169
|
+
JSON.stringify({ paint: casNoSelectPaint, selected: casView.getSelectionData().ids }))
|
|
3170
|
+
|
|
3171
|
+
// 普通单击 / 多选分开控制:关闭的那一路只改选中,并把旧级联收掉;另一路照常触发
|
|
3172
|
+
casView.updateOptions({ cascadeHighlight: { scope: 'both', triggerOnClick: false, triggerOnMultiSelect: true } })
|
|
3173
|
+
await settle(40)
|
|
3174
|
+
await casClick(casView.project(0, 0))
|
|
3175
|
+
check('triggerOnClick: false:普通单击不触发级联', casView.getCascadeHighlight() === null, JSON.stringify(casView.getCascadeHighlight()))
|
|
3176
|
+
await casClick(casView.project(240, 0), { ctrlKey: true })
|
|
3177
|
+
check('triggerOnMultiSelect: true:Ctrl 多选仍触发级联', casView.getCascadeHighlight()?.origin?.item?.id === 'cas-c' && casView.getCascadeHighlight()?.pointIndexes?.length === 2, JSON.stringify({ origin: casView.getCascadeHighlight()?.origin?.item?.id, points: casView.getCascadeHighlight()?.pointIndexes }))
|
|
3178
|
+
casView.updateOptions({ cascadeHighlight: { scope: 'both', triggerOnClick: true, triggerOnMultiSelect: false } })
|
|
3179
|
+
await settle(40)
|
|
3180
|
+
await casClick(casView.project(120, 0))
|
|
3181
|
+
const casClickOn = casView.getCascadeHighlight()
|
|
3182
|
+
await casClick(casView.project(60, 90), { ctrlKey: true })
|
|
3183
|
+
check('triggerOnMultiSelect: false:普通单击照常触发,Ctrl 多选不再更新而是收掉旧级联',
|
|
3184
|
+
casClickOn?.origin?.item?.id === 'cas-b' && casView.getCascadeHighlight() === null,
|
|
3185
|
+
JSON.stringify({ click: casClickOn?.origin?.item?.id, multi: casView.getCascadeHighlight() }))
|
|
3186
|
+
|
|
3187
|
+
// 框选:默认不触发;打开后整框邻接并集参与级联,直接框中的节点仍走 boxSelectStyle
|
|
3188
|
+
casView.updateOptions({
|
|
3189
|
+
pointBoxSelectStyle: { color: '#00ccff', strokeColor: '#005577', strokeWidth: 1, sizeScale: 1 },
|
|
3190
|
+
cascadeHighlight: { scope: 'both', triggerOnBoxSelect: true }
|
|
3191
|
+
})
|
|
3192
|
+
await settle(40)
|
|
3193
|
+
const casBoxA = casView.project(0, 0)
|
|
3194
|
+
const casBoxB = casView.project(120, 0)
|
|
3195
|
+
const casBox = {
|
|
3196
|
+
x: Math.min(casBoxA[0], casBoxB[0]) - 30,
|
|
3197
|
+
y: Math.min(casBoxA[1], casBoxB[1]) - 30,
|
|
3198
|
+
width: Math.abs(casBoxA[0] - casBoxB[0]) + 60,
|
|
3199
|
+
height: Math.abs(casBoxA[1] - casBoxB[1]) + 60
|
|
3200
|
+
}
|
|
3201
|
+
casView.selectInBox(casBox, { selectionKinds: ['point'] })
|
|
3202
|
+
await settle(40)
|
|
3203
|
+
const casBoxInfo = casView.getCascadeHighlight()
|
|
3204
|
+
const casBoxSnapshot = casView.snapshotScene({ background: false })
|
|
3205
|
+
const casBoxPaint = casPointPaint(casBoxSnapshot, 'cas-a')
|
|
3206
|
+
const casBoxNeighborPaint = casPointPaint(casBoxSnapshot, 'cas-c')
|
|
3207
|
+
check('triggerOnBoxSelect: true:框内所有命中项一起算邻接并集',
|
|
3208
|
+
casBoxInfo?.pointIndexes?.length === 3 && casBoxInfo?.origin?.item?.id === 'cas-a' && casBoxInfo.lineIndexes.length >= 3,
|
|
3209
|
+
JSON.stringify({ origin: casBoxInfo?.origin?.item?.id, points: casBoxInfo?.pointIndexes, lines: casBoxInfo?.lineIndexes }))
|
|
3210
|
+
check('框选直接命中的节点走 boxSelectStyle,只有框外邻接节点套级联样式',
|
|
3211
|
+
casBoxPaint?.fill === 'rgba(0,204,255,1)' && casBoxNeighborPaint?.fill === 'rgba(255,247,237,1)',
|
|
3212
|
+
JSON.stringify({ boxed: casBoxPaint, neighbor: casBoxNeighborPaint }))
|
|
3213
|
+
casView.updateOptions({ cascadeHighlight: { scope: 'both', triggerOnBoxSelect: false } })
|
|
3214
|
+
await settle(40)
|
|
3215
|
+
const casBoxD = casView.project(60, 90)
|
|
3216
|
+
casView.selectInBox({ x: casBoxD[0] - 30, y: casBoxD[1] - 30, width: 60, height: 60 }, { selectionKinds: ['point'] })
|
|
3217
|
+
check('triggerOnBoxSelect: false(缺省):框选只改选中集并收掉旧级联', casView.getCascadeHighlight() === null, JSON.stringify(casView.getCascadeHighlight()))
|
|
3218
|
+
|
|
3219
|
+
casEvents.length = 0
|
|
3220
|
+
casView.updateOptions({ cascadeHighlight: false })
|
|
3221
|
+
await settle(60)
|
|
3222
|
+
check('关掉级联高亮:当前读数与事件都收成 null',
|
|
3223
|
+
casView.getCascadeHighlight() === null && casEvents.length === 1 && casEvents[0] === null,
|
|
3224
|
+
JSON.stringify({ info: casView.getCascadeHighlight(), events: casEvents.length }))
|
|
3225
|
+
casView.destroy()
|
|
3226
|
+
casHost.remove()
|
|
3227
|
+
|
|
2819
3228
|
/* 导出选中的节点:图片 / 数据二选一,线路按「两端都在选中集里」决定带不带 */
|
|
2820
3229
|
const exHost = document.createElement('div')
|
|
2821
3230
|
exHost.style.cssText = 'width:420px;height:300px'
|
|
@@ -2925,6 +3334,67 @@
|
|
|
2925
3334
|
svgView.destroy()
|
|
2926
3335
|
svgHost.remove()
|
|
2927
3336
|
|
|
3337
|
+
/* exportImage 的范围:viewport 只导当前可视区域;all 临时适应全部内容,出完恢复相机 */
|
|
3338
|
+
const scopeHost = document.createElement('div')
|
|
3339
|
+
scopeHost.style.cssText = 'width:360px;height:240px'
|
|
3340
|
+
document.body.appendChild(scopeHost)
|
|
3341
|
+
const scopeView = createGisView(scopeHost, {
|
|
3342
|
+
points: [
|
|
3343
|
+
{ id: 'scope-a', x: 0, y: 0, size: 40, color: '#ff0000' },
|
|
3344
|
+
{ id: 'scope-b', x: 1000, y: 0, size: 40, color: '#0000ff' }
|
|
3345
|
+
],
|
|
3346
|
+
background: { color: '#ffffff' },
|
|
3347
|
+
showGrid: false
|
|
3348
|
+
})
|
|
3349
|
+
await settle(140)
|
|
3350
|
+
scopeView.setCamera({ center: [0, 0], scale: 6 })
|
|
3351
|
+
await settle(60)
|
|
3352
|
+
const scopeCamera = scopeView.getCamera()
|
|
3353
|
+
const scopeColorCounts = async (url) => {
|
|
3354
|
+
const image = new Image()
|
|
3355
|
+
await new Promise((resolve, reject) => {
|
|
3356
|
+
image.onload = resolve
|
|
3357
|
+
image.onerror = () => reject(new Error('导出图片加载失败'))
|
|
3358
|
+
image.src = url
|
|
3359
|
+
})
|
|
3360
|
+
const buffer = document.createElement('canvas')
|
|
3361
|
+
buffer.width = image.width
|
|
3362
|
+
buffer.height = image.height
|
|
3363
|
+
const context = buffer.getContext('2d')
|
|
3364
|
+
context.drawImage(image, 0, 0)
|
|
3365
|
+
const data = context.getImageData(0, 0, buffer.width, buffer.height).data
|
|
3366
|
+
let red = 0
|
|
3367
|
+
let blue = 0
|
|
3368
|
+
for (let index = 0; index < data.length; index += 4) {
|
|
3369
|
+
if (data[index] > 180 && data[index + 1] < 90 && data[index + 2] < 90) {
|
|
3370
|
+
red += 1
|
|
3371
|
+
}
|
|
3372
|
+
if (data[index + 2] > 180 && data[index] < 90 && data[index + 1] < 90) {
|
|
3373
|
+
blue += 1
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
return { red, blue, width: buffer.width, height: buffer.height }
|
|
3377
|
+
}
|
|
3378
|
+
const scopeViewport = await scopeColorCounts(await scopeView.exportImage({ scope: 'viewport', scale: 1, includeLabels: false }))
|
|
3379
|
+
const scopeAll = await scopeColorCounts(await scopeView.exportImage({ scope: 'all', padding: 16, scale: 1, includeLabels: false }))
|
|
3380
|
+
const scopeAfter = scopeView.getCamera()
|
|
3381
|
+
check('exportImage scope: viewport 只含当前视口内容', scopeViewport.red > 50 && scopeViewport.blue === 0, JSON.stringify(scopeViewport))
|
|
3382
|
+
check('exportImage scope: all 临时适应全部内容(两个节点都进画面)', scopeAll.red > 50 && scopeAll.blue > 50, JSON.stringify(scopeAll))
|
|
3383
|
+
check('exportImage scope: all 出图后相机原样恢复', scopeAfter.scale === scopeCamera.scale && scopeAfter.center[0] === scopeCamera.center[0] && scopeAfter.center[1] === scopeCamera.center[1], JSON.stringify({ before: scopeCamera, after: scopeAfter }))
|
|
3384
|
+
const scopeSvg = decodeSvgUrl(await scopeView.exportImage({ type: 'svg', scope: 'all', padding: 12, includeLabels: false }))
|
|
3385
|
+
const scopeSvgAfter = scopeView.getCamera()
|
|
3386
|
+
check('exportImage scope: all 在 SVG 分支同样生效并恢复相机', (scopeSvg.match(/<circle/g) || []).length >= 2 && scopeSvgAfter.scale === scopeCamera.scale, JSON.stringify({ circles: (scopeSvg.match(/<circle/g) || []).length, camera: scopeSvgAfter }))
|
|
3387
|
+
let scopeError = ''
|
|
3388
|
+
try {
|
|
3389
|
+
await scopeView.exportImage({ scope: 'window' })
|
|
3390
|
+
}
|
|
3391
|
+
catch (error) {
|
|
3392
|
+
scopeError = String(error?.message ?? error)
|
|
3393
|
+
}
|
|
3394
|
+
check('exportImage scope 写错直接抛异常(不静默按 viewport 导)', scopeError.includes('viewport') && scopeError.includes('all'), scopeError)
|
|
3395
|
+
scopeView.destroy()
|
|
3396
|
+
scopeHost.remove()
|
|
3397
|
+
|
|
2928
3398
|
/* 适应视图的留白:padding 比画布还大时,扣完留白的可用尺寸不能被钳到 1px(那会把整幅图缩成一个点) */
|
|
2929
3399
|
const fitHost = document.createElement('div')
|
|
2930
3400
|
fitHost.style.cssText = 'width:220px;height:140px'
|
|
@@ -4294,6 +4764,37 @@
|
|
|
4294
4764
|
nwView.cancelAddChannel()
|
|
4295
4765
|
await settle(30)
|
|
4296
4766
|
check('提前结束:cancelAddChannel 收掉草稿并补一条 cancel(不新建)', nwView.getAddChannelDraft() === null && nwEvents.some(payload => payload.phase === 'cancel') && nwEvents.every(payload => payload.phase !== 'commit'), JSON.stringify(nwEvents.map(payload => payload.phase)))
|
|
4767
|
+
|
|
4768
|
+
// ⑩b 预指定起点:beginAddChannel({ from }) 直接进入「等终点」,下一次点击就是 ready
|
|
4769
|
+
nwEvents.length = 0
|
|
4770
|
+
nwSelectChanges.length = 0
|
|
4771
|
+
nwView.beginAddChannel({ from: 'na', select: true })
|
|
4772
|
+
await settle(40)
|
|
4773
|
+
const nwPresetFrom = nwView.getAddChannelDraft()
|
|
4774
|
+
check('beginAddChannel({ from }):草稿直接以该节点为起点进入 from 相位',
|
|
4775
|
+
nwPresetFrom?.phase === 'from' && nwPresetFrom.from?.id === 'na' && nwPresetFrom.to === null && nwEvents[0]?.phase === 'from',
|
|
4776
|
+
JSON.stringify({ phase: nwPresetFrom?.phase, from: nwPresetFrom?.from?.id, events: nwEvents.map(payload => payload.phase) }))
|
|
4777
|
+
check('beginAddChannel({ from, select: true }):预指定起点当场进选中集', JSON.stringify(nwView.getSelectionData().ids) === JSON.stringify(['na']), JSON.stringify(nwView.getSelectionData().ids))
|
|
4778
|
+
nwClick(nwScreenOf('nb'))
|
|
4779
|
+
await settle(60)
|
|
4780
|
+
const nwPresetReady = nwEvents.find(payload => payload.phase === 'ready')
|
|
4781
|
+
check('预指定起点后只需再选终点:一次点击得到 from=na / to=nb 的 ready',
|
|
4782
|
+
nwPresetReady?.from?.id === 'na' && nwPresetReady?.to?.id === 'nb' && nwView.getAddChannelDraft()?.phase === 'ready',
|
|
4783
|
+
JSON.stringify({ from: nwPresetReady?.from?.id, to: nwPresetReady?.to?.id, phases: nwEvents.map(payload => payload.phase) }))
|
|
4784
|
+
nwView.cancelAddChannel()
|
|
4785
|
+
await settle(20)
|
|
4786
|
+
nwView.continueAddChannel({ from: { id: 'nc' } })
|
|
4787
|
+
await settle(30)
|
|
4788
|
+
check('continueAddChannel({ from }):没有草稿时也按预指定起点重新进入', nwView.getAddChannelDraft()?.phase === 'from' && nwView.getAddChannelDraft()?.from?.id === 'nc', JSON.stringify(nwView.getAddChannelDraft()))
|
|
4789
|
+
nwView.cancelAddChannel()
|
|
4790
|
+
await settle(20)
|
|
4791
|
+
const nwWarnings = []
|
|
4792
|
+
nwView.updateOptions({ onWarning: message => nwWarnings.push(message) })
|
|
4793
|
+
nwView.beginAddChannel({ from: '不存在的起点' })
|
|
4794
|
+
await settle(20)
|
|
4795
|
+
check('beginAddChannel({ from }) 找不到节点:草稿不开始并报 warning',
|
|
4796
|
+
nwView.getAddChannelDraft() === null && nwWarnings.some(message => message.includes('找不到起点')),
|
|
4797
|
+
JSON.stringify({ draft: nwView.getAddChannelDraft(), warnings: nwWarnings }))
|
|
4297
4798
|
nwView.updateOptions({ onChannelDraft: null })
|
|
4298
4799
|
// ⑪ getChannelBetween:两个节点之间的通道读数(只读,与方案表同一份)
|
|
4299
4800
|
const cbPair = nwView.getChannelBetween('na', 'nb')
|