Skip to content

Repository files navigation

DRE Visualizations

An Omeka S module that adds interactive visualizations to resource pages using ECharts and MapLibre GL.

Operational documentation: administration, architecture, security and privacy, roadmap status, release procedure, security reporting, and third-party notices.

Features

Knowledge Graph (Item Pages)

A live force-directed network the reader can handle directly: drag a node and its neighbourhood relaxes around it, and the node keeps the position you gave it. For items with rich outgoing links (research items, projects, people), shows linked persons, subjects, locations, and other items sharing the same properties. For items that are primarily linked TO (subjects, languages, locations, genres), shows the research items that reference them.

Rendered on a canvas by d3-force (graph-canvas.js + graph-force.js), not ECharts — the previous graph/force series ran its layout to a frozen state with no collision pass, so nodes overlapped, only the centre could carry a label, and a drag moved one node through a static picture. The renderer is generic; only knowledge-graph*.js knows about research items.

  • Draggable, with live relaxation — drag to curate the layout; a dragged node is pinned (a ring marks it), Alt-click releases it, and "Release all" clears every pin
  • Not just a star — the precompute also draws the statements between the item's neighbours (a person who is a member of its project, a project carrying its subjects), so the picture is a network rather than hub-and-spokes. These cross edges are drawn thinner so the item's own statements still read as the primary structure
  • Labels placed by collision test — as many as fit, prioritised by centrality; zoom in and more appear. Toolbar toggles force all entity labels, and name every connection rather than only the selected entity's
  • Consistent colours across every network — an entity type keeps one hue on this graph, on the Entity Network and on the contributor/affiliation networks, resolved from a shared registry (ns.entityColor) rather than from whichever slot a category happened to land in
  • Hover an entity to isolate its connections — its neighbours and their edges brighten while everything else fades
  • Community colours — a coloured halo rings entities that co-occur, so connected clusters read at a glance (toggle in the toolbar); the busiest (hub) entities are drawn larger
  • Clickable legend below the graph toggles whole entity types in and out
  • Pan by dragging the background; zoom with Ctrl/⌘ + scroll, a double-click, pinch, or +/. A plain scroll still scrolls the page — the graph never hijacks the wheel mid-article
  • Keyboard + screen reader — the canvas is focusable: ←/→ walk every entity, ↑/↓ walk the focused entity's own neighbours, Enter opens one, and each move is announced. A Relationships as a list disclosure gives the same content as real links (also handy for Ctrl+F)
  • Freeze the layout when you like it; Reset view re-fits; Save as image exports a 2× PNG with every label and a legend
  • Deterministic — the same item lays out identically on every load, so a graph you share is the graph you return to
  • Click selects, it does not navigate. Clicking an entity anchors it — neighbourhood highlighted, its connections named on their edges — and opens a card with its type, its stats, the kinds of relationship it participates in, and an explicit link to its record. Clicking it again, clicking the background, or Escape clears it. Navigation is always a second, deliberate act on a real link, which is what makes the graph usable on a touchscreen (there is no hover to fall back on) and stops the obvious "tell me more" gesture from throwing the graph away
  • Fullscreen mode (Escape to exit); inside it a plain scroll zooms
  • Node caps (220 direct + 90 shared + 40 reverse + 60 referencing) prevent overload on highly-connected entities; the "Max. neighbours" slider trims further
  • Loads ~17 KiB of d3-force rather than the 1.1 MiB ECharts bundle, so an item page whose only visualization block is the graph is much lighter
  • Respects prefers-reduced-motion — the layout settles before first paint and never animates on its own
  • Collapsible section — a native <details> disclosure mirroring the DRE theme's Linked resources accordion (expanded by default; the graph re-fits on expand)

Item Location Map (Item Pages)

Automatically rendered below the knowledge graph when an item has geographic data. Shows distinct markers for:

  • Origin (teal) — where the resource was produced/fieldwork conducted (dcterms:spatial)
  • Current location (orange) — where the resource is currently held (dcterms:provenance)

Coordinates are resolved from linked Location or Institution items with geo:lat/geo:long — so an item held at a geocoded institution (an archive, museum, or university) now shows a current-location marker too, not just origins.

Sibling-items Sparkline (Item Pages)

For a research item that belongs to a project, a compact sparkline of the project's items-per-year with the current item's year marked — context for where the item sits in its project's timeline. A resource page block (Admin > Sites > [site] > Theme > Configure resource pages); it resolves the parent project + the project's precomputed timeline client-side and hides itself when not applicable.

Visualizations Dashboard (Item Pages)

