Published
Edited
Dec 21, 2020
Insert cell
Insert cell
Insert cell
key = {
const marginLeft = 0;
var ab = html`<style type="text/css">
.hidden-desktop{display:none;}
.hidden-mobile{display:block;}
@media only screen and (min-width: 543px) {
.hidden-desktop{display:block;}
.hidden-mobile{display:none;}
}
</style>
${alegreya()}
<span class="hidden-desktop" style="text-align:center"><span style="display: inline-block">${desktopreference}</span></span>
<span class="hidden-mobile" style="text-align:center"><span style="display: inline-block">${mobilereference}</span></span>`
return ab
}
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

svg.append("g")
.selectAll("g")
.data(series)
.join("g")
.attr("fill", (d, i) => jailcolor(i))
.selectAll("rect")
.data(d => d)
.join("rect")
.attr("x", d => x(d[0]))
.attr("y", (d, i) => y(d.data.name))
.attr("stroke", (d, i) => "#111")
.attr("stroke-width", 3)
// .attr("fill", (d, i) => jailcolor(i))
.attr("width", d => x(d[1]) - x(d[0]))
.attr("height", y.bandwidth())
.append("title")
.text(d => `${d.data.name} ${d.key}
${formatValue(d.data[d.key])}`);
svg.append("g")
.selectAll("g")
.data(monoseries)
.join("g")
// .attr("fill", (d, i) => color(i))
.selectAll("rect")
.data(d => d)
.join("rect")
.attr("x", d => x(d[0]))
.attr("y", (d, i) => y(d.data.name))
.attr("stroke", (d, i) => "#111")
.attr("stroke-width", 3)
.attr("fill", (d, i) => color(i))
.attr("width", d => x(d[1]) - x(d[0]))
.attr("height", y.bandwidth())
.append("title")
.text(d => `${d.data.name} ${d.key}
${formatValue(d.data[d.key])}`);

svg.append("g")
.call(xAxis)
.attr("font-family", "Alegreya")
.attr("font-weight", "500")
.attr("font-size", 15);

svg.append("g")
.call(yAxis)
.attr("font-family", "Alegreya")
.attr("font-weight", "500")
.attr("font-size", 15);

