`
: nothing
}
${this.chartViewport.renderChartViewport(this.renderMap(lines, bd), this.t('Astrocartography'), this.translator)}
${this.renderLegend(lines)}
${
// The map and its key are the deliverable here; the summary is prose
// about what the map shows.
data.summary && !this.hideReadings
? html`
`;
}
private renderMap(
lines: LineSet[],
bd: AstrocartographyResponse['birthDetails'],
) {
return html``;
}
private renderGraticule() {
const meridians = [];
for (let lon = -150; lon <= 150; lon += 30) {
const x = lonToX(lon);
const axis = lon === 0;
meridians.push(
svg``,
);
meridians.push(
svg`${formatLon(lon)}`,
);
}
const parallels = [];
for (let lat = -60; lat <= 60; lat += 30) {
const y = latToY(lat);
const axis = lat === 0;
parallels.push(
svg``,
);
parallels.push(
svg`${formatLat(lat)}`,
);
}
// Tropics and polar circles as dashed climate-band references.
const refs = [TROPIC, -TROPIC, POLAR, -POLAR].map(
(lat) =>
svg``,
);
return svg`${meridians}${parallels}${refs}`;
}
/**
* Every body's four lines, with the glyph that names each line fanned along
* its edge so two bodies within a few degrees of longitude never print one
* glyph over the other. Longitude wraps like a wheel, so the same fan the
* wheels use spaces the glyphs; the LINE stays at its true longitude and the
* colour ties a displaced glyph back to it.
*/
private renderBodyLines(lines: LineSet[]) {
const bodies = lines.map((line, index) => {
const name = display(line, 'planet');
return {
line,
name,
color: planetColor(line.planet, index),
// Response symbol first, then the shared table, then the full name. Never a
// truncation: `North Node.slice(0, 2)` labelled a whole map line "No".
glyph: line.symbol || planetGlyph(line.planet) || name,
};
});
type Body = (typeof bodies)[number];
const nearEquator = (points: GeoPoint[]): GeoPoint =>
points.reduce(
(best, p) =>
Math.abs(p.latitude) < Math.abs(best.latitude) ? p : best,
points[0] ?? { latitude: 0, longitude: 0 },
);
// The label longitude for each line of each body, fanned per edge.
const fanned = (longitudeOf: (b: Body) => number) =>
new Map(
fanOut(bodies, (b) => longitudeOf(b) + 180, GLYPH_SEPARATION).map(
({ item, displayLongitude }) => [item, displayLongitude - 180],
),
);
const mcAt = fanned((b) => b.line.mc.longitude);
const icAt = fanned((b) => b.line.ic.longitude);
const ascAt = fanned(
(b) => nearEquator(b.line.ascendant.points ?? []).longitude,
);
const dscAt = fanned(
(b) => nearEquator(b.line.descendant.points ?? []).longitude,
);
return bodies.map((b) => {
const { line, name, color, glyph } = b;
const at = (m: Map) => m.get(b) ?? 0;
return svg`${[
this.renderMeridian(
line.mc.longitude,
at(mcAt),
color,
glyph,
name,
'mc',
false,
),
this.renderMeridian(
line.ic.longitude,
at(icAt),
color,
glyph,
name,
'ic',
true,
),
this.renderCurve(
line.ascendant.points,
{
longitude: at(ascAt),
latitude: nearEquator(line.ascendant.points ?? []).latitude,
},
color,
glyph,
name,
'ascendant',
false,
),
this.renderCurve(
line.descendant.points,
{
longitude: at(dscAt),
latitude: nearEquator(line.descendant.points ?? []).latitude,
},
color,
glyph,
name,
'descendant',
true,
),
]}`;
});
}
private renderMeridian(
lon: number,
labelLon: number,
color: string,
glyph: string,
planet: string,
angle: string,
dashed: boolean,
) {
const x = lonToX(lon);
// MC label rides the top edge, IC the bottom, so the two meridians of one
// body never stack their glyphs at the same point; the IC row sits above
// the longitude labels along the bottom edge.
const labelY = angle === 'ic' ? H - 16 : 9;
return svg`${this.t('{{planet}} {{angle}} line', { planet, angle: this.t(ANGLE_LABEL[angle] ?? 'MC') })}${glyph}`;
}
private renderCurve(
points: GeoPoint[],
label: GeoPoint,
color: string,
glyph: string,
planet: string,
angle: string,
dashed: boolean,
) {
const segments = toSegments(points ?? []);
if (segments.length === 0) return nothing;
// Labelled at the sample nearest the equator, the most visible band.
return svg`
${segments.map(
(pts) =>
svg`${this.t('{{planet}} {{angle}} line', { planet, angle: this.t(ANGLE_LABEL[angle] ?? 'MC') })}`,
)}
${glyph}`;
}
private renderLegend(lines: LineSet[]) {
if (lines.length === 0) return nothing;
return html`
${lines.map((l, i) => {
const color = planetColor(l.planet, i);
return html`
${l.symbol ? html`${l.symbol} ` : nothing}${display(l, 'planet')}
`;
})}
${this.t('Solid lines are the Ascendant and Midheaven, dashed are the Descendant and IC.')}
`;
}
/**
* The written read of each planetary line. Prose end to end, and the lines it
* describes are already drawn and keyed on the map above, so `hide-readings`
* takes the section whole.
*/
private renderInterpretations(lines: LineSet[]) {
if (lines.length === 0 || this.hideReadings) return nothing;
return html`