JSFiddle - React, Tailwind, and code Playground
by stitot
HTML
<script src="https://code.highcharts.com/dashboards/datagrid.js"></script>
<div style="margin: 1rem 0;">
<label>
Layout:
<select id="layoutSelect">
<option value="pills" selected>Pills per product</option>
<option value="grouped">Grouped headers: editions under product</option>
</select>
</label>
</div>
<div id="container"></div>
CSS
@import url("https://code.highcharts.com/dashboards/css/datagrid.css");
body {
font-family: ui-sans-serif,-apple-system,system-ui,Segoe UI,Helvetica,Apple Color Emoji,Arial,sans-serif,Segoe UI Emoji,Segoe UI Symbol;
}
h2 {
margin: 30px 0 5px 0;
text-transform: uppercase;
font-weight: 500;
font-size: 20px;
}
p {
margin: 0 0 15px 0;
font-size: 14px;
}
.highcharts-datagrid-table tbody tr td {
vertical-align: top;
line-height: 1.5em;
&.na {
background: rgb(248, 171, 171);
}
&.partial {
background: rgb(235, 235, 143);
}
&.full {
background: rgb(179, 254, 179);
}
div.editions span {
display: inline-block;
padding: 3px 4px;
background: #959595;
color: #ffffff;
font-size: 0.7em;
text-transform: uppercase;
line-height: 1em;
margin-right: 5px;
border-radius:5px;
}
}
.theme-features {
--hcg-font-size: 14px;
--hcg-padding: 10px;
--hcg-horizontal-padding: 1px;
--hcg-header-font-weight: 600;
--hcg-header-row-border-color: #cccccc;
--hcg-row-border-width: 1px;
--hcg-row-border-color: #cccccc50;
}
JavaScript
// ================== Feature Matrix → Highcharts Grid (Pills vs Grouped) ==================
// - Fetch ONCE, cache payload
// - Render either layout from the SAME dataset (no refetch)
// - If a product has NO editions → it shows in BOTH layouts:
// • Pills: as a normal product column
// • Grouped: as a simple (non-grouped) column (i.e., not a grouped header)
// ==========================================================================================
// ---------- Utilities ----------
const escapeHtml = (s) =>
String(s ?? "").replace(/[&<>"']/g, (ch) =>
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch])
);
const colIdFor = (productName, editionName) => `${productName} | ${editionName}`;
// ---------- Core normalization ----------
function coreFromPayload(payload) {
const data = payload && payload.result ? payload.result : payload;
const products = Array.isArray(data?.products) ? data.products : [];
const categories = Array.isArray(data?.categories) ? data.categories : [];
// Keep stable product order
const productOrder = products.map((p) => ({
id: p._id,
name: p.name,
editions: Array.isArray(p.editions) ? p.editions : []
}));
return { categories, productOrder };
}
function indexSupport(supportedBy) {
// Map productId → { editions: string[], comment: string }
// (No coverage here; we compute it later using product's total editions)
return new Map(
(Array.isArray(supportedBy) ? supportedBy : []).map((s) => [
s?._id,
{
editions: Array.isArray(s?.editions) ? s.editions : [],
comment: typeof s?.comment === "string" ? s.comment : ""
}
])
);
}
// ---------- Unified builder (same output shape for BOTH layouts) ----------
/**
* Build tables for the selected layout.
* @param {'pills'|'grouped'} layout
* @returns Array<{ name, description, dataTable: {columns}, header }>
*/
function...