return svg.node();
}
Insert cell
mobilereference = table(mobile, {
paged: 8,
columns: {
ColourX: {
formatter(val, i) {
// A formatter that changes its behavior based on the row index,
// so we don’t repeat the $ for every row.
var x = html`<svg width="20" height="20" style="padding-top:.4em; padding-bottom:0em; padding-left:.2em; padding-right:.2em;">
<rect width="12.5" height="12.5" style="fill:${val};stroke-width:2;stroke:rgb(0,0,0)" />
</svg>`
return x
// return i === 0 ? d3.format('$.2f')(val) : d3.format('.2f')(val);
}
},
ColourY: {
formatter(val, i) {
// A formatter that changes its behavior based on the row index,
// so we don’t repeat the $ for every row.
var x = html`<svg width="20" height="20" style="padding-top:.4em; padding-bottom:0em; padding-left:.2em; padding-right:.2em;">
<rect width="12.5" height="12.5" style="fill:${val};stroke-width:2;stroke:rgb(0,0,0)" />
</svg>`
return x
// return i === 0 ? d3.format('$.2f')(val) : d3.format('.2f')(val);
}
},
Category: {
formatter(val) {
// Formatters can also return HTML values! Here’s
// one that highlights first names.
// let parts = val.match(/([^,]*)\,(.*)/);
var x = html`<div><text>${val}</text></div>`
return x
// return html`<strong>${parts[1]}</strong>, ${parts[2]}`;
}
}
}
})
Insert cell
desktopreference = table(desktop, {
paged: 4,
columns: {
ColourX: {
formatter(val, i) {
// A formatter that changes its behavior based on the row index,
// so we don’t repeat the $ for every row.
var x = html`<svg width="20" height="20" style="padding-top:.4em; padding-bottom:0em; padding-left:.2em; padding-right:.2em;">
<rect width="12.5" height="12.5" style="fill:${val};stroke-width:2;stroke:rgb(0,0,0)" />
</svg>`
return x
// return i === 0 ? d3.format('$.2f')(val) : d3.format('.2f')(val);
}
},
ColourY: {
formatter(val, i) {
// A formatter that changes its behavior based on the row index,
// so we don’t repeat the $ for every row.
var x = html`<svg width="20" height="20" style="padding-top:.4em; padding-bottom:0em; padding-left:.2em; padding-right:.2em;">
<rect width="12.5" height="12.5" style="fill:${val};stroke-width:2;stroke:rgb(0,0,0)" />
</svg>`
return x
// return i === 0 ? d3.format('$.2f')(val) : d3.format('.2f')(val);
}
},
ColourZ: {
formatter(val, i) {
// A formatter that changes its behavior based on the row index,
// so we don’t repeat the $ for every row.
var x = html`<svg width="20" height="20" style="padding-top:.4em; padding-bottom:0em; padding-left:.2em; padding-right:.2em;">
<rect width="12.5" height="12.5" style="fill:${val};stroke-width:2;stroke:rgb(0,0,0)" />
</svg>`
return x
// return i === 0 ? d3.format('$.2f')(val) : d3.format('.2f')(val);
}
},
Category: {
formatter(val) {
// Formatters can also return HTML values! Here’s
// one that highlights first names.
// let parts = val.match(/([^,]*)\,(.*)/);
var x = html`<div><text>${val}</text></div>`
return x
// return html`<strong>${parts[1]}</strong>, ${parts[2]}`;
}
}
}
})
Insert cell
Insert cell
mobile = d3.csv(
await FileAttachment("mobile@2.csv").url()
)
Insert cell
desktop = d3.csv(
await FileAttachment("desktop@1.csv").url()
)
Insert cell
colours = d3.csv(
await FileAttachment("colours.csv").url()
)
Insert cell
data = d3.csvParse(await FileAttachment("jail.csv").text(), (d, i, columns) => (d3.autoType(d), d.total = d3.sum(columns, c => d[c]), d)).sort((a, b) => b.total - a.total)
Insert cell
mono = d3.csvParse(await FileAttachment("mono2@1.csv").text(), (d, i, columns) => (d3.autoType(d), d.total = d3.sum(columns, c => d[c]), d)).sort((a, b) => b.total - a.total)
Insert cell
series = d3.stack()
.keys(data.columns.slice(1))
(data)
.map(d => (d.forEach(v => v.key = d.key), d))
Insert cell
monoseries = d3.stack()
.keys(mono.columns.slice(1))
(mono)
.map(d => (d.forEach(v => v.key = d.key), d))
Insert cell
x = d3.scaleLinear()
.domain([0, d3.max(series, d => d3.max(d, d => d[1]))])
.range([margin.left, width - margin.right])
Insert cell
y = d3.scaleBand()
.domain(data.map(d => d.name).concat(mono.map(d => d.name)))
.range([margin.top, height - margin.bottom])
.padding(0.3)
Insert cell
color = d3
.scaleOrdinal()
.range(["#ED1A25", // Trafalgar Square (Red)
"#FEA616", // Vine Street (Orange)
"#FEA616", // Marlborough Street (Orange)
"#FFFFFF", // Kings Cross Station (White)
"#BFDBAE", // Go (Mint)
"#FFFFFF", // Marlyebone Station (White)
"#BFDBAE", // Free Parking (Mint)
"#fea616", // Bow Street (Orange)
"#DB3A98", // Pall Mall (Pink)
"#FFFFFF", // Fenchurch Street Station (White)
"#ED1A25", // The Strand (Red)
"#BFDBAE", // Water Works (Mint)
"#BFDBAE", // Electric Company (Mint)
"#ED1A25", // Fleet Street (Red)
"#ECE006", // Leicester Square (Yellow)
"#BFDBAE", // Community Chest (Mint)
"#ECE006", // Coventry Street (Yellow)
"#A8D6E9", // Euston Road (Cyan)
"#519F46", // Regent Street (Green)
"#DB3A98", // Northumberland Avenue (Pink)
"#ECE006", // Piccadilly (Yellow)
"#519F46", // Oxford Street (Green)
"#A8D6E9", // Pentonville Road (Cyan)
"#0271B9", // Mayfair (Blue)
"#A8D6E9", // The Angel, Islington (Cyan)
"#DB3A98", // Whitehall (Pink)
"#BFDBAE", // Income Tax (Mint)
"#519F46", // Bond Street (Green)
"#FFFFFF", // Liverpool Street Station (White)
"#BFDBAE", // Community Chest (Mint)
"#590D38", // Whitechapel Road (Maroon)
"#0271B9", // Park Lane (Blue)
"#BFDBAE", // Luxury Tax (Mint)
"#590D38", // Old Kent Road (Maroon)
"#BFDBAE", // Community Chest (Mint)
"#BFDBAE", // Chance (Mint)
"#BFDBAE", // Chance (Mint)
"#BFDBAE", // Chance (Mint)
])
Insert cell
jailcolor = d3
.scaleOrdinal()
.range(["#7B2A83", "#BBBBBB"])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${margin.top})`)
.call(d3.axisTop(x).ticks(width / 100, "s").tickFormat(d => d + "%"))
.call(g => g.selectAll(".domain").remove())
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y).tickSizeOuter(0))
.call(g => g.selectAll(".domain").remove())
Insert cell
formatValue = x => isNaN(x) ? "N/A" : x.toLocaleString("en")
Insert cell
height = (mono.length + data.length) * 22.5 + margin.top + margin.bottom
Insert cell
margin = ({top: 30, right: 10, bottom: 0, left: 175})
Insert cell
table = (data, options) => {
options = Object.assign({}, defaultOptions, options);
const { sortable, rank, paged } = options;
let sortKey = undefined;
let sortDirection = true;
let page = 0;
if (sortable && rank) {
throw new Error("A table can either be ranked or sortable, but not both");
}
let columns = Object.keys(data[0]).map(key => {
const opts = options.columns[key] || {};
return {
key: key,
type: opts.type || typeof data[0][key],
options: opts
};
});

function bake() {
if (sortKey) {
data = data.slice().sort((a, b) => {
let as = a[sortKey];
let bs = b[sortKey];
// make this sort stable
if (as == bs) return JSON.stringify(a).localeCompare(JSON.stringify(b));
let res = as > bs ? 1 : as < bs ? -1 : 0;
if (sortDirection) res = -res;
return res;
});
}
let rows = data.slice(page * paged, page * paged + paged);
let pages = Math.ceil(data.length / paged);
return html`<div><div>
<style>
.pretty-pager {
text-align: center;
}
.pretty-pager button {
padding-left: .5rem;
padding-right: .5rem;
cursor: pointer;
background: #efefef;
border: 2px solid #efefef;
font-family: Alegreya;
font-size: 12px;
font-weight: 400;
font-style: italic;

visibility:hidden;
}
.pretty-pager button:hover {
background: #ffffff;
border: 2px solid #efefef;
}
.pretty-table.normal {
font-family: Alegreya;
font-size: 13px;
text-align: center;
font-weight: 500;
font-style: italic;
}
.pretty-table.normal th,
.pretty-table.normal td {
padding: 2px 2px;
}
.pretty-table th,
.pretty-table td {
vertical-align: top;
font-family: Alegreya;
font-size: 13px;
text-align: center;
font-weight: 500;
font-style: italic;
}
.pretty-table thead th {
font-family: Alegreya;
font-size: 13px;
font-weight: 500;
font-style: normal;

visibility:hidden;
}
.pretty-table thead th.column-type-number string {
order: 1;
}
.pretty-table th.sortable {
cursor: pointer;
}
.pretty-table thead th.column-type-number,
.pretty-table tbody td.cell-type-number,
.pretty-table tbody td.cell-rank {
text-align:right;
}
.pretty-table tbody td.cell-type-number,
.pretty-table tbody td.cell-rank {
font-family: Alegreya;
font-size: 14px;
}
.pretty-table tbody td.cell-rank {
padding-right: 1em;
color: #666;
}

