Published unlisted
Edited
Feb 15, 2022
2 forks
Importers
7 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
createDemo(histConfig)
Insert cell
Insert cell
histConfig = ({
// Control panel (the code on the left): an array of controls, one for each line (including text)
controls: [
{ type: "text", value: "Plot.rectY(data,", indent: 0 },
{ type: "text", value: "Plot.binX({", indent: 1 },

{
param: "reducer",
label: "y",
type: "select",
options: ["sum", "count"],
value: "count"
},
{ type: "text", value: "},{", indent: 1},
{ param: "x", value: "date" },
{
param: "fill",
value: "blue",
type: "color",
width: 50
},
{
param: "thresholds",
options: [10, 50, "d3.utcDay", "d3.utcWeek", "d3.utcMonth"],
type: "select"
},
{
param: "fillOpacity",
type: "range",
min: 0.1,
max: 1,
value: 0.5,
step: 0.05,
},
{ type: "text", value: "}", indent: 1 },
{ type: "text", value: "})" }
],
// Function that returns a string of the plot code: input is key/value pairs of the controls
plot: (config) => `Plot.plot({
marks: [
Plot.rectY(data, Plot.binX({ y: "${config.reducer}"},
{x: "date", y:"price_in_usd", fill:"${config.fill}", fillOpacity:${
config.fillOpacity
}, thresholds: ${config.thresholds}}))
],
marginLeft:100,
width: ${plotWidth}
})`
})
Insert cell
Insert cell
Insert cell
welcomeBox()
Insert cell
intro(samplePng, md`<div>Here is a sample introduction callout</div>`)
Insert cell
Insert cell
// Makes a single input for the code form
createInput = ({
param,
type,
label = param,
min = 0,
max = 1,
options,
width = 100,
indent = 0,
...rest
}) => {
switch (type) {
case "range":
return htl.html`${Inputs.range([min, max], {
label: htl.html`${label}:`,
width: 100,
...rest
})}`;
case "select":
return Inputs.select(options, {
label: htl.html`${label}:`,
...rest
});
case "toggle":
return Inputs.toggle({
label: htl.html`${label}:`,
...rest
});

case "radio":
return Inputs.radio(options, {
label: htl.html`${label}:`,
...rest
});
case "textInput":
return Inputs.text({
label: htl.html`${label}:`,
...rest
});
case "color":
return Inputs.color({
label: htl.html`${label}:`,
width: width,
value: rest.value
});
case "text":
const space = _.range(indent)
.map((d) => " ")
.join("");
return md`~~~js\n${space}${rest.value}\n~~~`;
default:
return Inputs_const({ label, ...rest });
}
}
Insert cell
// Function to create the entire set of inputs
createForm = (config) => {
// Get inputs
const inputs = Object.fromEntries(
config.map((row) => [row.param || id_generator(), createInput(row)])
);

// Create form
const form = d3.select(Inputs.form(inputs));

// Assign randomly generated input classname to custom input element
form.classed("newInput", true);
const className = d3.select(Inputs.select([])).attr("class");
form.selectAll("form").attr("class", className);

return htl.html`<div class="code_form">
${form.node()}
<style>
.code_form {width: ${controlWidth}px; margin-top:0px; padding-top:0px;background-color: #f3f3f3; display:inline-block;}
.newInput form { margin-left: 3em; padding-top: .5em; margin-bottom:0px; padding:0px}
.newInput pre { margin-top: 3px; margin-bottom:0px; padding:0px; }
.code_form p { margin-top: 3px; margin-bottom:0px; padding:0px; font-size: 15px; }
.code_form form.${className} label {
width: 80px;
}
.code_form form.${className} {
width: auto;
}
.code_form pre {
margin:0px;
}
form.${className} input[type=color] {
width: 50px;
}
`;
}
Insert cell
// Create the entire interactive component
createDemo = (config) => {
const wStyles = Object.assign({}, wrapperStyles, config.wrapper); // additional configuration for the styles
const wrapper = htl.html`<div class="demo-wrapper" style=${wStyles}></div>`;
const buttonStyles =
config.copyButton === undefined ? copyButtonStyles : config.copyButton;

// Append containers for the plot, code, and copy button
const plotWrapper = htl.html`<div style="flex-grow:1; padding-left: 15px; margin:10px 0 10px 0; ">`;
const buttonWrapper = htl.html`<div style=${buttonStyles}>`;
const controlsWrapper = htl.html`<div style="padding:10px 10px 0px 10px; background-color:#f3f3f3; min-width:250px; position:relative">`;

// Reset button
const reset = htl.html`<text style="position:absolute; cursor: pointer; bottom: 3px; right: 10px;font-family: sans-serif; font-size: 13px; opacity: .7;">reset`;
reset.onclick = () => {
// Replace the form
const newControls = createForm(config.controls);
controlsWrapper.replaceChild(
newControls,
controlsWrapper.querySelector(".code_form")
);

// Reconnet the form to the chart.
const form = d3.select(newControls).select(".newInput");

// Add event listener
form.on("input", function () {
render(this.value);
});

// Render the plot
form.dispatch("input");
};
const controls = createForm(config.controls);
controlsWrapper.append(controls);
controlsWrapper.append(buttonWrapper);
controlsWrapper.append(reset);
wrapper.appendChild(controlsWrapper);
wrapper.appendChild(plotWrapper);

// Function to update the plot, code, and copy button
const render = (inputValue) => {
// Get the updated code
const plotCode = config.plot(inputValue);
plotWrapper.replaceChildren(evalCodeStr(plotCode));

// Update the copy button
const copyButton = makeCopyIcon(plotCode, {
instructions: `copy`
});
buttonWrapper.replaceChildren(copyButton);
};

const form = d3.select(controls).select(".newInput");

// Add event listener
form.on("input", function () {
render(this.value);
});

// Render the plot
form.dispatch("input");
return wrapper;
}
Insert cell
controlWidth = 250
Insert cell
plotWidth = (width - controlWidth - 100) < 250 ? 250 : (width - controlWidth - 100)
Insert cell
Insert cell
evalCodeStr = (str) => {
const func = new Function("data", "Plot", "width", "d3", `return ${str}`)
return func(data, Plot, width, d3);
}
Insert cell
Inputs_const = ({ label = "some label", value = "output" } = {}) => {
const form = htl.html`<form><label>${label}:</label>${md`~~~js\n"${value}"~~~`}`
return Object.assign(form, { value })
}
Insert cell
// To generate a unique ID for each input element (probably a better way to do this)
id_generator = () => {
var S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return "a" + S4() + S4();
}
Insert cell
Insert cell
inputStyles = htl.html`
<style>
.${Inputs.text().classList[0]}-input > input[type="number"] {
flex-shrink: 1;
}
</style>
`
Insert cell
wrapperStyles = ({
fontFamily: "monospace",
display: "flex",
alignItems: "top",
width: width - 10 + "px",
boxShadow: "0 0 0 1px #d3d3d3",
margin: "0 0 1px 1px",
fontSize: "18px",
flexWrap: "wrap",
position: "relative",
overflow: "visible"
})
Insert cell
Insert cell
makeCopyIcon() // it's to the right ---------->
Insert cell
makeCopyIcon = (
text = "Sample Text",
{ instructions = "copy", completed = "Copied" } = {}
) => {
const wrapper = htl.html`<div style="position:relative; white-space:nowrap;"></div>`;

// Select wrapper
const wrapperSelected = d3.select(wrapper);
// Icon
const icon = htl.html`<div style="float:right;">${copyIcon()}</div>`;
wrapper.onclick = (event) => {
event.stopPropagation();
wrapperSelected.selectAll(".hover-tip").style("opacity", 1);
wrapperSelected.selectAll(".hover-tip").select("text").text(completed);
navigator.clipboard.writeText(typeof text === "function" ? text() : text);
};

const displayText = htl.html`<div class="hover-tip arrow_box" style="opacity:0; position:absolute;pointer-events:none;right:0px; top:-40px"><text>${instructions}</text></div>`;
wrapper.append(displayText);
wrapper.append(icon);

d3.select(icon).on("mouseleave", function (event) {
wrapperSelected.selectAll(".hover-tip").style("opacity", 0);
});

wrapper.append(copyStyle);

return wrapper;
}
Insert cell
//Created on: https://cssarrowplease.com/
copyStyle = htl.html`
<style>
.arrow_box {
position: relative;
background: #f3f3f3;
border: 1px solid #d3d3d3;
border-radius:5px;
font-family: monospace;
padding:3px;
}
.arrow_box:after, .arrow_box:before {
top: 100%;
left: 50%;
border: solid transparent;
content: "";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}

.arrow_box:after {
border-color: rgba(83, 112, 130, 0);
border-top-color: #f3f3f3;
border-width: 8px;
margin-left: -8px;
}
.arrow_box:before {
border-color: rgba(211, 211, 211, 0);
border-top-color: #d3d3d3;
border-width: 9px;
margin-left: -9px;
}
.copy-icon {
opacity:.7;
}
.copy-icon:hover {
opacity:1;
}
</style>`
Insert cell
copyIcon = () => htl.html`<div class="copy-icon" style="cursor:pointer;"><svg width="65" height="30" viewBox="0 0 73 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.323242 4.12427C0.323242 1.91513 2.1141 0.124268 4.32324 0.124268H68.3232C70.5324 0.124268 72.3232 1.91513 72.3232 4.12427V28.1243C72.3232 30.3334 70.5324 32.1243 68.3232 32.1243H4.32324C2.11411 32.1243 0.323242 30.3334 0.323242 28.1243V4.12427Z" fill="#E8E8E8"/>
<path d="M10.3232 17.1243V14.0188C10.3232 13.4665 10.771 13.0188 11.3232 13.0188H18.3232C18.8755 13.0188 19.3232 13.4665 19.3232 14.0188V22.0188C19.3232 22.5711 18.8755 23.0188 18.3232 23.0188H16.3232" stroke="black" stroke-width="2"/>
<line x1="12.3232" y1="18.1243" x2="12.3232" y2="24.1243" stroke="black" stroke-width="2"/>
<path d="M13.3232 11.1243L13.3232 10.1243L20.3232 10.1243C21.4278 10.1243 22.3232 11.0197 22.3232 12.1243V20.1243H21.3232" stroke="black" stroke-width="2" stroke-linejoin="round"/>
<line x1="15.3232" y1="21.1243" x2="9.32324" y2="21.1243" stroke="black" stroke-width="2"/>
<path d="M33.6553 21.3704C35.9316 21.3704 37.5996 20.0237 37.832 18.0276H36.0957C35.8564 19.135 34.9062 19.8391 33.6553 19.8391C31.9736 19.8391 30.9277 18.4377 30.9277 16.1887C30.9277 13.9465 31.9736 12.5452 33.6484 12.5452C34.8926 12.5452 35.8428 13.3176 36.0889 14.5071H37.8252C37.6201 12.4631 35.8906 11.0139 33.6484 11.0139C30.8525 11.0139 29.123 12.9895 29.123 16.1956C29.123 19.3879 30.8594 21.3704 33.6553 21.3704ZM42.8428 21.2747C45.0781 21.2747 46.4385 19.8118 46.4385 17.385C46.4385 14.9719 45.0713 13.509 42.8428 13.509C40.6211 13.509 39.2471 14.9788 39.2471 17.385C39.2471 19.8118 40.6006 21.2747 42.8428 21.2747ZM42.8428 19.887C41.6602 19.887 40.9834 18.9709 40.9834 17.3918C40.9834 15.8127 41.6602 14.8967 42.8428 14.8967C44.0186 14.8967 44.6953 15.8127 44.6953 17.3918C44.6953 18.9709 44.0254 19.887 42.8428 19.887ZM52.2217 13.5295C51.1963 13.5295 50.3281 14.0422 49.8975 14.8899H49.7812V13.6526H48.1475V23.6057H49.8428V19.9895H49.959C50.3418 20.7825 51.1689 21.2473 52.2422 21.2473C54.1289 21.2473 55.3047 19.7639 55.3047 17.3918C55.3047 14.9993 54.1152 13.5295 52.2217 13.5295ZM51.6885 19.8391C50.5332 19.8391 49.8154 18.9026 49.8086 17.3918C49.8154 15.8811 50.54 14.9446 51.6953 14.9446C52.8574 14.9446 53.5615 15.8606 53.5615 17.3918C53.5615 18.9231 52.8643 19.8391 51.6885 19.8391ZM57.5264 23.8313C59.2012 23.8313 60.001 23.2229 60.6367 21.384L63.3369 13.6526H61.5459L59.8027 19.5178H59.6865L57.9365 13.6526H56.0771L58.75 21.1516L58.6611 21.5002C58.4424 22.2043 58.0391 22.4778 57.3213 22.4778C57.2051 22.4778 56.9521 22.4709 56.8564 22.4573V23.804C56.9658 23.8245 57.4238 23.8313 57.5264 23.8313Z" fill="#454545"/>
</svg>

</div>`
Insert cell
copyButtonStyles = ({
position: "absolute",
top: "0px",
right: "0px",
width: "0px"
})
Insert cell
Insert cell
// An introductory paragraph for each concept
intro = async (
png,
paragraph,
{
fileName = "cheatsheet",
label = "Download the PDF",
src = "https://github.com/observablehq/plot-cheatsheets/raw/main/plot-cheatsheets.pdf",
notebookUrl = null
} = {}
) => {
// Create icon wrapper
const rotate = width < 920 ? 0 : 10;
const img = await png.image({ width: 200, style: "border: 1px solid black" });
const imgWrapper = htl.html`<div style="width:280px; transform: rotate(${rotate}deg)">`;
imgWrapper.append(img);

// Text on the left
const textWrapper = htl.html`<div style="width:640px">`;
const text = htl.html`<div style="margin-bottom:25px;">`;
text.append(paragraph);

const button = download({ label, src });
if (notebookUrl !== null) text.append(notebookLink(notebookUrl));
text.append(button);
textWrapper.append(text);
// Wrapper
const wrapper = htl.html`<div style="display: flex; max-width: 920px; flex-wrap: wrap;">`;
wrapper.append(textWrapper);
wrapper.append(imgWrapper);
return wrapper;
}
Insert cell
dataDescription = () => md`
This notebook uses purchase data from the [Google Merchandise Store](https://shop.googlemerchandisestore.com/). See [this notebook](https://observablehq.com/@observablehq/google-merchandise-sales-data) for more details.`
Insert cell
download = ({
label = "Download PDF",
src = "https://github.com/observablehq/plot-cheatsheets/raw/main/plot-cheatsheets.pdf"
} = {}) => {
return htl.html`<a href="${src}" style="text-decoration: none;"><div style="cursor: pointer; padding: 6px 10px; display: inline-flex; border-radius: 5px; color: #2d48b2; border: 1px solid #d3d3d3; font-family: sans-serif;font-weight:600; font-size:14px;">${label} &nbsp; ${downloadIcon()}</div></a>`;
}
Insert cell
downloadIcon = () => svg`<svg width="18" height="18" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 14L3.99999 14C3.44771 14 2.99999 13.5523 2.99999 13L2.99999 3C3 2.44771 3.44771 2 3.99999 2L10 2M10 2L13 5M10 2L10 5L13 5M13 5L13 7.5" stroke="#2d48b2" stroke-width="2"/>
<path d="M8 12L11 14.5L14 12" stroke="#2d48b2" stroke-width="2"/>
<line x1="11" y1="9" x2="11" y2="15" stroke="#2d48b2" stroke-width="2"/>
</svg>`
Insert cell
Insert cell
rightArrow = () => svg`<svg width="20" height="20" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.5 11L11 8L8.5 5" stroke="white" stroke-width="1.6"/>
<line x1="4" y1="7.99999" x2="11" y2="7.99999" stroke="white" stroke-width="1.6"/>
</svg>
`
Insert cell
welcomeBox = (intro = "This notebooks is part of the") => {
const boxStyles = {
background: "#F5F5F5",
borderRadius: "5px",
padding: "15px",
width: "100%",
maxWidth: "640px",
minWidth: "250px"
};

return htl.html`
<div style=${boxStyles}>${intro} <a href="https://observablehq.com/@observablehq/plot-cheatsheets?collection=@observablehq/plot-cheatsheets">Plot Cheatsheets collection</a>. Each notebook in the collection provides an <em>interactive overview</em> of core Plot techniques, as well as a <em>downloadable PDF</em> for quick reference.</div>
`;
}
Insert cell
Insert cell
deck()
Insert cell
deck = (highlight, { elements = cards } = {}) => {
const topOffset = [30, 9, 0, 10, 40];
const single = highlight !== undefined;
const order = [
elements.filter((d) => !d.url.match(highlight)),
elements.filter((d) => d.url.match(highlight))
].flat();
return html`<style>
.fan {width: 740px; height: 300px}
.card {box-shadow: 0px 5px 10px 5px rgba(0, 0, 0, 0.13); position: absolute; transition: margin-top ease 0.3s; width: 380px}
.card:hover {margin-top: -10px;}
@media screen and (max-width: 30em) {
.fan {width: 100vw; height: 45vw;}
.card {width: 150px}
}
</style>
<div class="fan" style="border: 1px solid white; position:relative; overflow: hidden;border-radius:4px;">
${order.map((card, i) => {
// Get properties based on the highlighted item
const [top, zIndex, brightness, pointerEvents] = card.url.match(highlight)
? [25, i + 10, 100, single ? "none" : "all"] //
: [75, i, single ? 80 : 100, "all"];
return `<div class="card" style="top: ${topOffset[i] + top}px; left: ${
12 + i * 7
}%;
z-index: ${zIndex}; transform: rotate(${-18 + 7 * i}deg)">
<a style="pointer-events:${pointerEvents}" href=${
card.url
}><img width=100% style="filter: brightness(${brightness}%);" src=${
card.image.currentSrc
}></a>
</div>`;
})}</div>`;
}
Insert cell
// Info for cards
cards = [
{
image: await colorsPng.image(),
url: "https://observablehq.com/@observablehq/plot-cheatsheets-colors?collection=@observablehq/plot-cheatsheets"
},
{
image: await layoutsPng.image(),
url: "https://observablehq.com/@observablehq/plot-cheatsheets-layouts?collection=@observablehq/plot-cheatsheets",
},
{
image: await transformsPng.image(),
url: "https://observablehq.com/@observablehq/plot-cheatsheets-transforms?collection=@observablehq/plot-cheatsheets",
},
{
image: await scalesPng.image(),
url: "https://observablehq.com/@observablehq/plot-cheatsheets-scales?collection=@observablehq/plot-cheatsheets"
},
{
image: await marksPng.image(),
url: "https://observablehq.com/@observablehq/plot-cheatsheets-marks?collection=@observablehq/plot-cheatsheets"
}
]
Insert cell
Insert cell
Insert cell
import {toc} from "@nebrius/indented-toc"
Insert cell
Insert cell
// Purchase event data -- queried from this notebook using this query:
// https://observablehq.com/@observablehq/bigquery-e-commerce-public-data
/* select * from events
join items on events.item_id = items.id
where type = 'purchase' */
data = FileAttachment("purchase_data.csv").csv({ typed: true })
Insert cell
samplePng = FileAttachment("scales-cheatsheet.png")
Insert cell
colorsPdf = FileAttachment("colors.pdf")
Insert cell
colorsPng = FileAttachment("colors@3.png")
Insert cell
layoutsPdf = FileAttachment("layouts.pdf")
Insert cell
layoutsPng = FileAttachment("layouts@2.png")
Insert cell
marksPdf = FileAttachment("marks.pdf")
Insert cell
marksPng = FileAttachment("marks@3.png")
Insert cell
scalesPdf = FileAttachment("scales.pdf")
Insert cell
scalesPng = FileAttachment("scales@2.png")
Insert cell
transformsPdf = FileAttachment("transforms.pdf")
Insert cell
transformsPng = FileAttachment("transforms@2.png")
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