Draw the worksheet
Turn the worksheet’s summary data into your own rendering — an SVG chart with no library, sized to the canvas Tableau gave you.
Why it matters
This is the whole job of a viz extension: data in, drawing out. The discipline that makes it work is a single idempotent draw() — read the data, clear the canvas, render from scratch — because the next lesson will call it on every filter change and resize, and incremental-update rendering is where custom vizzes go to die. Plain SVG is the honest starting point: no bundler, no dependency, and it makes the data-to-pixels mapping visible. Swap in D3 or ECharts later; the draw() contract stays identical.
Write viz.html's JavaScript for a Tableau viz extension that renders a horizontal bar chart of the worksheet's summary data as inline SVG with no chart library. Structure it as one top-level async function draw(worksheet) that: reads the data with getSummaryDataReaderAsync()/getAllPagesAsync() and releases the reader in a finally block; picks the first string column as labels and the first int or float column as values; scales bars to window.innerWidth and window.innerHeight; renders each bar with its label text-anchored at the left and shows the value's formattedValue in a title element for hover; and replaces the page body's children wholesale so repeated calls are idempotent. If no string or numeric column exists, render one sentence telling the author what to add to the viz. Call draw() once after initializeAsync().
Read the shape of the function, not just the SVG mechanics. It starts from the worksheet every time, ends with replaceChildren — wipe and redraw — and never mutates what a previous call drew. That makes it safe to call from anywhere, any number of times, which the next stop exploits.
Two carried-over habits from the dashboard route earn their keep here: value does the math while formattedValue does the talking, and columns are discovered by dataType rather than hard-coded by name — your chart type should survive being dropped on any worksheet, and telling the author what is missing beats rendering nothing.
async function draw(worksheet) {
const reader = await worksheet.getSummaryDataReaderAsync();
let table;
try {
table = await reader.getAllPagesAsync();
} finally {
await reader.releaseAsync();
}
// Discover, don't hard-code: first dimension-ish and measure-ish columns.
const cols = table.columns;
const li = cols.findIndex(c => c.dataType === 'string');
const vi = cols.findIndex(c => c.dataType === 'int' ||
c.dataType === 'float');
if (li < 0 || vi < 0) {
document.body.textContent =
'Add a dimension and a measure to the viz to draw it.';
return;
}
const rows = table.data.map(r => ({
label: r[li].formattedValue,
value: Number(r[vi].value) || 0,
pretty: r[vi].formattedValue,
}));
const W = window.innerWidth, H = window.innerHeight;
const max = Math.max(...rows.map(r => r.value), 0);
const barH = Math.max(4, Math.floor(H / rows.length) - 6);
const NS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(NS, 'svg');
svg.setAttribute('width', W);
svg.setAttribute('height', H);
rows.forEach((r, i) => {
const y = i * (barH + 6);
const bar = document.createElementNS(NS, 'rect');
bar.setAttribute('x', 150);
bar.setAttribute('y', y);
bar.setAttribute('width', max ? (r.value / max) * (W - 160) : 0);
bar.setAttribute('height', barH);
bar.setAttribute('fill', '#4E79A7');
const hover = document.createElementNS(NS, 'title');
hover.textContent = r.label + ': ' + r.pretty;
bar.appendChild(hover);
svg.appendChild(bar);
const label = document.createElementNS(NS, 'text');
label.setAttribute('x', 144);
label.setAttribute('y', y + barH / 2 + 4);
label.setAttribute('text-anchor', 'end');
label.setAttribute('font-size', '12');
label.textContent = r.label;
svg.appendChild(label);
});
// Wipe and redraw — idempotent by construction.
document.body.replaceChildren(svg);
}
(async () => {
await tableau.extensions.initializeAsync();
await draw(tableau.extensions.worksheetContent.worksheet);
})();Got what you came for? Mark the stop and the line fills in beneath you.