Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ jobs:

- name: Test
run: yarn test

- name: Build docs
run: yarn docs:build
59 changes: 41 additions & 18 deletions docs/.vitepress/theme/components/DggsD3HierarchyDemo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const state = reactive({
selectedCell: null,
selectedRes: null,
parentCell: null,
allParentCells: [],
childrenCells: [],
neighborCells: [],
addressInfo: null,
Expand Down Expand Up @@ -107,9 +108,16 @@ function selectCell(cellId, resolution) {
state.neighborCells = []
}
try {
state.parentCell = resolution > 0 ? dggs.sequenceNumParent([cellId], resolution)[0] : null
if (resolution > 0) {
state.allParentCells = dggs.sequenceNumAllParents([cellId], resolution)[0]
state.parentCell = state.allParentCells[0] ?? null
} else {
state.allParentCells = []
state.parentCell = null
}
} catch {
state.parentCell = null
state.allParentCells = []
}
try {
state.childrenCells = dggs.sequenceNumChildren([cellId], resolution)[0]
Expand Down Expand Up @@ -144,14 +152,14 @@ function drawScene(cellId, resolution) {
const childrenGeom = dggs.sequenceNumToGridFeatureCollection(state.childrenCells, resolution + 1)

let parentGeom = null
if (state.parentCell !== null && resolution > 0) {
parentGeom = dggs.sequenceNumToGridFeatureCollection([state.parentCell], resolution - 1)
if (state.allParentCells.length > 0 && resolution > 0) {
parentGeom = dggs.sequenceNumToGridFeatureCollection(state.allParentCells, resolution - 1)
}

// Draw order: parent -> neighbors -> children -> center
const features = []
if (parentGeom) {
features.push(...parentGeom.features.map(f => ({ ...f, _type: 'parent', _res: resolution - 1 })))
features.push(...parentGeom.features.map((f, i) => ({ ...f, _type: i === 0 ? 'parent' : 'parent-secondary', _res: resolution - 1 })))
}
features.push(...neighborGeom.features.map(f => ({ ...f, _type: 'neighbor', _res: resolution })))
features.push(...childrenGeom.features.map(f => ({ ...f, _type: 'child', _res: resolution + 1 })))
Expand All @@ -177,13 +185,15 @@ function drawScene(cellId, resolution) {

const colorMap = {
parent: '#33cc33',
'parent-secondary': '#88cc88',
neighbor: '#3399ff',
center: '#ff3333',
child: '#ffcc00',
}

const opacityMap = {
parent: 0.25,
'parent-secondary': 0.15,
neighbor: 0.85,
center: 0.35,
child: 0.6,
Expand Down Expand Up @@ -218,13 +228,15 @@ function drawScene(cellId, resolution) {

// Parent outline on top
if (parentGeom) {
svg.append('polygon')
.attr('points', parentGeom.features[0].geometry.coordinates[0].map(c => proj(c)).join(' '))
.attr('fill', 'none')
.attr('stroke', '#33cc33')
.attr('stroke-width', 2.5)
.attr('stroke-dasharray', '6,3')
.attr('pointer-events', 'none')
parentGeom.features.forEach((f, i) => {
svg.append('polygon')
.attr('points', f.geometry.coordinates[0].map(c => proj(c)).join(' '))
.attr('fill', 'none')
.attr('stroke', i === 0 ? '#33cc33' : '#88cc88')
.attr('stroke-width', i === 0 ? 2.5 : 1.5)
.attr('stroke-dasharray', '6,3')
.attr('pointer-events', 'none')
})
}

// Center outline on top
Expand All @@ -236,7 +248,7 @@ function drawScene(cellId, resolution) {
.attr('pointer-events', 'none')

// Draw index labels on cells (skip parent — too large)
const labelFeatures = features.filter(f => f._type !== 'parent')
const labelFeatures = features.filter(f => f._type !== 'parent' && f._type !== 'parent-secondary')
const labelGroup = svg.append('g').attr('pointer-events', 'none')

labelGroup.selectAll('text.cell-label')
Expand Down Expand Up @@ -420,12 +432,15 @@ function loadScript(src) {

<!-- Parent -->
<div class="panel-cell">
<div class="section-title parent-title">Parent</div>
<div v-if="state.parentCell !== null" class="cell-list">
<button class="btn btn-parent" @click="goToParent">
{{ state.parentCell.toString() }}
<span class="res-badge small">res {{ state.selectedRes - 1 }}</span>
</button>
<div class="section-title parent-title">Parents ({{ state.allParentCells.length }})</div>
<div v-if="state.allParentCells.length" class="cell-list">
<button
v-for="(p, i) in state.allParentCells"
:key="i"
class="btn btn-parent"
:class="{ 'btn-primary-parent': i === 0 }"
@click="navigateTo(p, state.selectedRes - 1)"
>{{ p.toString() }}<span v-if="i === 0" class="primary-tag">primary</span></button>
</div>
<div v-else class="muted">Root</div>
</div>
Expand Down Expand Up @@ -646,6 +661,14 @@ function loadScript(src) {
color: var(--vp-c-text-2);
}
.btn-parent { border-left: 3px solid #33cc33; }
.btn-primary-parent { font-weight: 600; }
.primary-tag {
font-size: 9px;
color: #1a8a1a;
margin-left: 4px;
text-transform: uppercase;
font-weight: 600;
}
.btn-neighbor { border-left: 3px solid #3399ff; }
.btn-child { border-left: 3px solid #ffcc00; }
.muted {
Expand Down
83 changes: 61 additions & 22 deletions docs/.vitepress/theme/components/DggsGlobe.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const selectedCellId = ref(null)
const selectedCellRes = ref(null)
const hierarchyInfo = reactive({
parent: null,
allParents: [],
children: [],
neighbors: [],
})
Expand Down Expand Up @@ -209,6 +210,7 @@ function generateGrid() {
selectedCellId.value = null
selectedCellRes.value = null
hierarchyInfo.parent = null
hierarchyInfo.allParents = []
hierarchyInfo.children = []
hierarchyInfo.neighbors = []
removeHierarchyMapLayers()
Expand Down Expand Up @@ -370,8 +372,14 @@ function selectCell(cellId, resolution) {
hierarchyInfo.neighbors = webdggrid.sequenceNumNeighbors([seqnum], resolution)[0]
} catch { hierarchyInfo.neighbors = [] }
try {
hierarchyInfo.parent = resolution > 0 ? webdggrid.sequenceNumParent([seqnum], resolution)[0] : null
} catch { hierarchyInfo.parent = null }
if (resolution > 0) {
hierarchyInfo.allParents = webdggrid.sequenceNumAllParents([seqnum], resolution)[0]
hierarchyInfo.parent = hierarchyInfo.allParents[0] ?? null
} else {
hierarchyInfo.allParents = []
hierarchyInfo.parent = null
}
} catch { hierarchyInfo.parent = null; hierarchyInfo.allParents = [] }
try {
hierarchyInfo.children = webdggrid.sequenceNumChildren([seqnum], resolution)[0]
} catch { hierarchyInfo.children = [] }
Expand All @@ -383,6 +391,7 @@ function clearSelection() {
selectedCellId.value = null
selectedCellRes.value = null
hierarchyInfo.parent = null
hierarchyInfo.allParents = []
hierarchyInfo.children = []
hierarchyInfo.neighbors = []
removeHierarchyMapLayers()
Expand Down Expand Up @@ -424,35 +433,54 @@ function updateHierarchyLayers(seqnum, resolution) {
// Remove previous MapLibre hierarchy layers
removeHierarchyMapLayers()

// --- Parent polygon ---
if (hierarchyInfo.parent !== null && resolution > 0) {
// --- Parent polygons (all touching parents) ---
if (hierarchyInfo.allParents.length > 0 && resolution > 0) {
try {
const parentFc = sanitizeFc(webdggrid.sequenceNumToGridFeatureCollection([hierarchyInfo.parent], resolution - 1))
console.log('Parent cell geometry:', JSON.stringify(parentFc.features[0]?.geometry))
const parentFc = sanitizeFc(webdggrid.sequenceNumToGridFeatureCollection(hierarchyInfo.allParents, resolution - 1))
// Tag primary vs secondary parents
parentFc.features.forEach((f, i) => {
f.properties._primary = i === 0
})
map.addSource('hier-parent', { type: 'geojson', data: parentFc })
map.addLayer({ id: 'hier-parent-fill', type: 'fill', source: 'hier-parent', paint: { 'fill-color': '#33cc33', 'fill-opacity': 0.15 } })
map.addLayer({ id: 'hier-parent-line', type: 'line', source: 'hier-parent', paint: { 'line-color': '#33cc33', 'line-width': 3, 'line-dasharray': [3, 2] } })

// Parent label — offset upward so it doesn't overlap selected cell
const geo = webdggrid.sequenceNumToGeo([hierarchyInfo.parent], resolution - 1)[0]
const parentLabelFc = { type: 'FeatureCollection', features: [{
type: 'Feature',
geometry: { type: 'Point', coordinates: [geo[0], geo[1]] },
properties: { label: getCellIndexLabel(hierarchyInfo.parent, resolution - 1) },
}] }
map.addSource('hier-labels-parent', { type: 'geojson', data: parentLabelFc })
map.addLayer({ id: 'hier-parent-fill', type: 'fill', source: 'hier-parent', paint: {
'fill-color': ['case', ['get', '_primary'], '#22cc55', '#66dd88'],
'fill-opacity': ['case', ['get', '_primary'], 0.35, 0.25],
} })
map.addLayer({ id: 'hier-parent-line', type: 'line', source: 'hier-parent', paint: {
'line-color': ['case', ['get', '_primary'], '#11aa33', '#44bb66'],
'line-width': ['case', ['get', '_primary'], 3.5, 2.5],
'line-dasharray': [3, 2],
} })

// Parent labels
const parentLabelFeatures = hierarchyInfo.allParents.map((p, i) => {
const geo = webdggrid.sequenceNumToGeo([p], resolution - 1)[0]
return {
type: 'Feature',
geometry: { type: 'Point', coordinates: [geo[0], geo[1]] },
properties: {
label: getCellIndexLabel(p, resolution - 1),
primary: i === 0,
},
}
})
map.addSource('hier-labels-parent', { type: 'geojson', data: { type: 'FeatureCollection', features: parentLabelFeatures } })
map.addLayer({
id: 'hier-labels-parent-text',
type: 'symbol',
source: 'hier-labels-parent',
layout: {
'text-field': ['get', 'label'],
'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],
'text-size': 13,
'text-size': ['case', ['get', 'primary'], 13, 11],
'text-offset': [0, -2],
'text-allow-overlap': false,
},
paint: { 'text-color': '#1a8a1a', 'text-halo-color': 'rgba(255,255,255,0.95)', 'text-halo-width': 2 },
paint: {
'text-color': ['case', ['get', 'primary'], '#1a8a1a', '#5a9a5a'],
'text-halo-color': 'rgba(255,255,255,0.95)',
'text-halo-width': 2,
},
})
} catch (err) { console.error('Parent geometry error:', err) }
}
Expand Down Expand Up @@ -814,11 +842,13 @@ defineExpose({ getMap: () => map })
<button class="hier-clear-btn" @click="clearSelection">Clear</button>
</div>

<div v-if="hierarchyInfo.parent !== null" class="hier-group">
<div v-if="hierarchyInfo.allParents.length" class="hier-group">
<div class="hier-label hier-parent-label">
Parent (res {{ selectedCellRes - 1 }}<template v-if="ctrlMixedAperture && showAperture && ctrlApertureSeq[selectedCellRes - 2]">, a{{ ctrlApertureSeq[selectedCellRes - 2] }}</template>)
Parents ({{ hierarchyInfo.allParents.length }}, res {{ selectedCellRes - 1 }}<template v-if="ctrlMixedAperture && showAperture && ctrlApertureSeq[selectedCellRes - 2]">, a{{ ctrlApertureSeq[selectedCellRes - 2] }}</template>)
</div>
<div class="hier-chips">
<span v-for="(p, i) in hierarchyInfo.allParents" :key="i" class="hier-chip hier-chip-parent" :class="{ 'hier-chip-primary': i === 0 }">{{ p.toString() }}<span v-if="i === 0" class="hier-primary-badge">primary</span></span>
</div>
<div class="hier-value">{{ hierarchyInfo.parent.toString() }}</div>
</div>

<div v-if="hierarchyInfo.children.length" class="hier-group">
Expand Down Expand Up @@ -1136,6 +1166,15 @@ defineExpose({ getMap: () => map })
color: var(--vp-c-text-1);
word-break: break-all;
}
.hier-chip-parent { border-left: 2px solid #33cc33; }
.hier-chip-primary { font-weight: 600; }
.hier-primary-badge {
font-size: 8px;
font-weight: 600;
color: #1a8a1a;
margin-left: 3px;
text-transform: uppercase;
}
.hier-chip-child { border-left: 2px solid #ffcc00; }
.hier-chip-neighbor { border-left: 2px solid #3399ff; }
.hier-hint {
Expand Down
Loading
Loading