The Africa Data Hub Inflation Observer helps journalists, researchers, and civil society organisations explore consumer price inflation (CPI) in African countries and compare it with neighbouring nations. It is a React app that charts year-on-year percentage change for headline CPI and COICOP categories, using data from the IMF, national statistical agencies, and Africa Data Hub.
The app has two views, chosen from the query string:
/) — choose a country from the dropdown.?country=kenya) — load that country’s monthly series and show a line chart.On a country page you can:
All chart values are percentage change, year on year. Historical coverage starts in 2008.
Country URLs use a slug of the name in src/data/countries.json (spaces become hyphens). Special cases: cote-d-ivoire and guinea-bissau.
The dashboard is not a standalone site. Africa Data Hub pages (Webflow) inject the built CSS and JS from GitHub Pages into a #adh-embed node. React then mounts on .app inside that node.
yarn build scopes every CSS rule under a unique class (currently unique-oo0kza in post-build-script.js) so Webflow styles do not leak in or out. The host page must wrap .app in that same class.
This is the Embed / script block on the Webflow page. Production uses these working files on GitHub Pages:
https://africadatahub.github.io/inflation-observer/dist/inflation-observer.csshttps://africadatahub.github.io/inflation-observer/dist/inflation-observer.jsIf the host URL contains webflow, the same loader swaps in inflation-observer.dev.css / inflation-observer.dev.js. Paths that contain climate-observer load the CO2 widget instead. Any other path falls back to a Pym.js iframe whose src is the Webflow CMS field Resource embed URL.
The loader injects style overrides so Webflow .container / .col / .card rules do not break the widget, then the CSS, the module script, and the scoped DOM tree (unique-oo0kza → .html → .body → .app).
<div id="adh-embed"></div>
<script src="https://pym.nprapps.org/pym.v1.min.js"></script>
<script>
if(location.pathname.includes('inflation-observer') || location.pathname.includes('climate-observer')) {
let customClass, cssFile_href, jsFile_src;
if(location.pathname.includes('inflation-observer')) {
customClass = 'unique-oo0kza';
if(window.location.href.includes('webflow')) {
cssFile_href = 'https://africadatahub.github.io/inflation-observer/dist/inflation-observer.dev.css';
jsFile_src = 'https://africadatahub.github.io/inflation-observer/dist/inflation-observer.dev.js';
} else {
cssFile_href = 'https://africadatahub.github.io/inflation-observer/dist/inflation-observer.css';
jsFile_src = 'https://africadatahub.github.io/inflation-observer/dist/inflation-observer.js';
}
} else {
customClass = 'unique-lpxjij';
if(window.location.href.includes('webflow')) {
cssFile_href = 'https://africadatahub.github.io/co2/dist/co2-dev.css';
jsFile_src = 'https://africadatahub.github.io/co2/dist/co2-dev.js';
} else {
cssFile_href = 'https://africadatahub.github.io/co2/dist/co2.css';
jsFile_src = 'https://africadatahub.github.io/co2/dist/co2.js';
}
}
let styleOverride = document.createElement('style');
styleOverride.innerHTML = `
.${customClass} .container {
display: block
}
.${customClass} .col {
padding-bottom: unset;
}
.${customClass} .card {
color: unset;
background-color: unset;
border-radius: unset;
padding: unset;
text-decoration: unset;
display: unset;
box-shadow: unset;
}`;
let adhEmbed = document.getElementById("adh-embed");
let cssFile = document.createElement('link');
cssFile.href= cssFile_href;
cssFile.rel="stylesheet"
let jsFile = document.createElement('script');
jsFile.src= jsFile_src;
jsFile.type= "module";
adhEmbed.append(styleOverride);
adhEmbed.append(cssFile);
adhEmbed.append(jsFile);
const div1 = document.createElement('div');
div1.className = customClass;
const innerDiv1 = document.createElement('div');
innerDiv1.className = 'html';
const bodyDiv1 = document.createElement('div');
bodyDiv1.className = 'body';
const appDiv1 = document.createElement('div');
appDiv1.className = 'app';
bodyDiv1.appendChild(appDiv1);
innerDiv1.appendChild(bodyDiv1);
div1.appendChild(innerDiv1);
document.getElementById("adh-embed").appendChild(div1);
} else {
var pymParent = new pym.Parent('adh-embed', '', {});
}
</script>
embed.js (also in this repo as embed.js) runs on the host page. When the URL contains ?country=…, it rewrites the title, meta tags, Schema.org dataset markup, and an intro paragraph using the bundled annual-rate figures.
The in-app Embed button copies:
<iframe width="700" height="400" src="{current-url}" frameBorder="0"></iframe>
There are two data layers.
| Layer | What it powers | Source |
|---|---|---|
| Monthly time series | The country chart | CKAN Datastore API, live at runtime |
| Annual headline rates | “CPI for full year YYYY was X%”, plus SEO intro copy | Bundled JSON in this repo (also duplicated in embed.js) |
Chart indicators and the CKAN resource id live in src/data/settings.json. The default series is IMF code PCPI_PC_CP_A_PT (Consumer Price Index, all items). Other options are the standard COICOP groups.
The country page queries CKAN with the country’s ISO-3 code, then pivots rows (one per indicator) into date records. Date columns in the datastore look like unsafe_2008_01_31 and are turned into 2008-01-31.
56d80035-ba9a-49f8-a670-70be4dd50ce4 via datastore_search. The public CSV dump linked from the UI is resource 626c5497-a3d2-461f-9f51-8485d94e36b3.src/data/annual-rates.json (consumed by the React app) and the same objects inside embed.js (consumed on the host page).src/data/countries.json (ISO-3 + display name).Annual rates (src/data/annual-rates.csv / .json) — one row per country:
| Field | Meaning |
|---|---|
country_name |
Display name |
country_code |
ISO-3 (JSON only; the working CSV annual-rates-edit-csv also has this column) |
2019 … 2022 |
Full-year headline CPI, percent. Empty string or #N/A when missing |
last_full_year |
Latest year with a usable annual figure |
Extra_notes |
Optional caveat shown under the headline rate |
CKAN monthly resource — one row per country × indicator. Identifier fields include indicator_code and the country ISO-3; remaining keys are unsafe_YYYY_MM_DD month-end values.
Country list (src/data/countries.json):
{ "iso_code": "KEN", "location": "Kenya" }
Monthly data is not generated in this repo. Update the CKAN resource through the inflation-database pipeline; this app will pick up the new series on the next page load.
Annual rates are static. To refresh them:
src/data/annual-rates.csv (or src/data/annual-rates-edit-csv, which includes country_code).src/data/annual-rates.json (same objects: year keys, country_name, country_code, last_full_year, Extra_notes). There is no conversion script in this repo.country_inflation_data variable in embed.js (and into the SEO copy of that file if it is hosted separately).If you add or rename a country, update src/data/countries.json as well. Slugs are derived from location.
#N/A for every bundled annual year.Questions or corrections: info@africadatahub.org.
Install dependencies:
yarn
Add a .env in the project root with a valid CKAN API key. The country page reads process.env.CKAN:
CKAN=<provided-key-here>
Start Parcel:
yarn dev
Then open a country URL such as http://localhost:1234/?country=kenya.
├── embed.js # Host-page SEO / intro script (country query param)
├── post-build-script.js # Scopes built CSS under the unique embed class
├── netlify.toml # SPA fallback to index.html
├── src/
│ ├── index.html # #adh-embed mount + SEO embed.js
│ ├── index.js # React bootstrap
│ ├── App.jsx # Home vs Country from ?country=
│ ├── app.scss
│ ├── pages/
│ │ ├── Home.jsx # Country picker
│ │ └── Country.jsx # Chart, downloads, metadata
│ ├── components/
│ │ ├── CountrySelect.jsx
│ │ └── SocialMedia.jsx # Share + iframe snippet
│ ├── data/
│ │ ├── settings.json # Indicators, CKAN URL / resource ids, copy
│ │ ├── countries.json
│ │ ├── annual-rates.json
│ │ └── annual-rates.csv
│ └── utils/func.js # location ↔ URL slug
└── dist/ # Built CSS/JS served from GitHub Pages
src/pages/DataExplorer.jsx and src/components/DataExplorer.jsx are leftover and are not mounted.
The live widget is the Parcel build on GitHub Pages (iframe branch, dist/), embedded by Webflow on africadatahub.org. netlify.toml is present for an optional SPA host.
Built assets are renamed before publish:
| Environment | Files |
|---|---|
Staging (africadatahub.webflow.io / africadata.webflow.io) |
inflation-observer.dev.css, inflation-observer.dev.js |
| Production (GitHub Pages → africadatahub.org) | inflation-observer.css, inflation-observer.js |
Deploy steps:
src changes into the iframe branch.yarn dev.dist/ contents and .parcel-cache if a previous build is stale.yarn build. The post-build script prints the scoped class (for example unique-oo0kza) and wraps .app in that class.dist/ to the staging or production names above, then push so GitHub Pages updates.yarn build copies logo/favicon assets into dist/, runs Parcel, then post-build-script.js (CSS scoping and rem → px).
| Service | Role |
|---|---|
| ADH CKAN | Monthly inflation Datastore API |
| IMF CPI database | Historical core of the CKAN dataset |
| National statistics offices / central banks | Monthly updates scraped into the CKAN dataset |
| GitHub Pages | Hosts dist/ and the SEO embed.js |
| Webflow | Production and staging Africa Data Hub pages that embed the widget |
| Font Awesome kit | Share-button icons on the country page |
| OpenUp | “Powered by” credit on the country page |
Reuse of the data requires attribution to the IMF and Africa Data Hub. The ADH project is supported by the Bill & Melinda Gates Foundation.