Mount the ring legend, and dim the rings it is not talking about

This commit is contained in:
Doxa Maps 2026-09-28 16:09:15 +00:00
parent f49c81cec6
commit 04ac124cb7

View file

@ -24,10 +24,14 @@ import { useBoardState } from '../useBoard.js'
import { useGiT } from '../useGiT.js'
import { loadNearRows, indexFor } from '../nearRows.js'
import { layoutCenteredSet, ringTally, inwardArrow, ringRadii } from '../centeredSet.js'
import GiRingLegend from './GiRingLegend.vue'
const T = useGiT()
const board = useBoardState()
const near = ref(null)
// Which ring the reader has asked about. null = none; the chart dims the others rather than
// hiding them, because a ring with no dots left would read as a ring with no groups.
const activeRing = ref(null)
const failed = ref('')
onMounted(async () => {
@ -55,8 +59,14 @@ const prepared = computed(() => {
const layout = computed(() => layoutCenteredSet(prepared.value.groups, { innerR: 30, ringGap: 22 }))
const tally = computed(() => ringTally(layout.value))
const radii = computed(() => ringRadii({ innerR: 30, ringGap: 22 }))
// The arrow carries its group's ring so the dimming can tell them apart. inwardArrow returns
// geometry and the neighbour it names, not the ring — reading a.ring off it would have been
// undefined for every arrow, and `undefined !== activeRing` is true, so selecting any ring
// would have dimmed EVERY arrow rather than all but one. Attached here, where the group is
// still in hand, instead of widening the shared module regions is promoting to library.
const arrows = computed(() => layout.value.placed
.map((p) => inwardArrow(p, { innerR: 30 })).filter(Boolean).slice(0, 400))
.map((p) => { const a = inwardArrow(p, { innerR: 30 }); return a && { ...a, ring: p.ring } })
.filter(Boolean).slice(0, 400))
const fmt = (n) => (Number(n) || 0).toLocaleString()
useShadowStyles(
@ -64,6 +74,7 @@ useShadowStyles(
'.gcs svg{max-width:100%;max-height:100%}' +
'.gcs-ring{fill:none;stroke:#d1d4cd;stroke-width:1}' +
'.gcs-arrow{stroke:#92b195;stroke-width:.6;opacity:.5}' +
'.gcs-dim{opacity:.12}' +
'.gcs-dot{fill:#6d766e}.gcs-cross{stroke:#3b463d;stroke-width:3;stroke-linecap:square}' +
'.gcs-cap{margin-block-start:8px;color:#3b463d;font-size:13px;text-align:center;max-width:52ch}' +
'.gcs-warn{color:#6d766e;font-size:12px;margin-block-start:4px;max-width:52ch;text-align:center}',
@ -81,8 +92,12 @@ useShadowStyles(
<svg viewBox="-300 -300 600 600" role="img"
:aria-label="T('gospelInternet.centered.aria', 'Centered set: unreached groups placed by closeness to a reached neighbour, arrows pointing inward')">
<circle v-for="r in radii" :key="r.ring" class="gcs-ring" cx="0" cy="0" :r="r.r" />
<line v-for="(a, i) in arrows" :key="i" class="gcs-arrow" :x1="a.x1" :y1="a.y1" :x2="a.x2" :y2="a.y2" />
<circle v-for="p in layout.placed" :key="p.id" class="gcs-dot" :cx="p.x" :cy="p.y" r="1.8" />
<line v-for="(a, i) in arrows" :key="i" class="gcs-arrow"
:class="{ 'gcs-dim': activeRing != null && a.ring !== activeRing }"
:x1="a.x1" :y1="a.y1" :x2="a.x2" :y2="a.y2" />
<circle v-for="p in layout.placed" :key="p.id" class="gcs-dot"
:class="{ 'gcs-dim': activeRing != null && p.ring !== activeRing }"
:cx="p.x" :cy="p.y" r="1.8" />
<line class="gcs-cross" x1="0" y1="-13" x2="0" y2="13" />
<line class="gcs-cross" x1="-9" y1="-4" x2="9" y2="-4" />
</svg>
@ -90,6 +105,8 @@ useShadowStyles(
{{ T('gospelInternet.centered.caption', 'Each group sits at its distance from a reached neighbour; arrows point at the one that places it.') }}
<b dir="ltr">{{ fmt(tally.placed) }}</b>
</p>
<GiRingLegend :groups="prepared.groups" :unmatched="prepared.unmatched"
:active-ring="activeRing" @select="activeRing = $event" />
<p v-if="tally.centre || tally.unplaced || prepared.unmatched" class="gcs-warn">
<span v-if="tally.centre">{{ T('gospelInternet.centered.reached', 'Already reached, so at the centre:') }} <b dir="ltr">{{ fmt(tally.centre) }}</b>. </span>
<span v-if="tally.unplaced">{{ T('gospelInternet.centered.unplaced', 'No relation found, so not placed:') }} <b dir="ltr">{{ fmt(tally.unplaced) }}</b>. </span>