Skip to main content
Technology

Embedding a Province-Level Tender Heatmap on Your South African Website

The Tenders-SA Heatmap widget renders a colour-coded province-level heatmap of active tenders across all nine South African provinces. Open source, zero-dependency, and embeddable with a single HTML tag.

Why a Province-Level Tender Heatmap?

South Africa's nine provinces have vastly different procurement profiles. Gauteng dominates with the highest tender volume and value, while provinces like the Northern Cape and North West offer fewer but often higher-value opportunities in mining and energy. For suppliers deciding where to focus their bidding efforts, a geographic view of procurement activity is an essential reference.

The Heatmap widget (github.com/Tenders-SA/widget-heatmap

) provides a colour-coded, embeddable province-level heatmap showing where active tenders are concentrated across South Africa. It is an open-source, zero-dependency JavaScript widget that renders a compact visual summary of procurement activity by province.

This kind of geographic context is especially useful early in a bidding strategy, before a supplier has committed time to researching individual notices. A contractor weighing whether to expand operations into a new province, or a consultancy deciding which regional office to prioritise, benefits from seeing procurement activity laid out visually rather than scrolling through a long list of individual tenders. The heatmap format compresses that decision-relevant information into something that can be scanned in a few seconds.

What Data Does the Widget Display?

The widget fetches data from the /api/widgets/heatmap endpoint and renders a row for each province. The heatmap shows the following for every province:

FieldTypeDescription
provincestringProvince name (e.g. 'Gauteng', 'Western Cape')
tenderCountnumberNumber of active tenders currently published
totalValuenumberTotal combined estimated value of active tenders in ZAR
topIndustriesstring[]List of the top industries with active tenders in the province

Provinces are sorted by tender count (highest first). Each row has a horizontal bar whose width and colour intensity are proportional to the province's share of total active tenders. A summary badge in the top-right corner shows the national total. When you hover over the widget, a call-to-action footer appears that links to the full interactive heatmap on Tenders-SA.org.

The topIndustries field is a small but meaningful addition — it gives a viewer more than a raw count. A row showing a high tender count in the Eastern Cape paired with construction and infrastructure as the leading industries tells a very different story than the same tender count paired with healthcare and social services. For a visitor scanning the heatmap quickly, this extra layer of context often prompts them to click through to the full site to investigate further, which is exactly the behaviour the embedded widget is designed to encourage.

Architecture: How the Widget Works

The Heatmap widget follows the same three-layer architecture as the other Tenders-SA widgets:

  • api.ts — A fetchHeatmapData() function that calls the heatmap API endpoint with an optional AbortSignal. The endpoint returns an array of ProvinceData objects and is CORS-enabled for cross-origin requests.
  • renderer.ts — The DOM renderer builds the heatmap layout from scratch: header with branded indicator dot and "Powered by" link, ranked province list with colour-coded horizontal bars, a summary badge showing the national active tender count, and a footer CTA that appears on hover.
  • index.ts — The HeatmapWidget class manages the lifecycle: constructor (reads data attributes and merges with provided config), render() (fetches data, delegates to renderer), and destroy() (aborts in-flight requests and cleans up the DOM). It also supports an optional province filter in the config to highlight a single province.

A utility module (utils.ts) handles helper functions: getIntensityColor() maps tender counts to a colour gradient (green through amber to deep blue), getBarWidth() calculates proportional bar widths, and formatProvinceName() normalises province names for display.

Keeping these concerns in separate modules is what makes the widget straightforward to maintain and extend without touching the parts that already work. If the colour gradient needs adjusting for accessibility reasons, that change lives entirely in utils.ts. If the layout of the header needs to change, that is isolated to renderer.ts. And because index.ts only orchestrates the lifecycle rather than containing rendering or fetching logic itself, the widget's public API — constructor, render(), destroy() — stays stable even as the internals evolve.

Configuration Options

OptionTypeDefaultDescription
theme'light''dark'System preference
provincestringnullnull
apiBasestring'https://tenders-sa.org
'
Base URL for the API endpoint

Embed Methods

Method 1: HTML Data Attribute

1<!-- Full national heatmap with dark theme -->
2<div data-tendersa-heatmap data-theme="dark"></div>
3
4<script
5  src="https://unpkg.com/@tenders-sa-org/widget-heatmap@latest/dist/widget-heatmap.js"
6  async
7></script>
HTML

To highlight a single province (useful for region-specific websites):

1<!-- Show only Western Cape tenders, light theme -->
2<div
3  data-tendersa-heatmap
4  data-province="western-cape"
5  data-theme="light"
6></div>
7
8<script
9  src="https://unpkg.com/@tenders-sa-org/widget-heatmap@latest/dist/widget-heatmap.js"
10  async
11></script>
HTML

Method 2: Programmatic (NPM)

1npm install @tenders-sa-org/widget-heatmap
BASH
1import { HeatmapWidget } from '@tenders-sa-org/widget-heatmap'
2
3const container = document.getElementById('heatmap-container')
4const widget = new HeatmapWidget(container, {
5  theme: 'dark',
6  province: 'gauteng',
7})
8
9await widget.render()
10
11// Clean up when component unmounts
12// widget.destroy()
JAVASCRIPT

A Practical Walkthrough

Consider a regional business chamber that represents members across the Western Cape. Rather than writing custom code to fetch and display provincial procurement data, the chamber's webmaster can drop in the province-filtered embed shown above with data-province="western-cape", and the widget takes care of fetching, rendering, and keeping the display current on every page load. Because the widget queries live data from the /api/widgets/heatmap endpoint rather than a static snapshot, the chamber never needs to manually update the figures — the numbers reflect whatever is currently active on Tenders-SA.org each time a visitor loads the page.

