();
for (const c of cells ?? []) {
if (!c.devataName || c.square == null) continue;
held.set(c.devataName, [...(held.get(c.devataName) ?? []), c.square]);
}
if (held.size === 0) return nothing;
return html`
${this.t('Devata')}
${[...held].map(
([name, squares]) => html`-
${name}
${squares
.sort((a, b) => a - b)
.map((n) => formatInteger(locale, n))
.join(', ')}
`,
)}
`;
}
/**
* What the fills mean.
*
* @remarks
* The outside-the-plot key is drawn only when a square actually falls outside one, because a
* legend entry for a fill nothing on the drawing carries reads as a missing feature.
*/
private renderLegend(
entrance: Entrance | undefined,
mandala: Mandala | undefined,
) {
if (entrance) {
return html`
${this.t('Door pada')}
`;
}
const hasOutside = (mandala?.cells ?? []).some(
(c) => c.withinPlot === false,
);
return html`
${this.t('Brahmasthan')}
${
hasOutside
? html`${this.t('Outside the plot')}`
: nothing
}
`;
}
/** The numbers a reader takes off the projection or the entrance read. */
private renderFacts(
locale: string | undefined,
d: VastuData,
mandala: Mandala | undefined,
entrance: Entrance | undefined,
) {
const gridValue = d.conventions?.grid;
return html`
${
gridValue
? html`${displayField(locale, 'grid')}${displayOption(locale, 'grid', gridValue)}`
: nothing
}
${
entrance?.pada != null
? html`${this.t('Pada')}${formatInteger(locale, entrance.pada)}`
: nothing
}
${
entrance?.side
? html`${this.t('Side')}${displayOption(locale, 'facing', entrance.side)}`
: nothing
}
${
entrance?.startCorner
? html`${this.t('Counted from')}${displayOption(locale, 'facing', entrance.startCorner)}
${
entrance.ordinalOnSide != null
? html`· ${formatInteger(locale, entrance.ordinalOnSide)}`
: nothing
}`
: nothing
}
${
entrance?.devata?.name
? html`${this.t('Devata')}${entrance.devata.name}
${
entrance.devata.padaCount != null
? html`· ${this.t('Pada')} ${formatInteger(locale, entrance.devata.padaCount)}`
: nothing
}`
: nothing
}
${
entrance?.recommendedPadas?.length
? html`${this.t('Recommended padas')}${entrance.recommendedPadas.map((p) => formatInteger(locale, p)).join(', ')}`
: nothing
}
${
// Both figures are areas in the SQUARE of the plot unit, so the label has
// to say so or 300 beside 4.17 reads as two counts.
mandala?.brahmasthan?.area != null
? html`${this.t('Brahmasthan area')}${this.areaLabel(locale, mandala.brahmasthan.area, mandala.conventions?.unit)}`
: nothing
}
${
mandala?.marma?.areaEach != null
? html`${this.t('Area of one marma')}${this.areaLabel(locale, mandala.marma.areaEach, mandala.conventions?.unit)}`
: nothing
}
${
mandala?.atimarma?.length
? html`${this.t('Crossing points')}${mandala.atimarma.map((s) => formatInteger(locale, s)).join(', ')}`
: nothing
}
`;
}
/** Every verse the drawing rests on, or the convention where no verse states the rule. */
private renderSources(sources: readonly Citation[]) {
if (sources.length === 0) return nothing;
return html`
${sources.map((s) => html`- ${citationLine(s)}
`)}
`;
}
/**
* An area in the square of the plot unit: the number plus the standard symbol for the wire unit
* the response echoed, with the catalogue's own word for that unit as the `title`, one hover
* away and translated.
*
* @remarks
* The symbol is fixed on the WIRE value, not read out of the field-label catalogue: `ft²` and
* `m²` are what every practitioner writes whatever language the card is in, and hanging a
* superscript off a translator's word is not that convention. `conventions.unit` is absent on a
* payload from before the API started echoing it, and a default here would be a guess about
* which unit the reader actually sent, so the bare number prints on its own with no title.
*/
private areaLabel(
locale: string | undefined,
value: number,
unit: string | undefined,
) {
const formatted = formatNumber(locale, value, 2);
if (!unit) return formatted;
const symbol = AREA_SYMBOL[unit] ?? `${unit}²`;
return html`${formatted} ${symbol}`;
}
/** The name of one ring, falling back to the wire id for a class the API might add. */
private ringLabel(ring: string): string {
const source = RING_LABEL[ring];
return source ? this.t(source) : humanize(ring);
}
/** How the stated effect reads, falling back to the wire value. */
private verdictLabel(verdict: string): string {
const source = VERDICT_LABEL[verdict];
return source ? this.t(source) : humanize(verdict);
}
}
/**
* A bare lattice for a response that carries no square centres, sized from the grid it names.
*
* @remarks
* The entrance read returns one square and the division it was read on, so the drawing is in pada
* units from the start and the plot mapping is the identity. Nothing here derives a square position
* the response did not give.
*/
function unitGrid(grid: string | undefined): PadaGrid | undefined {
const size = Number.parseInt(grid ?? '', 10);
const side = Math.round(Math.sqrt(size));
if (!Number.isFinite(side) || side < 2 || side * side !== size)
return undefined;
return { size: side, originX: 0, cellW: 1, originY: 0, cellH: 1 };
}
declare global {
interface HTMLElementTagNameMap {
'roxy-vastu-mandala': RoxyVastuMandala;
}
}