Contextual charts adapted per entity type. All chart elements are clickable, linking to the corresponding Omeka S item page. ~2,500 dashboards pre-computed across all entity types. The whole dashboard sits in a collapsible <details> header (matching the Knowledge Graph and the theme's Linked resources accordion); charts re-fit when a collapsed section is re-opened.

Charts by Entity Type

Chart Sections Projects People Organisations Locations Subjects Languages Types Genres
Stacked Timeline x x
Language Timeline x x
Timeline x x x x x x x x x
Gantt (project timelines) x
Beeswarm (projects by year) x
Resource Types (pie) x x x x x x x x
Languages x x x x x x x x
Contributor Roles x x x
Heatmap (type x language) x x
Subjects (word cloud) x x x x x x x
Subject Trends over Time x x
Sunburst (type > language > subject) x x
Treemap (project x type) x x
Geographic Origins & Current Locations (map) x x x x x x
Origin > Current Location (flow map) x x x
Items by Country (choropleth) x x
Self-location MiniMap x x
Affiliation Map x x
Profile (radar) x x x
Subject Co-occurrence (chord) x x
Collaboration Network x
Contributor Network x x x
Affiliation Network x
Top Associated Persons x x x x x x x x
Co-authors x
Co-occurring Subjects x
Items per Project x
Sankey (contributor > project > type) x x

Note: The basic Timeline is automatically hidden when the Stacked Timeline is available (since it's redundant).

The collection / section / project overviews additionally carry a box plot of items-per-project across research sections, and a time-aware chord (subject co-occurrence with a year slider / play button). Single-project dashboards also carry an affiliation map of the geocoded institutions the project's members (PI + team) are affiliated with.

Dashboard layouts are resource-type-aware: each resource template has its own chart order and wide/tall configuration defined in dashboard-layouts.js. This prevents layout gaps in the 2-column grid by pairing half-width charts side by side.

Category Overviews (Item Pages)

Parent/category items get aggregate dashboards spanning their entire item set. Each overview includes a ranked distribution bar chart of the category members plus contextual charts:

Overview Profile key Distribution Chart Additional Charts
Genre overviewItems.genre Top genres (124) Stacked timeline, types, languages, roles, heatmap, subjects, subject trends
Language overviewItems.language Top languages (28) Stacked timeline, language timeline, types, roles, heatmap, subjects, subject trends
Resource Type overviewItems.resourceType Top types (16) Stacked timeline, languages, roles, heatmap, subjects, subject trends
Target Audience overviewItems.targetAudience Top audiences (49) Stacked timeline, types, languages, subjects
Person overviewItems.person Top persons (1,242) Stacked timeline, types, languages, roles, heatmap, subjects, subject trends, choropleth
Institution overviewItems.institution Top institutions (552) Stacked timeline, types, languages, roles, subjects, subject trends, choropleth
Group overviewItems.group Top groups Stacked timeline, types, languages, roles, heatmap, subjects, subject trends
LCSH Subjects overviewItems.lcsh Top LCSH subjects (418) Stacked timeline, types, languages, roles, heatmap, subjects, subject trends
Tags overviewItems.tag Top tags (773) Stacked timeline, types, languages, roles, heatmap, subjects, subject trends
Research Project overviewItems.project Top projects (36) Stacked timeline, language timeline, gantt, beeswarm, types, languages, roles, heatmap, subjects, subject trends, choropleth

Collection Overview & Collection Dashboard

Two collection-wide site-page blocks (Admin > Sites > [site] > Pages) that share one precomputed dataset (asset/data/item-dashboards/collection-overview.json, which aggregates every research item and cluster publication) but render different slices of it. The slice is chosen by a data-layout attribute on the block template, so both stay in sync from a single regeneration.

  • Collection Overview — a curated, home-page-friendly subset mirroring the amira dashboard homepage, in this order: summary stat cards → Africa Multiple Research Centres (AMRCs) and its partners (cluster-geography map) → Research Sections (projects per section) → Research Section × University (research items by section and funding university) → Items by Year and TypeResource Type × LanguageLanguagesResource TypesSubjects & TagsItems by Country. Uses the collectionOverview layout. Here "items" regroups research items, cluster publications (the curated Publications item set), cluster podcasts and YouTube videos: each publication, podcast or video is folded in under a single synthetic Publication / Podcast / YouTube video resource type — overriding any type of its own — so the whole bibliography reads as one Publication category, the podcast episodes as one Podcast category and the channel videos as one YouTube video category in Items by Year and Type, Resource Types and Resource Type × Language (drill into the Publications block for the per-type breakdown). Subjects & Tags spans both controlled LCSH subjects and free tags (both are dcterms:subject). Resource Type × Language drops any resource type that never co-occurs with a language, collapses its tall axis on mobile and hides the per-cell counts so the matrix stays legible on a phone.
  • Collection Dashboard — the full set of collection-wide visualizations (the former "Collection Overview"): stacked timeline, resource-type / language breakdowns, subject trends, co-occurrence chord, sankey, sunburst, geo flows, choropleth, time-aware chord, items-per-project box plot, and more. Uses the full section layout.

Both open with a grid of summary stat cards — Research Items, Projects (with items), People, Organisations, Locations (with the number of countries they span), Languages, Subjects & Tags, Resource Types, Publications, Podcasts, and YouTube videos — each with a lucide icon. All counts come from the precompute: People, Organisations, Languages, Subjects & Tags, Resource Types, Publications, Podcasts and YouTube videos are the sizes of their authority item sets (Persons, Institutions, Languages, Subjects, Type of Resource, Publications, Podcasts, YouTube videos); Projects counts projects that have research items, plus each external partner collection (ILAM, BayGlo) — which the amira dashboard models as a virtual project but which has no template-5 project entity of its own; Research Items and Locations reflect what is present in the corpus. Cards with a zero count are dropped.

The three Collection Overview-only charts come from the precompute too: Research Sections (Aggregators::buildSectionsBar) counts projects per frapo:ResearchGroup; Research Section × University (Aggregators::buildSectionUniversity) routes each project's items to its funding university, read from the project's frapo:isFundedBy link (UBT / UNILAG / UJKZ / UFBA / Rhodes); and the AMRCs & partners map (Aggregators::clusterPartners) is data-driven from Omeka: every institution that dcterms:isPartOf one of the four African Multiple Partners category authorities (Africa Multiple Research Centres, Privileged partner, Cooperation partners, Global partner Centres of African Studies) and carries geo:lat/geo:long coordinates, rendered as colour-coded MapLibre markers with a toggleable legend — the categories, their labels, and the coordinates all come from the authority records and institution items, so curation lives in Omeka (no hard-coded list). A project with no research-section assignment is omitted from the section charts.

The stat cards are a reusable component: any dashboard that emits a precomputed stats array (Aggregators::buildStatCards() on the PHP side, ns.renderStatCards() on the front end) gets the same icon grid — see Recipe C in CONTRIBUTING.md. Like every dataset in this module, both blocks refresh on "Regenerate now" — run it once after installing this version so the snapshot picks up the stat cards, the new section/cluster charts, and the items-by-country map.

Project Explorer

A single project selector that retunes a full project dashboard (~12 charts) beneath it — a meta-page over the precomputed per-project dashboards, with no navigation. Added as a site-page block (Admin > Sites > [site] > Pages); deep-links via ?project=ID.

Compare

Side-by-side comparison of two entities of the same type — paired charts, an overlaid A/B radar profile, and overlap statistics (shared-item percentage + shared badges). Added as the Compare (any entity) site-page block (Admin > Sites > [site] > Pages): an in-page type switcher across projects, people, institutions, subjects, languages, genres (opens on projects by default), each with its own paired-chart set + overlap key (e.g. co-occurring subjects when comparing subjects). Loads the matching {type}-index.json.

Compare Genres

A single-purpose variant of Compare: the same paired-chart comparison locked to genres, so page authors can offer a genre-vs-genre view without exposing the type switcher. Added as the Compare Genres site-page block (Admin > Sites > [site] > Pages); it reuses the generic compare controller and loads genres-index.json.

Discursive Communities — Entity Network

A collection-wide entity network: people, organisations, places, subjects and tags that co-occur across the research items, drawn as an explorable force-directed graph with MapLibre GL (WebGL). Positions are precomputed (ForceAtlas2 in PHP, projected onto a pseudo-Mercator plane), so the client renders ~15k edges with zero layout cost and the network looks identical on every load. Nodes are coloured by entity type and sized by connectivity; an optional toggle re-colours by Louvain co-occurrence cluster. Added as a site-page block (Admin > Sites > [site] > Pages), it loads asset/data/communities/entity-graph.json. Hover a node to isolate its links, scroll to zoom, search or filter by entity type, raise the minimum link weight, and click an entity for its details and page. Organisations are surfaced through their authors' affiliations (person → dcterms:isPartOf → foaf:Organization); subjects split into LCSH Subjects vs free Tags.

The graph renders on the MapLibre GL the module already vendors for its maps — no extra front-end dependency. Node positions are baked by src/Precompute/ForceLayout.php (a pure-PHP ForceAtlas2 port of graphology's, projected to pseudo lng/lat). (The earlier ECharts subject-only discursive.json pipeline was removed in v2.20.0; its force-graph builder lives on as the Publications/Podcasts network chart.)

Network Explorer

Collection-wide relationship networks in one tabbed block — Contributors ↔ Projects (who contributed items to which project), Co-authorship (people who worked on the same items), People ↔ Institutions (affiliations), and Institution collaborations (organisations connected through shared people) — each with its own summary stats. Added as a site-page block (Admin > Sites > [site] > Pages), it loads asset/data/network-explorer.json (rebuilt by "Regenerate now") and renders through the shared network chart builders.

Spatial Exploration

A collection-wide places map: every geocoded location the research items reference, drawn as MapLibre GL bubbles sized by referencing-item count and split into two separately coloured, toggleable layers — places of origin (dcterms:spatial, brand accent) and current locations (dcterms:provenance, cluster Hellblau), the same colour language as the per-entity dashboard map; the legend doubles as a show/hide toggle. Added as a site-page block (Admin > Sites > [site] > Pages), it loads asset/data/item-dashboards/spatial-exploration.json. A sidebar entity picker filters the map to a single project, research section, person, organisation or subject — the selection is served from a baked entity→places adjacency, so it filters client-side with no extra fetch — a country dropdown (built from the point-in-polygon country index, with per-country zoom bounds) focuses the map, and clicking a bubble opens that location's page. The high-cardinality picker types (people, organisations, subjects) are capped to the top entities by mapped-place count to keep the payload lean.

Shares the MapLibre GL engine and the dashboardAssets(['spatial' => true]) asset mode (mirroring the entity network's ['graph' => true]), so a page carrying this block alongside the Discursive Communities graph and the dashboards loads MapLibre exactly once. Built by Aggregators\SpatialTrait + Runner::generateSpatialExploration(); rendered by asset/js/spatial-exploration.js.

Publications

A bibliographic analytics view over the cluster Publications item set (articles, books, chapters, working papers, …). Added as a site-page block (Admin > Sites > [site] > Pages), it loads asset/data/item-dashboards/publications.json and shows:

  • summary stat cards — publications, how many are peer-reviewed (bibo:status), how many carry a full text (EPub's open-access PDFs attached as media), publication types, languages, the people credited as authors or editors (distinct Person records across bibo:authorList + bibo:editorList), the distinct venues (journals & book series, dcterms:isPartOf) and publishers (dcterms:publisher), and the places of publication on the map — the same reusable component as the Collection Overview;
  • a publication-type breakdown (dcterms:type: Article vs. Book vs. Chapter …) and publications per year;
  • a places-of-publication map — the cities the publications were issued in (marcrel:pup, linked to geocoded Location items), as clustered MapLibre bubbles sized by publication count; click a bubble to list the publications issued there. Places the sync couldn't reconcile to a Location item (literal-only values) stay off the map;
  • top venues (dcterms:isPartOf) and top authors (bibo:authorList, unifying literal names with linked Person records);
  • funders (frapo:isFundedBy — the DFG, the EXC 2052 grant, partner foundations), clickable through to each funder's page;
  • a collaboration network — authors and editors who appear together on a publication, with each edge coloured by the relationship (co-authorship, author–editor, or co-editorship) and people matched to a Person record drawn solid (click-through) versus external names muted;
  • a keyword word cloud and keyword co-occurrence chord over dcterms:subject, plus Languages as a pie;
  • an abstract word cloud — the most frequent words across the publication abstracts (bibo:abstract), lemmatised (see Word clouds below).

Authors/editors matched to Person records and subjects matched to Authority/LCSH records are clickable through to their pages. (The By Resource Template chart still appears on person and organisation dashboards, and a person's authored publications surface on their own dashboard.)

YouTube

Analytics for the cluster YouTube channel — the synced YouTube videos item set configured as itemSets.youtube in config/amira-profile.json (bibo:AudioVisualDocument). Added as a site-page block (Admin > Sites > [site] > Pages), it loads asset/data/item-dashboards/youtube.json and shows:

  • summary stat cards — videos, playlists, languages, and the people credited as speakers (marcrel:spk, manually curated so often empty) — the same reusable component as the Collection Overview;
  • transcript word cloud — the headline chart, from the videos' captions (bibo:content), lemmatised when the Word clouds Action has run (in-PHP tokeniser fallback otherwise) — the YouTube counterpart of the Podcasts cloud;
  • who appears together — speakers (marcrel:spk) featuring on the same video, as the shared community force graph; auto-hidden until speaker credits are curated;
  • videos by playlist — each video's dcterms:isPartOf links to a playlist authority item in itemSets.youtubePlaylists, so this ranks the channel's playlists by video count;
  • videos by year (upload date, dcterms:date) and the language mix (dcterms:language) plus languages over time;
  • speakers, when credited.

YouTube videos carry no dcterms:type of their own, so they don't appear in the resource-type pie here; instead they fold into the Collection Overview under a single synthetic YouTube video type (see above). Playlists and speakers are clickable through to their Omeka pages.

Podcasts

Analytics for the cluster's curated podcast episodes — the manually-catalogued Podcasts item set configured as itemSets.podcasts in config/amira-profile.json (fabio:AudioDocument). Added as a site-page block (Admin > Sites > [site] > Pages), it loads asset/data/item-dashboards/podcasts.json and shows:

  • summary stat cards — episodes, series, distinct speakers (marcrel:spk), total hours of audio (with the average length), and the languages — the same reusable component as the Collection Overview;
  • transcript word cloud — the headline chart, from the episodes' AI-generated transcripts (bibo:content), with audio cues ([music]), Speaker N: labels and numbers stripped. Lemmatised when the Word clouds Action has run, with an accessible All / English / French / German / Portuguese language switch. The in-PHP fallback also collapses common English and French inflections in addition to its EN+FR stop-word/filler filtering;
  • speakers & hosts (marcrel:spk / hst / sde), the episode-length distribution (dcterms:extent, ISO-8601, bucketed into bands by Aggregators::buildDurationHistogram), episodes by year (dcterms:date), and episodes by series (dcterms:isPartOf, clickable through to each series);
  • subjects and places — ranked facets, subject trends, a subject co-occurrence chord, and an items-by-country choropleth using the same linked-value and geocoding rules as the other collection dashboards.

Podcasts carry no dcterms:type of their own, so (like YouTube videos) they don't appear in the resource-type pie here; instead they fold into the Collection Overview under a single synthetic Podcast type (see above). Speakers and series are clickable through to their Omeka pages.

Word clouds (lemmatised)

The text word clouds (Podcasts transcripts, Publications abstracts, YouTube captions) are lemmatised so word forms collapse to their base (knowledge/knowledges, study/studies). Proper lemmatisation needs spaCy, which PHP can't do, so it runs as a small CI step rather than in-Omeka:

  • tools/wordclouds/build_wordclouds.py reads each corpus's text from the public REST API (no VPN/auth), lemmatises it with spaCy (EN / FR / DE / PT models; content-word POS only, plus per-language + domain stop-words), and writes per-corpus, per-language frequencies to asset/data/wordclouds/<corpus>.json.
  • The Build word clouds GitHub Action (.github/workflows/wordclouds.yml, manual workflow_dispatch) runs the script and commits the regenerated inputs.
  • These are committed static inputs — like geo/countries.geojson, not the git-ignored generated dashboards. The precompute reads them via Runner::wordCloudInput() and folds the combined (all) frequencies into the dashboard; when a file is absent it falls back to the in-PHP tokeniser, so the clouds always render — just unlemmatised until the Action has run.
  • Reusable: add a corpus under wordcloudCorpora in config/amira-profile.json (item-set key + text property). The Python builder and PHP precompute share that profile, and the per-language buckets feed the word cloud's language toggle (shipped in v2.16.0).

Semantic Map & Similar Items

The Semantic Map site-page block (Admin > Sites > [site] > Pages) places public podcasts, YouTube videos, publications, projects, research sections, and research items in one multilingual Gemini embedding space. Its UMAP scatter can be coloured by resource type or semantic cluster, searched by title, zoomed, and embedded like the other site blocks. Low-signal records remain visible as faint context but do not produce recommendations.

The Similar Items resource-page block (Admin > Sites > [site] > Theme > Configure resource pages) adds up to six cross-type neighbours to an item page and stays hidden when no reliable recommendation exists. Both components read compact, committed public-only JSON from asset/data/embeddings/; the full 768-dimensional float32 vectors are kept out of Git and published as a versioned GitHub Release for downstream search systems. See Semantic embeddings for the shared card, schema, refresh, and compatibility contract.

What's New

A recent-additions feed with a 3 / 6 / 12-month window selector and a "most active projects" bar. Added as a site-page block (Admin > Sites > [site] > Pages), it loads asset/data/item-dashboards/whats-new.json. "Now" is the latest item-creation date in the corpus, so it stays meaningful regardless of when the data was imported.

Featured Collections

A curated landing grid of collection cards (cover mosaic, title, description, partner credit and an item/photo count), added as a site-page block (Admin > Sites > [site] > Pages). The collections come from the module registry (src/FeaturedCollections/Registry.php) — add an entry and it appears here; the only per-block setting is an optional heading. Counts and cover thumbnails are precomputed (asset/data/featured-collections/index.json) with a live API fallback, and most cards link to an in-module Photo Browsing detail page.

Two kinds of card are special:

  • Sub-collections — one item set split into several cards, either by dcterms:identifier prefix (the three Museu Afro-Digital sub-collections share item set 6295) or grouped into journal issues by DOI (ILAM).
  • Link-out cards — a collection with no in-module gallery. The DECCA and Jambo record-label catalogues are image-less Audio recordings credited as the producer (marcrel:prn) of items inside the "Beyond the Digital Return" collection (item set 6262), so they are not item sets of their own. Their cards show a producer-filtered count and link straight to the matching Omeka listing (the entry's externalUrl); set the entry's thumbnail to give the card a cover.

Photo Browsing

Image-first browsing for an image-heavy item set, as a site-page block (Admin > Sites > [site] > Pages). Pick an item set in the block settings; the page renders that set's image-bearing items into three browsers sharing one keyboard-navigable lightbox (← / → / Esc, with a metadata sidebar and an item deep-link):

  • Grid — a responsive masonry of lazy-loaded thumbnails;
  • Map — a clustered MapLibre map of the geolocated photos, loaded on demand so the default Grid view ships zero map weight. Coordinates are resolved by following each photo's dcterms:spatial link to a Location item (geo:lat / geo:long) — the photos themselves rarely carry coordinates;
  • Timeline — a chronological grid that fills the row width, with a year tick above the first cover of each year (a single-year collection shows one year header instead of repeating it). For a journal-issue collection (ILAM) the units are issues placed by year, each opening its table of contents; otherwise they are the photos, each opening the lightbox.

The gallery is precomputed per item set (like every other dataset — see Pre-computing Data), and only for sets that have at least one image-bearing item. Until the first "Regenerate", the block falls back to resolving the gallery live through the Omeka API, so it still works the moment it is added. The Timeline tab appears when the set has dated items and the Map tab when it has coordinates (Map is off for journal-issue collections); the default tab is configurable.

Item Set Dashboard

Inline dashboard for item set pages with server-side aggregation.

Chart Features

  • Toolbox: Save-as-image (2x resolution) and restore on all ECharts charts
  • Word count slider: Adjust the number of words displayed in the word cloud (5 to max)
  • DataZoom: Interactive slider on timeline charts with >15 data points
  • ARIA: Screen reader descriptions on all charts; global decal pattern toggle for accessibility (excluded on wordcloud, chord, heatmap, and sankey where patterns are not meaningful)
  • Cooperative gestures: Main maps require Ctrl+scroll to zoom (prevents scroll hijacking)
  • Globe projection: Main maps default to globe view with a toggle control
  • Scale control: Metric scale bar on all maps

Embedding visualizations

Every site-page visualization block can be embedded on another website via an <iframe>. The embed renders on a bare, chrome-less page that still follows the active theme — it loads the site theme's stylesheet, so the design tokens, self-hosted fonts, and light/dark mode all carry over and an embed reads as a native DRE panel rather than a generic chart.

Each site exposes a snippet gallery at /s/<site-slug>/dre-embed (also linked from Admin → DRE Visualizations): it lists every embeddable block with a live preview and a copy-paste iframe + auto-resize snippet.

  • Whole block/s/<site-slug>/dre-embed/<block>, e.g. …/dre-embed/publications. Renders the exact on-page block.
  • Single chart (dashboard blocks only) — /s/<site-slug>/dre-embed/<block>/<chart>, e.g. …/dre-embed/publications/coAuthorNetwork. Renders one chart full-bleed, without the dashboard header/accordion. The gallery lists each dashboard's available charts, enumerated live from the layout definitions and filtered to the charts that actually carry data.

You can also grab the code without leaving the page: every embeddable visualization on the live site carries a small copy-embed-code button — one per chart in the dashboards' toolbars (next to Save as image), and one per block on the single-view maps/networks — that copies the matching snippet to the clipboard. It reuses the chart toolbar styling, follows light/dark, and never appears inside an embed itself.

Embeddable blocks: Collection Overview, Collection Dashboard, Publications, YouTube, Podcasts (these five also support single-chart embeds), Discursive Communities, Spatial Exploration, Network Explorer, Compare (any entity), Compare Genres, Project Explorer, What's New.

The iframe auto-resizes to its content (the snippet pairs each frame with a tiny postMessage listener). Two optional query params:

  • ?theme=dark — switch to dark mode. Embeds render light by default (an iframe can't read its host page's colour scheme, so light is the safe match for most pages); the embedder opts into dark explicitly.
  • ?primary=RRGGBB — override the brand seed; the theme re-tints every accent, hover, and focus colour from it.

Every embed shows a small source link back to the site, and the endpoint sends Content-Security-Policy: frame-ancestors * in place of the site-wide X-Frame-Options: SAMEORIGIN (set in Module::relaxEmbedFraming()) so it can be framed on any origin. If a reverse proxy forces X-Frame-Options with always, that header must also be relaxed for the /dre-embed path there — PHP can't drop a proxy-added header.

The endpoint is public (it is served into third-party pages), reuses each block's existing precomputed JSON over same-origin fetches, and adds no build step. Single-chart embeds work by pinning the dashboard orchestrator to one chart key via data-chart-only (see asset/js/dashboard.js), wired up in src/Controller/Site/EmbedController.php and view/dre-visualizations/layout/embed.phtml + view/dre-visualizations/embed/*.phtml.

Installation

Download via Omeka S CLI:

docker compose exec php omeka-s-cli module:download --base-path /var/www/html https://github.com/AM-Digital-Research-Environment/DRE-Visualizations/releases/latest/download/DreVisualizations.zip

Then activate in Admin > Modules.

Module folder name. Omeka loads this module from a directory named DreVisualizations, matching the PHP namespace. Official release archives already contain that top-level directory. For development installs, clone the DRE-Visualizations repository explicitly into it: git clone https://github.com/AM-Digital-Research-Environment/DRE-Visualizations.git modules/DreVisualizations.

Configure Resource Pages

Go to Admin > Sites > [site] > Theme > Configure resource pages:

  • Item page: add "Knowledge Graph" and "Visualizations" blocks
  • Item set page: add "Item Set Dashboard" block (optional)

Pre-computing Data

Visualizations load from precomputed JSON in asset/data/. Dashboard snapshots regenerate inside Omeka with no Python at runtime, shell access, or extra database credentials. The optional lemmatised word-cloud inputs are refreshed separately by the repository's Python-based GitHub Action.

Admin → Modules → DRE Visualizations → "Regenerate now" dispatches an Omeka background job (src/Precompute/, pure PHP) that rebuilds, straight from the Omeka database via Omeka's own connection:

  • per-entity & category dashboards + the collection overview
  • the Discursive Communities graph
  • the Spatial Exploration places map (spatial-exploration.json)
  • the Publications analytics (publications.json)
  • the Photo Browsing galleries (one JSON per image-bearing item set)
  • the per-item knowledge graphs + item location maps

Watch progress and any errors at Admin → Jobs → the job's log. Re-run after importing or substantially editing items.

asset/data/knowledge-graphs/ is not committed to the repo (≈6,000 files) — it regenerates on demand. Until the first "Regenerate now", the knowledge-graph block falls back to a lighter live REST-API graph.

Upgrading to 2.22: the new graph renderer works against existing precomputed files, so it needs no regeneration. The cross edges between an item's neighbours, and the raised node caps, are produced by the precompute — click "Regenerate now" to get them.

Updating the module

To pull a new module release into the container:

docker compose exec php omeka-s-cli module:download --base-path /var/www/html --force https://github.com/AM-Digital-Research-Environment/DRE-Visualizations/releases/latest/download/DreVisualizations.zip
docker compose restart php

Then click "Regenerate now" to rebuild the precomputed data.

Architecture

DreVisualizations/
├── Module.php                          # Asset injection (self-hosted ECharts / MapLibre)
├── config/
│   ├── module.ini                      # Module metadata
│   └── module.config.php               # Resource page block registration
├── src/Site/ResourcePageBlockLayout/
│   ├── KnowledgeGraph.php              # Item pages — graph block
│   ├── LinkedItemsDashboard.php        # Item pages — visualizations block
│   └── ItemSetDashboard.php            # Item set pages — dashboard block
├── view/common/resource-page-block-layout/
│   ├── knowledge-graph.phtml           # Lightweight async container
│   ├── linked-items-dashboard.phtml    # Lightweight async container
│   ├── item-set-dashboard.phtml        # Server-side rendered
│   └── partials/dashboard-charts.phtml # Shared chart rendering (inline mode)
├── src/Controller/Site/EmbedController.php # Public iframe-embed endpoint (gallery + block/chart)
├── view/dre-visualizations/
│   ├── layout/embed.phtml              # Bare, theme-following layout for iframes
│   ├── embed/index.phtml               # Per-site embed snippet gallery
│   ├── embed/block.phtml               # Whole-block or single-chart embed body
│   └── embed/not-found.phtml           # Bare 404 inside the frame
├── asset/
│   ├── js/
│   │   ├── graph-canvas.js                       # Reusable: view transform, canvas painter, hit tests
│   │   ├── graph-force.js                        # Reusable: d3-force simulation + pointer/keyboard interaction
│   │   ├── knowledge-graph-data.js               # Payload load + REST fallback + IDF filters (pure data)
│   │   ├── knowledge-graph-ui.js                 # Toolbar, filter panel, legend, text alternative
│   │   ├── item-location-map.js                  # MapLibre origin / current-location panel
│   │   ├── knowledge-graph.js                    # Controller wiring the five together
│   │   ├── dashboard-core.js                     # THEME, COLORS, helpers (window.RV)
│   │   ├── dashboard-layouts.js                  # Per-resource-type layout configs
│   │   ├── dashboard-charts-timeline.js          # Timeline (bar by year)
│   │   ├── dashboard-charts-pie.js               # Pie/donut chart
│   │   ├── dashboard-charts-bar.js               # Horizontal bar chart (top 20)
│   │   ├── dashboard-charts-wordcloud.js         # Word cloud with slider
│   │   ├── dashboard-charts-gantt.js             # Gantt chart (project timelines)
│   │   ├── dashboard-charts-heatmap.js           # Heatmap (type × language)
│   │   ├── dashboard-charts-chord.js             # Chord diagram (co-occurrence)
│   │   ├── dashboard-charts-sankey.js            # Sankey flow diagram
│   │   ├── dashboard-charts-sunburst.js          # Sunburst hierarchy
│   │   ├── dashboard-charts-stacked-timeline.js  # Stacked bar by year and type
│   │   ├── dashboard-charts-beeswarm.js          # Beeswarm scatter (projects by year)
│   │   ├── dashboard-charts-histogram.js         # Histogram (episode-length bands)
│   │   ├── dashboard-charts-boxplot.js           # Box plot (items-per-project spread)
│   │   ├── dashboard-charts-map.js               # Geographic origins map, mini map
│   │   ├── dashboard-charts-cluster-map.js       # AMRCs & partners map (category legend)
│   │   ├── dashboard-charts-affiliation-map.js   # Geocoded affiliation markers
│   │   ├── dashboard-charts-stacked-area.js      # Subject trends, language timeline
│   │   ├── dashboard-charts-treemap.js           # Hierarchical treemap
│   │   ├── dashboard-charts-choropleth.js        # Country choropleth (MapLibre fill)
│   │   ├── dashboard-charts-radar.js             # Entity breadth-profile radar (ECharts)
│   │   ├── dashboard-charts-time-chord.js        # Subject co-occurrence with a year slider
│   │   ├── dashboard-charts-communities.js       # Community force graph (co-author / speaker networks)
│   │   ├── entity-graph.js                       # Entity Network — MapLibre renderer + controller
│   │   ├── spatial-exploration.js                # Spatial Exploration — places map controller
│   │   ├── dashboard-charts-contributor-network.js # Contributor + affiliation networks
│   │   ├── dashboard-collab-network.js           # Institution collaboration network
│   │   ├── dashboard-compare.js                  # Compare controller (any entity type)
│   │   ├── dashboard-compare-unify.js            # Compare: shared category → colour mapping
│   │   ├── dashboard-explorer.js                 # Project Explorer controller
│   │   ├── dashboard-network-explorer.js         # Network Explorer controller (tabbed graphs)
│   │   ├── dashboard-whats-new.js                # What's New controller (window selector)
│   │   ├── item-set-photo-views.js               # Photo Browsing: masonry / map / timeline + lightbox
│   │   ├── sibling-sparkline.js                  # Sibling-items sparkline (standalone; no RV core)
│   │   ├── dashboard-stat-cards.js               # Reusable summary stat cards (lucide icon + value); renders any dashboard's `stats`
│   │   ├── dashboard-registry.js                 # CHART_MAP, labels, descriptions
│   │   └── dashboard.js                          # Orchestrator: render + async/inline init
│   ├── css/
│   │   └── dre-visualizations.css # Styles with CSS custom properties
│   ├── vendor/                    # Committed third-party bundles (byte-identical upstream)
│   │   └── echarts.min.js, maplibre-gl.js, …   # ECharts + MapLibre (self-hosted)
│   └── data/
│       ├── geo/
│       │   └── countries.geojson       # Natural Earth 110m boundaries (choropleth) — committed INPUT
│       ├── wordclouds/                 # Lemmatised frequencies from the CI Action — committed INPUT
│       ├── communities/
│       │   └── entity-graph.json       # Multi-entity co-occurrence network (MapLibre; baked positions)
│       ├── knowledge-graphs/           # Per-item graph JSON — gitignored, regenerated in-Omeka
│       ├── photo-galleries/            # Per-item-set gallery JSON — gitignored, regenerated in-Omeka
│       └── item-dashboards/            # Dashboard JSON + {type}-index.json + publications/podcasts/
│                                       #   youtube/whats-new/spatial-exploration artifacts
├── src/Precompute/                     # PHP precompute engine (admin "Regenerate now")
│   ├── DataLoader.php                  # Items/links/literals/geo via Omeka\Connection
│   ├── Aggregators.php                 # Facade: composes the trait builders + shared constants (unit-tested)
│   ├── Aggregators/                    # Builders split by concern (one trait per file)
│   │   ├── SupportTrait.php            # sort/lookup + PageRank/Louvain primitives
│   │   ├── BasicChartsTrait.php        # aggregateItems, heatmap, roles, templates
│   │   ├── TemporalChartsTrait.php     # timelines, trends, boxplot, time-chord, what's-new
│   │   ├── NetworkChartsTrait.php      # chord, sankey, contributor/affiliation/collab/co-author networks
│   │   ├── GeoChartsTrait.php          # choropleth, geo-flows, country index
│   │   ├── HierarchyChartsTrait.php    # sunburst, treemap
│   │   ├── EntityGraphTrait.php        # global multi-entity co-occurrence graph (MapLibre block)
│   │   ├── PublicationChartsTrait.php  # venues/funders, top authors, publication stat-card counts
│   │   └── OverviewChartsTrait.php     # radar, stat cards, sections/section×university, cluster map
│   ├── KnowledgeGraphs.php             # Per-item knowledge-graph builder (IDF-ranked)
│   ├── ForceLayout.php                 # Pure-PHP ForceAtlas2 — bakes entity-network positions
│   ├── JsonArtifactWriter.php          # Consistent, temp-file-backed JSON artifact writes
│   └── Runner.php                      # Entities, overviews, publications, knowledge graphs
├── CONTRIBUTING.md                     # How to add a visualization (recipes + guardrails)
└── README.md

The front-end script chain (chart builders + registry + controller) is injected by a single view helper — src/View/Helper/DashboardAssets.php ($this->dashboardAssets(...)). Registering a new chart means adding its builder file to that helper's CHART_SCRIPTS list once and running npm run build. Omeka loads the generated dashboard-charts.bundle.js, reducing the modular builder chain to one request; npm run check rejects a stale bundle.

The in-Omeka regeneration (the admin "Regenerate" button) is a self-contained PHP port of the precompute under src/Precompute/ (DataLoaderAggregators / KnowledgeGraphsRunner), run by the background job src/Job/PrecomputeDashboards.php via the admin src/Controller/Admin/MaintenanceController.php. The Aggregators are dependency-free and unit-testable; the job reuses Omeka's Omeka\Connection, so no MySQL variables or Python are needed at runtime.

Local checks

The module ships plain PHP, CSS and JavaScript. Its dependency-free Node build concatenates the modular chart builders into one release asset. Before committing front-end or styling changes, run:

npm run build
npm run check

The check rejects a stale bundle, then runs the DRE design-token contract lint, a syntax sweep, HTML-safety checks, and registry/layout/embed/release contracts. The dependency-free PHP harnesses cover public scope, deterministic metadata selection, snapshot publication, and aggregators:

docker run --rm -v "$PWD:/m" php:8.4-cli sh -c \
  'php /m/tests/PublicCorpusTest.php && php /m/tests/DataSelectionTest.php && php /m/tests/SnapshotPublisherTest.php && php /m/tests/AggregatorsTest.php'

Theming — follows the DRE theme

This module is styled to match, and stay visually consistent with, the Africa Multiple Digital Research Environment (DRE) theme:

https://github.com/AM-Digital-Research-Environment/DRE-theme

It does not define its own colours. Every surface, border, text colour, accent, radius and shadow is taken from the DRE theme's CSS custom properties (design tokens)--surface, --ink, --primary, --border, --radius-*, --shadow-*, … — and the chart colours are read from those same tokens at runtime.

Because the theme re-defines its tokens for dark mode, the module automatically follows the active light / dark theme — including the theme's live toggle (body[data-theme="dark"|"light"]) and the system preference (prefers-color-scheme). No configuration is required.

Important

Always reference the DRE theme's variables — never hard-code a colour.

  • In CSS, use the --rv-* aliases declared at the top of asset/css/dre-visualizations.css; they map straight onto the theme tokens.
  • In JavaScript, resolve colours with ns.cssColor('--token', fallback) (see asset/js/dashboard-core.js).

The fallback is used only when the module is dropped into a non-DRE theme that lacks the token; whenever the DRE theme is present its token wins. This is what keeps the module consistent with the theme and dark-mode aware. Both files start with a "design contract" comment restating this rule.

How light / dark works

Layer Mechanism
CSS chrome (panels, buttons, sliders, popups, legends, cards) The --rv-* aliases resolve theme tokens live, so they flip with body[data-theme] / prefers-color-scheme with zero JS.
ECharts charts dashboard-core.js builds an ECharts theme from the tokens and re-applies it live with chart.setTheme() (ECharts 6) whenever the theme changes.
MapLibre maps Basemap switches between CartoDB Positron (light) and Dark Matter (dark); maps rebuild with the new basemap + marker colours on toggle.

The active theme is watched in dashboard-core.js (ns.refresh()) via a MutationObserver on body[data-theme] plus a prefers-color-scheme listener.

CSS tokens used (alias → DRE theme token)

Module alias (--rv-*) DRE theme token(s)
--rv-bg, --rv-bg-raised, --rv-bg-sunken, --rv-overlay --surface, --surface-raised, --surface-sunken, --surface-overlay
--rv-border, --rv-border-light, --rv-border-strong --border, --border-light, --border-strong
--rv-heading-color, --rv-text-strong, --rv-text-color --ink-strong, --ink, --ink-light
--rv-accent, --rv-accent-hover, --rv-accent-contrast --primary, --primary-hover, --primary-contrast
--rv-radius, --rv-radius-sm --radius-lg, --radius-sm
--rv-shadow, --rv-shadow-sm, --rv-focus-ring --shadow-lg, --shadow-sm, --ring-focus

Chart tokens used (THEME key → DRE theme token)

dashboard-core.js's shared THEME object is populated from these tokens on load and on every theme change:

THEME key DRE theme token Used for
accent --primary map markers, network hubs, flow lines, accents
text / heading --ink / --ink-strong chart text, titles
textMuted --ink-light axis labels, secondary text
border --surface segment gaps, marker outlines
grid / gridLight --border / --border-light axis lines, split lines

The 12-colour categorical palette (COLORS) for multi-series charts follows the data-colour contract: stops 1–6 are the --brand-* pigments and must stay in sync; stops 7–12 are an independent harmonious extension. Stops 1–6 therefore duplicate the brand tokens on purpose — change them in lockstep with --brand-* (see the theme's DESIGN.md §9). Compare-mode relies on a stable colour-by-index mapping, and the brand identity is carried by THEME.accent (= --primary).

Dependencies

Self-hosted from committed vendored bundles under asset/vendor/:

Related project

This module is the Omeka S half of a two-project initiative with the sibling amira dashboard — a SvelteKit static site (ECharts 6 + MapLibre GL) that browses and visualizes the same Africa Multiple Cluster of Excellence research data. (amira was formerly the "WissKI dashboard"; some historical names in the codebase and commit history reflect that.)

The two are complementary and were brought to analytical parity over the shared dataset — tracked in AM-Digital-Research-Environment/amira#10:

  • This module renders a full per-entity dashboard (7–20 charts) inline on each Omeka resource page, plus cross-cutting site-page blocks — Collection Overview, Collection Dashboard, Compare, Project Explorer, What's New, Discursive Communities, Spatial Exploration, Publications, YouTube, Podcasts, and Photo Browsing.
  • amira provides the broad cross-archive overviews as a standalone site.

A reader should find roughly the same analytical toolkit on either side. The "how to add a visualization" recipes and architecture guardrails distilled from that initiative live in CONTRIBUTING.md.

License

GPL-3.0-or-later

About

Omeka S module: ECharts-based knowledge graphs and dashboards as resource page blocks

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Contributors

Languages