Published
Edited
May 25, 2021
1 fork
Importers
3 stars
Insert cell
Insert cell
data = FileAttachment("targets-1.csv").csv({typed: true})
Insert cell
viewof validator = Table(data, {
dataRules: [data => data.length <= 5 || "Data cannot have more than 5 rows"],
columnRules: {actual: d => d !== 0 || "Cannot be zero"}
})
Insert cell
Insert cell
validator
Insert cell
Insert cell
columnRules = ({actual: d => d !== 0 || "Cannot be zero"})
Insert cell
// normal off-the-shelf @observablehq/inputs Table
Inputs.table(data, {format: getWarningFormatter(columnRules)})
Insert cell
renderWarnings(getColumnWarnings(columnRules, data))
Insert cell
Insert cell
Table = (data, {dataRules = [], columnRules = {}, ...options} = {}) => {
const dataWarnings = getDataWarnings(dataRules, data);
const columnWarnings = getColumnWarnings(columnRules, data)
const format = getWarningFormatter(columnRules)
const tableNode = Inputs.table(data, {...options, format});
const node = htl.html`
${tableNode}
${renderWarnings([...dataWarnings, ...columnWarnings])}
`
node.value = data;
tableNode.addEventListener("input", e => {
console.log(e)
node.value = e.currentTarget.value
});
return node;
}
Insert cell
renderWarnings = warnings => htl.html`${warnings.map(warning => htl.html`<div style=${warningStyle}>
${badge()} ${warning}
</div>`)}`
Insert cell
getWarningFormatter = columnRules => Object.fromEntries(Object.keys(columnRules).map(
col => [
col,
d => columnRules[col](d) !== true
? htl.html`<div style=${{background: reddish}}>${formatAuto(d)}</div>`
: formatAuto(d)
]))
Insert cell
getDataWarnings = (dataRules, data) => dataRules.map(rule => rule(data)).filter(d => d)
Insert cell
getColumnWarnings = (columnRules, data) => Object.keys(columnRules).map(
col => {
const warnings = data.map(row => columnRules[col](row[col])).filter(d => d !== true)
return warnings.length
&& htl.html`<code style=${{fontSize: "inherit"}}>${col}</code>: ${warnings[0]} ${warnings.length > 1 ? `(${warnings.length})` : ""}`
}).filter(d => d)
Insert cell
badge = () => htl.html`<div style=${{
display: "inline-block",
textTransform: "uppercase",
font: "10px var(--sans-serif)",
borderRadius: "3px",
padding: "2px 5px",
fontSize: "10px",
fontWeight: "bold",
background: reddish
}}>
!
</div>`
Insert cell
warningStyle = ({
font: "12px var(--sans-serif)",
padding: "3px 0"
})
Insert cell
reddish = "rgba(255, 165, 0, 0.15)"
Insert cell
// https://github.com/observablehq/inputs/blob/main/src/format.js
formatAuto = {
function stringify(x) {
return x == null ? "" : x + "";
}
function formatAuto(value) {
return value == null ? ""
: typeof value === "number" ? formatNumber(value)
: value instanceof Date ? formatDate(value)
: value + "";
}
function formatNumber(value) {
return value === 0 ? "0" : value.toLocaleString("en"); // handle negative zero
}
function formatDate(date) {
var hours = date.getUTCHours(),
minutes = date.getUTCMinutes(),
seconds = date.getUTCSeconds(),
milliseconds = date.getUTCMilliseconds();
return isNaN(date) ? "Invalid Date"
: formatYear(date.getUTCFullYear(), 4) + "-" + pad(date.getUTCMonth() + 1, 2) + "-" + pad(date.getUTCDate(), 2)
+ (milliseconds ? "T" + pad(hours, 2) + ":" + pad(minutes, 2) + ":" + pad(seconds, 2) + "." + pad(milliseconds, 3) + "Z"
: seconds ? "T" + pad(hours, 2) + ":" + pad(minutes, 2) + ":" + pad(seconds, 2) + "Z"
: minutes || hours ? "T" + pad(hours, 2) + ":" + pad(minutes, 2) + "Z"
: "");
}
function formatYear(year) {
return year < 0 ? "-" + pad(-year, 6) : year > 9999 ? "+" + pad(year, 6) : pad(year, 4);
}
function pad(value, width) {
return (value + "").padStart(width, "0");
}

return formatAuto;
}
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