d3-interactive-table · git:20260424.d50cf5a · 2026-04-24 · sha256 f0cb1753553b29f4

d3-interactive-table git:20260424.d50cf5aA

Immutable. This exact content never changes and is served at /api/v1/blob/f0cb1753553b29f4.

---
name: d3-interactive-table
description: Build a sortable, highlightable HTML data table linked to a D3 chart so clicking a chart element highlights the matching table row and vice versa.
---

# D3 v6 Interactive Table with Chart Cross-Linking

## Overview

A pure-HTML table built alongside a D3 chart where selections are synchronized: clicking a bubble highlights a table row, clicking a table row highlights the bubble.

## Table Construction Pattern

```javascript
function buildTable(data, containerId) {
    const container = d3.select(containerId);
    const table = container.append('table').attr('class', 'data-table');

    // Header
    const thead = table.append('thead');
    thead.append('tr').selectAll('th')
        .data(['Ticker', 'Full Name', 'Sector', 'Market Cap'])
        .join('th')
        .text(d => d);

    // Body
    const tbody = table.append('tbody');
    const rows = tbody.selectAll('tr')
        .data(data)
        .join('tr')
        .attr('data-ticker', d => d.ticker)  // stable identifier
        .on('click', function(event, d) {
            selectItem(d.ticker);
        });

    rows.selectAll('td')
        .data(d => [d.ticker, d.name, d.sector, formatMarketCap(d.marketCap)])
        .join('td')
        .text(v => v);
}
```

## Cross-Linking Pattern

Use a shared `selectItem(ticker)` function that updates both the chart and table:

```javascript
let selectedTicker = null;

function selectItem(ticker) {
    selectedTicker = ticker;

    // Update table rows
    d3.selectAll('tr[data-ticker]')
        .classed('selected', d => d.ticker === ticker);

    // Update chart circles
    d3.selectAll('circle.bubble')
        .classed('selected', d => d.ticker === ticker);

    // Scroll to highlighted row
    const row = document.querySelector(`tr[data-ticker="${ticker}"]`);
    if (row) row.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
}
```

## CSS for Highlighted State

```css
/* Table row highlight */
tr[data-ticker]:hover {
    background: #f0f4ff;
    cursor: pointer;
}
tr[data-ticker].selected {
    background: #dce8ff;
    font-weight: 600;
}

/* Bubble highlight */
circle.bubble.selected {
    stroke: #1a1a2e;
    stroke-width: 3px;
    filter: drop-shadow(0 0 4px rgba(0,0,0,0.5));
}
```

## Market Cap Formatting

```javascript
function formatMarketCap(val) {
    if (!val || isNaN(+val)) return '—';
    const v = +val;
    if (v >= 1e12) return (v / 1e12).toFixed(2) + 'T';
    if (v >= 1e9)  return (v / 1e9).toFixed(2) + 'B';
    if (v >= 1e6)  return (v / 1e6).toFixed(2) + 'M';
    return v.toFixed(0);
}
```

## Scrollable Table Container

For large datasets, constrain table height and enable overflow scrolling:

```css
.table-container {
    max-height: 600px;
    overflow-y: auto;
    border: 1px solid #ddd;
    border-radius: 6px;
}

/* Sticky header */
thead th {
    position: sticky;
    top: 0;
    background: #f8f9fa;
    z-index: 10;
}
```

## Click Toggle (Deselect on Second Click)

```javascript
.on('click', function(event, d) {
    if (selectedTicker === d.ticker) {
        clearSelection();
    } else {
        selectItem(d.ticker);
    }
})

function clearSelection() {
    selectedTicker = null;
    d3.selectAll('.selected').classed('selected', false);
}
```