</style>
<table class='pretty-table ${options.style}'>
${
options.header === false
? ``
: html`<thead>
${rank ? html`<th></th>` : ""}
${columns.map(c => {
return th(c, sortKey, sortDirection, sortable);
})}
</thead>`
}
<tbody>
${rows.map(
(row, i) => html`<tr>
${rank ? html`<td class='cell-rank'>${i + 1}</td>` : ""}
${columns.map(c => {
let displayValue = (c.options.formatter || identity)(
row[c.key],
i,
row
);
if (
displayValue instanceof window.HTMLElement &&
displayValue.tagName == "TD"
) {
return displayValue;
}
return html`<td class='cell-type-${
c.type
}'>${displayValue}</td>`;
})}
</tr>`
)}
</tbody>
</table>
${
pages
? html`<div class='pretty-pager'>
${Array.from({ length: pages }).map(
(_, i) => html`<button data-page="${i}">${i+1}</button>`
)}
</div>`
: ""
}
</div></div>`;
}

let dom = bake();

function rerender() {
dom.firstChild.remove();
dom.appendChild(bake().firstChild);
}

// dom.addEventListener("click", e => {
// if (e.target.tagName === "TH" && sortable) {
// if (sortKey == e.target.dataset.key) {
// sortDirection = !sortDirection;
// }
// sortKey = e.target.dataset.key;
// rerender();
// }
// if (e.target.tagName === "BUTTON") {
// if (e.target.dataset.action) {
// switch (e.target.dataset.action) {
// case "next":
// page++, rerender();
// break;
// case "previous":
// page--, rerender();
// break;
// }
// } else if (e.target.dataset.page) {
// (page = parseInt(e.target.dataset.page)), rerender();
// }
// }
// });

return dom;
}
Insert cell
th = (c, sortKey, sortDirection, sortable) => {
let {
options: { title }
} = c;
let sortIndicator = sortKey && sortDirection ? "↑" : "↓";
let arrow = html`<span style='${
sortKey === c.key ? "" : "visibility:hidden"
}'>${sortIndicator}</span>`;
let displayedTitle = title !== undefined ? title : c.key;
return c.type === "number"
? html`<th style="
padding-bottom: .5em;"
data-key="${c.key}"
class='column-type-${c.type} ${sortable ? "sortable" : ""}'>
${arrow}${displayedTitle}
</th>`
: html`<th style="
padding-bottom: .5em;"
data-key="${c.key}"
class='column-type-${c.type} ${sortable ? "sortable" : ""}'>
${displayedTitle}${arrow}
</th>`;
}
Insert cell
identity = i => i
Insert cell
defaultOptions = ({ columns: {}, style: 'normal', paged: 25 })
Insert cell
d3 = require("d3@5")
Insert cell
import {legend} from "@d3/color-legend"
Insert cell
import {alegreya} from "@mrdcy/globals"
Insert cell
// font = alegreya()
Insert cell
Insert cell
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