Public
Edited
Feb 11
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
victimizations_by_tenure_and_income_bracket.forEach((item) => {
if (item.tenure === "Owner-Occupied") {
item.tenure = "People in Owner-Occupied Units";
}
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
renters_by_income_data = FileAttachment("renters by income.csv").csv({typed:true})
Insert cell
renters_by_networth_data = FileAttachment("renters by networth@2.csv").csv({
typed: true
})
Insert cell
renters_by_race_data = [
{
race: "White",
pct_renters: "0.28"
},
{
race: "Black",
pct_renters: "0.53"
},
{
race: "Latino",
pct_renters: "0.5"
}
]
Insert cell
victimization_among_evicted_data = [
{
pop: "Recent Experience of Eviction",
pct: 0.43
},
{
pop: "No Recent Experience of Eviction",
pct: 0.22
}
]
Insert cell
eviction_among_victims = [
{
pop: "Recent Victimization",
value: 191
},
{
pop: "No Recent Victimization",
value: 74
}
]
Insert cell
victimizations_by_age_data = FileAttachment("victimizations by age@2.csv").csv({
typed: true
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
style = html`<style>:root {
--syntax_normal: #1b1e23;
--syntax_comment: #a9b0bc;
--syntax_number: #20a5ba;
--syntax_keyword: #c30771;
--syntax_atom: #10a778;
--syntax_string: #008ec4;
--syntax_error: #ffbedc;
--syntax_unknown_variable: #838383;
--syntax_known_variable: #005f87;
--syntax_matchbracket: #20bbfc;
--syntax_key: #6636b4;
--mono_fonts: 82%/1.5 Menlo, Consolas, monospace;
}

.observablehq--expanded,
.observablehq--collapsed,
.observablehq--function,
.observablehq--import,
.observablehq--string:before,
.observablehq--string:after,
.observablehq--gray {
color: var(--syntax_normal);
}

.observablehq--collapsed,
.observablehq--inspect a {
cursor: pointer;
}

.observablehq--field {
text-indent: -1em;
margin-left: 1em;
}

.observablehq--empty {
color: var(--syntax_comment);
}

.observablehq--keyword,
.observablehq--blue {
color: #3182bd;
}

.observablehq--forbidden,
.observablehq--pink {
color: #e377c2;
}

.observablehq--orange {
color: #e6550d;
}

.observablehq--null,
.observablehq--undefined,
.observablehq--boolean {
color: var(--syntax_atom);
}

.observablehq--number,
.observablehq--bigint,
.observablehq--date,
.observablehq--regexp,
.observablehq--symbol,
.observablehq--green {
color: var(--syntax_number);
}

.observablehq--index,
.observablehq--key {
color: var(--syntax_key);
}

.observablehq--prototype-key {
color: #aaa;
}

.observablehq--empty {
font-style: oblique;
}

.observablehq--string,
.observablehq--purple {
color: var(--syntax_string);
}

.observablehq--error,
.observablehq--red {
color: #e7040f;
}

.observablehq--inspect {
font: var(--mono_fonts);
overflow-x: auto;
display: block;
white-space: pre;
}

.observablehq--error .observablehq--inspect {
word-break: break-all;
white-space: pre-wrap;
}
{ font-size: 12px !important }
<style>`
Insert cell
style2 = html`<style> body {
font-family: arial, sans-serif !important; }</style>`
Insert cell
dollar_format = d3.format("$,.0f")
Insert cell
round = d3.format(",.0d")
Insert cell
// These cells are slightly modified from @mkfreeman
addTooltips = (chart, styles) => {
const stroke_styles = { stroke: "blue", "stroke-width": 3 };
const fill_styles = { fill: "blue", opacity: 0.5 };

// Workaround if it's in a figure
const type = d3.select(chart).node().tagName;
let wrapper =
type === "FIGURE" ? d3.select(chart).select("svg") : d3.select(chart);

// Workaround if there's a legend....
const svgs = d3.select(chart).selectAll("svg");
if (svgs.size() > 1) wrapper = d3.select([...svgs].pop());
wrapper.style("overflow", "visible"); // to avoid clipping at the edges

// Set pointer events to visibleStroke if the fill is none (e.g., if its a line)
wrapper.selectAll("path").each(function (data, index, nodes) {
// For line charts, set the pointer events to be visible stroke
if (
d3.select(this).attr("fill") === null ||
d3.select(this).attr("fill") === "none"
) {
d3.select(this).style("pointer-events", "visibleStroke");
if (styles === undefined) styles = stroke_styles;
}
});

if (styles === undefined) styles = fill_styles;

const tip = wrapper
.selectAll(".hover")
.data([1])
.join("g")
.attr("class", "hover")
.style("pointer-events", "none")
.style("text-anchor", "middle");

// Add a unique id to the chart for styling
const id = id_generator();

// Add the event listeners
d3.select(chart).classed(id, true); // using a class selector so that it doesn't overwrite the ID
wrapper.selectAll("title").each(function () {
// Get the text out of the title, set it as an attribute on the parent, and remove it
const title = d3.select(this); // title element that we want to remove
const parent = d3.select(this.parentNode); // visual mark on the screen
const t = title.text();
if (t) {
parent.attr("__title", t).classed("has-title", true);
title.remove();
}
// Mouse events
parent
.on("pointerenter pointermove", function (event) {
const text = d3.select(this).attr("__title");
const pointer = d3.pointer(event, wrapper.node());
if (text) tip.call(hover, pointer, text.split("\n"));
else tip.selectAll("*").remove();

// Raise it
// d3.select(this).raise();
// Keep within the parent horizontally
const tipSize = tip.node().getBBox();
if (pointer[0] + tipSize.x < 0)
tip.attr(
"transform",
`translate(${tipSize.width / 2}, ${pointer[1] + 7})`
);
else if (pointer[0] + tipSize.width / 2 > wrapper.attr("width"))
tip.attr(
"transform",
`translate(${wrapper.attr("width") - tipSize.width / 2}, ${
pointer[1] + 7
})`
);
})
.on("pointerout", function (event) {
tip.selectAll("*").remove();
// Lower it!
// d3.select(this).lower();
});
});

// Remove the tip if you tap on the wrapper (for mobile)
wrapper.on("touchstart", () => tip.selectAll("*").remove());

// Define the styles
chart.appendChild(html`<style>
.${id} .has-title { cursor: pointer; pointer-events: all; }
.${id} .has-title:hover { ${Object.entries(styles)
.map(([key, value]) => `${key}: ${value};`)
.join(" ")} }`);

return chart;
}
Insert cell
// Function to position the tooltip
hover = (tip, pos, text) => {
const side_padding = 10;
const vertical_padding = 5;
const vertical_offset = 15;

// Empty it out
tip.selectAll("*").remove();

// Append the text
tip
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("transform", `translate(${pos[0]}, ${pos[1] + 7})`)
.selectAll("text")
.data(text)
.join("text")
.style("dominant-baseline", "ideographic")
.text((d) => d)
.attr("y", (d, i) => (i - (text.length - 1)) * 15 - vertical_offset)
.style("font-weight", (d, i) => (i === 0 ? "normal" : "normal"));

const bbox = tip.node().getBBox();

// Add a rectangle (as background)
tip
.append("rect")
.attr("y", bbox.y - vertical_padding)
.attr("x", bbox.x - side_padding)
.attr("width", bbox.width + side_padding * 2)
.attr("height", bbox.height + vertical_padding * 2)
.style("fill", "white")
.style("stroke", "#d3d3d3")
.lower();
}
Insert cell
// To generate a unique ID for each chart so that they styles only apply to that chart
// from mkfreeman
id_generator = () => {
var S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return "a" + S4() + S4();
}
Insert cell
tooltipPlugin = (Plot) => {
const { plot } = Plot;
Plot.plot = ({ tooltip, ...options }) => addTooltips(plot(options), tooltip);
return Plot;
}
Insert cell
// automatically add tooltips to every Plot in a notebook
// this allows users to do import { Plot } from "@mkfreeman/plot-tooltip
Plot = tooltipPlugin(await require("@observablehq/plot"))
Insert cell
violent_incidents_by_type_and_tenure_data.filter((d) => d.offense_type == 4)
Insert cell
// https://getbootstrap.com/docs/4.1/getting-started/introduction/#css
//html`<code>css</code> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" crossorigin="anonymous">`
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

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