Published
Edited
Mar 19, 2021
Importers
34 stars
Insert cell
Insert cell
// Create a viewof the table
viewof sample_table = DraggableTable(input_data)
Insert cell
Insert cell
Insert cell
// A sample dataset
input_data = [
{ col1: "Label 1", col2: 40, col3: 1, col4: 200 },
{ col1: "Label 2", col2: 70, col3: 5, col4: 200 },
{ col1: "Label 3", col2: 100, col3: 7, col4: 300 },
{ col1: "Label 4", col2: 30, col3: 6, col4: 100 },
{ col1: "Label 5", col2: 80, col3: 1, col4: 100 }
]
Insert cell
// Create a viewof the table
viewof my_table = DraggableTable(input_data)
Insert cell
// Updated values are stored in this variable and can be used for whatever you like
my_table
Insert cell
Insert cell
Insert cell
Insert cell
// Import a dataset
import { penguins } from "@enjalot/palmer-penguins"
Insert cell
viewof penguin_table = DraggableTable(penguins)
Insert cell
penguin_table
Insert cell
DraggableTable = input => {
input = input || penguins;
const data = Array.from(input);
// A few constants (that can become parameters)
const min_width = 30;
const padding_right = 10;
const max_height = 400;
const formatter = x => (isFinite(x) ? d3.format(".1f")(x) : x);

// Create the table
const wrapper = d3
.create("div")
.style("width", width + "px")
.style("max-height", max_height + "px")
.style("overflow-y", "auto");

const table = wrapper
.append("table")
.style("table-layout", "fixed")
.style("border-collapse", "collapse");

const el = wrapper.node();
el.value = data.map(d => Object.assign({}, d));
const cols = Object.keys(data[0]);

// Compute the width for each column
const col_width = Math.floor(width / cols.length);

// Set the width by using a colgroup
const colgroup = table
.append("colgroup")
.append("col")
.attr("span", cols.length)
.style("width", col_width + "px")
.style("border-right", "40px");

let scales = new Map();
const header = table.append('tr');

// Append a table header and compute linear scales for each column
cols.forEach(col => {
header.append("th").text(col);
const max = d3.max(data, dd => +dd[col]);
const min = d3.min([0, d3.min(data, dd => +dd[col])]); // minimum of 0 or negative value
const col_scale = isNaN(max)
? x => x
: d3
.scaleLinear()
.domain([min, max])
.range([min_width, col_width - padding_right]);

scales.set(col, col_scale);
});

// Append rows
data.forEach((row, row_num) => {
const tr = table.append("tr");

// Append cells in each row
Object.keys(row).forEach((cell, cell_num) => {
const value = row[cell];
const scale = scales.get(cell);
const td = tr.append("td").style("width", col_width + "px");

const div = td
.append("div")
.style("display", "inline-block")
.style("border", "1px solid white")
.style("height", "20px")
.style("cursor", "pointer")
.style("background-color", isFinite(value) ? "lightblue" : "none");

div.append("text").text(formatter(value));

// Only assign drag events for numeric columns
if (scale.domain === undefined) return;

// Drag event based on: https://observablehq.com/@d3/circle-dragging-iii
// Name spacing these functions was hard....
div.style("width", scale(value) + "px").call(
d3
.drag()
.on("start", function(event) {
d3.select(this)
.raise()
.style("border", "1px solid black");
})
.on("drag", function(event) {
let x = event.x;
if (x > col_width - padding_right || x < min_width) return;
d3.select(this).style("width", x + "px");
const new_value = scale.invert(x);
d3.select(this)
.select("text")
.text(formatter(new_value));

// Set the value of the table to the updated data
el.value[row_num][cell] = new_value;
el.dispatchEvent(new Event("input", { bubbles: true }));
})
.on("end", function(event) {
d3.select(this).style("border", "1px solid white");
})
);
});
});

return el;
}
Insert cell
Insert cell
d3 = require("d3")
Insert cell
// This is just for code highlighting, not the table
code_styles = html`<style>
p code{color: #c30771;}
</style>`
Insert cell
// For the sample scatterplot
margin = ({ left: 30, top: 10, bottom: 30, right: 30 })
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more