diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md
index 6e3797168..996af1fcb 100644
--- a/docs/CURRENT-STATE.md
+++ b/docs/CURRENT-STATE.md
@@ -178,7 +178,7 @@ cull GhostMaps uses, sized by deaths (a bigger toll draws on top) and coloured
by era (a legend under the row): before 1900
(ancient wrecks dated only by period count here), 1900–1945, after 1945, and
date unknown. A period filter under the row picks a start year and an end year
-side by side (two pickers over the years in the data; a reversed pair is
+side by side (two pickers over the years that have a wreck; a reversed pair is
stored swapped); undated wrecks show only when every year
is selected. The filter is session-only (not in the share link). Clicking a marker opens a `stateless` card: deaths and
survivors first, then the sinking date or year (or the archaeological dating) and
diff --git a/src/layers/shipwrecks/index.js b/src/layers/shipwrecks/index.js
index 285881743..f2451d7bd 100644
--- a/src/layers/shipwrecks/index.js
+++ b/src/layers/shipwrecks/index.js
@@ -277,11 +277,20 @@ export function createShipwrecksLayer({
picking?.unregisterPickOwner?.(SHIPWRECKS_LAYER_ID);
}
- function countOf(era) {
- let count = 0;
+ /** Wrecks shown per era, in one pass over the records. */
+ function eraCounts() {
+ const counts = Object.fromEntries(SHIPWRECK_ERAS.map((era) => [era, 0]));
for (const record of _records.values())
- if (eraOf(record) === era && inPeriod(record, _range)) count++;
- return count;
+ if (inPeriod(record, _range)) counts[eraOf(record)]++;
+ return counts;
+ }
+
+ /** The years that have a dated wreck, plus `extra` ones, newest first. */
+ function yearsInData(extra = []) {
+ const years = new Set(extra);
+ for (const record of _records.values())
+ if (Number.isInteger(record.year)) years.add(record.year);
+ return [...years].sort((a, b) => b - a);
}
/** The start and end year pickers, side by side. */
@@ -293,9 +302,13 @@ export function createShipwrecksLayer({
: range.from === range.to
? String(range.from)
: `${range.from}–${range.to}`;
- const options = [];
- for (let year = _extent.max; year >= _extent.min; year--)
- options.push({ value: year, label: String(year) });
+ // Only years with a wreck: a picker of every year since the oldest
+ // dated wreck would run to well over a thousand entries.
+ // The chosen ends stay listed even when no wreck sank that year.
+ const options = yearsInData([range.from, range.to]).map((year) => ({
+ value: year,
+ label: String(year),
+ }));
return {
range: {
id: 'period',
@@ -425,16 +438,17 @@ export function createShipwrecksLayer({
},
getRowControls() {
+ const counts = eraCounts();
return {
chips: [],
...periodControls(),
// The colours are a legend: when the ship sank.
legend: SHIPWRECK_ERAS.map((era) => ({
label: t(ERA_LABELS[era]),
- count: countOf(era),
+ count: counts[era],
color: ERA_COLORS[era],
- blurb: tn(countOf(era), '{count} wreck', '{count} wrecks', {
- count: formatNumber(countOf(era)),
+ blurb: tn(counts[era], '{count} wreck', '{count} wrecks', {
+ count: formatNumber(counts[era]),
}),
})),
info: t('EMODnet (European waters) · Wikidata (worldwide)'),
diff --git a/src/layers/shipwrecks/index.test.mjs b/src/layers/shipwrecks/index.test.mjs
index 2e42dfd7f..d42aedaf1 100644
--- a/src/layers/shipwrecks/index.test.mjs
+++ b/src/layers/shipwrecks/index.test.mjs
@@ -208,3 +208,39 @@ test('a click opens the card; a period that excludes the wreck closes it', async
);
assert.equal(h.layer.getStats().count, 1);
});
+
+test('the year pickers list only years with a wreck, and each era is counted', async () => {
+ const h = harness({
+ snapshots: [
+ snapshot([
+ wreck('a', { year: 300 }),
+ wreck('b', { year: 1912 }),
+ wreck('c', { year: 1912 }),
+ wreck('d', { year: 1990 }),
+ wreck('e', { year: null }),
+ ]),
+ ],
+ });
+ h.layer.enable();
+ await h.layer.update();
+ let controls = h.layer.getRowControls();
+ assert.deepEqual(
+ controls.range.options.map((option) => option.value),
+ [1990, 1912, 300],
+ );
+ assert.deepEqual(
+ controls.legend.map((era) => era.count),
+ [1, 2, 1, 1],
+ );
+ h.layer.setParams({ from: 1900, to: 1950 });
+ controls = h.layer.getRowControls();
+ assert.deepEqual(
+ controls.range.options.map((option) => option.value),
+ [1990, 1950, 1912, 1900, 300],
+ 'the chosen ends are listed too',
+ );
+ assert.deepEqual(
+ controls.legend.map((era) => era.count),
+ [0, 2, 0, 0],
+ );
+});
diff --git a/src/layers/shipwrecks/records.js b/src/layers/shipwrecks/records.js
index e52e67702..8d362e7a1 100644
--- a/src/layers/shipwrecks/records.js
+++ b/src/layers/shipwrecks/records.js
@@ -20,7 +20,7 @@ export const SHIPWRECK_SOURCES = Object.freeze(['emodnet', 'wikidata']);
* come with their precision so a month-precise date is not shown as the 1st.
* Only ship and event facts are read: no items about people.
*/
-export const WIKIDATA_QUERY = `SELECT ?item ?itemLabel ?itemDescription ?coord ?sunk ?sunkPrecision ?end ?endPrecision ?deaths ?survivors ?typeLabel ?enwiki ?ptwiki ?event WHERE {
+export const WIKIDATA_QUERY = `SELECT ?item ?itemLabel ?itemDescription ?coord ?sunk ?sunkPrecision ?end ?endPrecision ?deaths ?survivors ?typeLabel ?enwiki ?event WHERE {
{
?item wdt:P31/wdt:P279* wd:Q852190 ; wdt:P625 ?coord ; wikibase:sitelinks ?links .
FILTER(?links > 0)
@@ -35,8 +35,7 @@ export const WIKIDATA_QUERY = `SELECT ?item ?itemLabel ?itemDescription ?coord ?
}
OPTIONAL { ?item wdt:P1120 ?deaths }
OPTIONAL { ?enwiki schema:about ?item ; schema:isPartOf }
- OPTIONAL { ?ptwiki schema:about ?item ; schema:isPartOf }
- SERVICE wikibase:label { bd:serviceParam wikibase:language "en,pt,mul". }
+ SERVICE wikibase:label { bd:serviceParam wikibase:language "en,mul,fr,de,es,it,pt,ru,uk,ja,zh,ar". }
}`;
const MAX_TEXT = 160;
@@ -300,9 +299,9 @@ export function normalizeWikidataResults(payload) {
country: null,
originator: null,
url:
- wikiUrl(value('enwiki')) ||
- wikiUrl(value('ptwiki')) ||
- `https://www.wikidata.org/wiki/${qid}`,
+ // English Wikipedia, else the Wikidata item, which links the
+ // article in every language that has one.
+ wikiUrl(value('enwiki')) || `https://www.wikidata.org/wiki/${qid}`,
});
}
return foldSinkingEvents([...byItem.values()]);
diff --git a/src/layers/shipwrecks/records.test.mjs b/src/layers/shipwrecks/records.test.mjs
index 2033e391f..4ce5514e2 100644
--- a/src/layers/shipwrecks/records.test.mjs
+++ b/src/layers/shipwrecks/records.test.mjs
@@ -307,3 +307,8 @@ test('the query reads the end of service with its precision', () => {
);
assert.doesNotMatch(WIKIDATA_QUERY, /wdt:P576/);
});
+
+test('labels and links do not default to one other language', () => {
+ assert.doesNotMatch(WIKIDATA_QUERY, /ptwiki|pt\.wikipedia/);
+ assert.match(WIKIDATA_QUERY, /wikibase:language "en,mul,/);
+});