For a developer who wants tighter control — for example, to place the heatmap inside a single-page application or to re-render it when a user changes a filter elsewhere on the page — the programmatic NPM approach is the better fit. Instantiating the HeatmapWidget class directly gives full control over when render() is called and lets the application decide when to call destroy(), which is important in frameworks that mount and unmount components frequently, since forgetting to clean up would leave orphaned network listeners behind.

Internationalisation and Display

The heatmap widget formats province names using a normalisation function that handles the different naming conventions used in government data. For example, it maps short codes and variations to the standard province names used on Tenders-SA.org. Currency values are formatted using the compact number notation consistently used across all Tenders-SA widgets.

Performance and Browser Support

  • Bundle size: approximately 6 KB (minified and gzipped)
  • All modern browsers (Chrome, Firefox, Safari, Edge)
  • The /api/widgets/heatmap endpoint supports CORS with Access-Control-Allow-Origin: *, which means it can be called directly from browser-side JavaScript even without the widget script
  • Styles are injected inline with deduplication — only one style element is added to the document head regardless of how many heatmap widget instances are on the page

The Open Source Package

The Heatmap widget is published as @tenders-sa-org/widget-heatmap on npm and is open source under the MIT license. The source code is available at github.com/Tenders-SA/widget-heatmap

.

The repository includes TypeScript source code with exported type definitions, unit tests via Vitest with happy-dom, a tsup build pipeline that outputs ESM, CJS, and UMD bundles, utility functions for colour mapping and number formatting, and a README with multiple embed examples.

Use Cases

  • Provincial business directories — Show a heatmap on your province-specific page to give visitors context about local procurement activity.
  • News and media sites covering SA procurement — Embed the national heatmap as a sidebar widget to visually show where procurement is concentrated.
  • Municipal and government sites — Display transparency visualisations showing tender activity across all provinces.
  • Supplier and contractor associations — Give members a geographic view of opportunities across the country.

Getting Started

Add the widget to any page with a single div and script tag. The full source code and documentation are at github.com/Tenders-SA/widget-heatmap

. For direct API access, the endpoint at /api/widgets/heatmap returns province-level data and is fully CORS-enabled.

If you are building on top of SA procurement data, you may also be interested in the Sector Trends widget

for an industry-level view, or the Winners Feed widget
for recent award data.

Tags

WidgetHeatmapData VisualisationOpen SourceGovernment TendersProvincial AnalysisGeographic Data
Relevant Tender Opportunities

Based on this article's topics, here are some current tenders that might interest you

Professional, Scientific and Technical Activities

Feasibility Study on Hydrogen-Powered Mining Equipment Conversion using Hydrogen Combustion Technology for Surface and Underground Mining.

South African National Energy Development Institute
Gauteng
02 Sept 2026
26d left
Administrative and Support Activities

VIRTUALIZATION TECHNOLOGY REQUIREMENTS AND CAPABILITIES FOR SABC

South African Broadcasting Corporation Limited
National
28 Aug 2026
21d left
Computer Programming, Consultancy and Related Activities

REQUEST FOR INFORMATION: CLOUD-HOSTED OBSERVABILITY SOLUTION FOR THE ROAD ACCIDENT FUND (RAF) TO SUPPORT ENTERPRISE-WIDE MONITORING, ANALYTICS AND OPERATIONAL INTELLIGENCE ACROSS ITS HYBRID TECHNOLOGY ENVIRONMENT

Road Accident Fund (RAF)
Gauteng
26 Aug 2026
19d left
Services: Professional

APPOINTMENT OF A SERVICE PROVIDER TO ROLL OUT AND IMPLEMENT A FOUNDRY TRAINING AND FURNACE REPAIRS PROJECT AT THE DOWNSTREAM ALUMINIUM CENTRE FOR TECHNOLOGY (DACT) TO IMPROVE METAL BENEFICIATION SKILLS FOR A PERIOD OF SIX MONTHS.

Kwazulu Natal - Economic Development, Tourism and Environ Affairs
KwaZulu-Natal
25 Aug 2026
18d left
Services: Professional

Job grading, remuneration benchmark, and employee engagement services Tender for Technology Innovation Agency

Technology Innovation Agency
National
24 Aug 2026
17d left
Civil Engineering

APPOINTMENT OF A SUITABLE CIDB GRADE 1SL (OR HIGHER) SERVICE PROVIDER TO REMOVE, SUPPLY, DELIVER AND INSTALL STEEL TRELLIS AND STEEL BURGLAR GATES REINFORCEMENTS TO IMPROVE SECURITY AT ST JOHNS COLLEGE TECHNOLOGY AND INNOVATION HUB

National Research Foundation
Eastern Cape
20 Aug 2026
13d left

Want to see all available tenders?

Browse All Tenders →
AI-Powered Matching
Never Miss a Perfect Tender Again
Our AI analyzes thousands of tenders and finds the ones YOUR company can actually win
AI Match Scoring for every tender
Instant alerts for 85%+ matches
B-BBEE level optimization
Document readiness checks

Share this article

Embedding a Province-Level Tender Heatmap on Your South African Website

The Tenders-SA Heatmap widget renders a colour-coded province-level heatmap of active tenders across all nine South African provinces. Open source, zero-dependency, and embeddable with a single HTML tag.

https://www.tenders-sa.org/blog/embed-heatmap-widget