mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
fix(shipwrecks): small row and query fixes: era counts in one pass, pickers list only years with a wreck, no Portuguese default
- The legend counted each era twice, scanning every record eight times per row refresh; it now counts once. - Each year picker listed every year from the oldest dated wreck (well over a thousand entries); it lists the years that have a wreck, plus the chosen ends. - Labels fell back to Portuguese and links to Portuguese Wikipedia while the app ships 11 locales: labels now fall back through the app's languages after English, and a wreck with no English article links its Wikidata item, which lists the article in every language.
This commit is contained in:
1 parent
38cf1e2be5
commit
0c159186f9
5 files changed
+71
-17
No files matched your search
@@ -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
|
by era (a legend under the row): before 1900
|
||||||
(ancient wrecks dated only by period count here), 1900–1945, after 1945, and
|
(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
|
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
|
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
|
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
|
survivors first, then the sinking date or year (or the archaeological dating) and
|
||||||
|
|||||||
@@ -277,11 +277,20 @@ export function createShipwrecksLayer({
|
|||||||
picking?.unregisterPickOwner?.(SHIPWRECKS_LAYER_ID);
|
picking?.unregisterPickOwner?.(SHIPWRECKS_LAYER_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
function countOf(era) {
|
/** Wrecks shown per era, in one pass over the records. */
|
||||||
let count = 0;
|
function eraCounts() {
|
||||||
|
const counts = Object.fromEntries(SHIPWRECK_ERAS.map((era) => [era, 0]));
|
||||||
for (const record of _records.values())
|
for (const record of _records.values())
|
||||||
if (eraOf(record) === era && inPeriod(record, _range)) count++;
|
if (inPeriod(record, _range)) counts[eraOf(record)]++;
|
||||||
return count;
|
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. */
|
/** The start and end year pickers, side by side. */
|
||||||
@@ -293,9 +302,13 @@ export function createShipwrecksLayer({
|
|||||||
: range.from === range.to
|
: range.from === range.to
|
||||||
? String(range.from)
|
? String(range.from)
|
||||||
: `${range.from}–${range.to}`;
|
: `${range.from}–${range.to}`;
|
||||||
const options = [];
|
// Only years with a wreck: a picker of every year since the oldest
|
||||||
for (let year = _extent.max; year >= _extent.min; year--)
|
// dated wreck would run to well over a thousand entries.
|
||||||
options.push({ value: year, label: String(year) });
|
// 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 {
|
return {
|
||||||
range: {
|
range: {
|
||||||
id: 'period',
|
id: 'period',
|
||||||
@@ -425,16 +438,17 @@ export function createShipwrecksLayer({
|
|||||||
},
|
},
|
||||||
|
|
||||||
getRowControls() {
|
getRowControls() {
|
||||||
|
const counts = eraCounts();
|
||||||
return {
|
return {
|
||||||
chips: [],
|
chips: [],
|
||||||
...periodControls(),
|
...periodControls(),
|
||||||
// The colours are a legend: when the ship sank.
|
// The colours are a legend: when the ship sank.
|
||||||
legend: SHIPWRECK_ERAS.map((era) => ({
|
legend: SHIPWRECK_ERAS.map((era) => ({
|
||||||
label: t(ERA_LABELS[era]),
|
label: t(ERA_LABELS[era]),
|
||||||
count: countOf(era),
|
count: counts[era],
|
||||||
color: ERA_COLORS[era],
|
color: ERA_COLORS[era],
|
||||||
blurb: tn(countOf(era), '{count} wreck', '{count} wrecks', {
|
blurb: tn(counts[era], '{count} wreck', '{count} wrecks', {
|
||||||
count: formatNumber(countOf(era)),
|
count: formatNumber(counts[era]),
|
||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
info: t('EMODnet (European waters) · Wikidata (worldwide)'),
|
info: t('EMODnet (European waters) · Wikidata (worldwide)'),
|
||||||
|
|||||||
@@ -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);
|
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],
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -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.
|
* 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.
|
* 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 .
|
?item wdt:P31/wdt:P279* wd:Q852190 ; wdt:P625 ?coord ; wikibase:sitelinks ?links .
|
||||||
FILTER(?links > 0)
|
FILTER(?links > 0)
|
||||||
@@ -35,8 +35,7 @@ export const WIKIDATA_QUERY = `SELECT ?item ?itemLabel ?itemDescription ?coord ?
|
|||||||
}
|
}
|
||||||
OPTIONAL { ?item wdt:P1120 ?deaths }
|
OPTIONAL { ?item wdt:P1120 ?deaths }
|
||||||
OPTIONAL { ?enwiki schema:about ?item ; schema:isPartOf <https://en.wikipedia.org/> }
|
OPTIONAL { ?enwiki schema:about ?item ; schema:isPartOf <https://en.wikipedia.org/> }
|
||||||
OPTIONAL { ?ptwiki schema:about ?item ; schema:isPartOf <https://pt.wikipedia.org/> }
|
SERVICE wikibase:label { bd:serviceParam wikibase:language "en,mul,fr,de,es,it,pt,ru,uk,ja,zh,ar". }
|
||||||
SERVICE wikibase:label { bd:serviceParam wikibase:language "en,pt,mul". }
|
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const MAX_TEXT = 160;
|
const MAX_TEXT = 160;
|
||||||
@@ -300,9 +299,9 @@ export function normalizeWikidataResults(payload) {
|
|||||||
country: null,
|
country: null,
|
||||||
originator: null,
|
originator: null,
|
||||||
url:
|
url:
|
||||||
wikiUrl(value('enwiki')) ||
|
// English Wikipedia, else the Wikidata item, which links the
|
||||||
wikiUrl(value('ptwiki')) ||
|
// article in every language that has one.
|
||||||
`https://www.wikidata.org/wiki/${qid}`,
|
wikiUrl(value('enwiki')) || `https://www.wikidata.org/wiki/${qid}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return foldSinkingEvents([...byItem.values()]);
|
return foldSinkingEvents([...byItem.values()]);
|
||||||
|
|||||||
@@ -307,3 +307,8 @@ test('the query reads the end of service with its precision', () => {
|
|||||||
);
|
);
|
||||||
assert.doesNotMatch(WIKIDATA_QUERY, /wdt:P576/);
|
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,/);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user