Published
Edited
Jan 28, 2022
1 fork
Importers
41 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Create a plot
Plot.plot({
// Pass in an array of desired marks
marks: [
// Create a line mark
Plot.line(data, {
x: "date", // feature for the x channel
y: "value", // feature for the y channel
stroke: "brand", // feature for the stroke
}),

// Create a dot mark
Plot.dot(data, {
x: "date", // feature for the x channel
y: "value", // feature for the y channel
fill: "brand", // feature for the stroke
}),

// Display a horizontal line at y = 0
Plot.ruleY([0])
],
// Include a legend for the color channel
color: {
legend: true,
}
})
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
Inputs.table(data)
Insert cell
Insert cell
Insert cell
Insert cell
ss = require("simple-statistics@7.7.3")
Insert cell
import {
createDemo,
intro,
getConfig,
plotWidth,
dataDescription,
download,
welcomeBox,
inputStyles,
allCheatsheets,
deck
} with { data, evalCodeStr } from "@observablehq/cheatsheet-utilities"
Insert cell
import {toc} from "@nebrius/indented-toc"
Insert cell
Insert cell
youtubeImg = await FileAttachment("youtubeIcon.png").url()
Insert cell
googleImg = await FileAttachment("googleImg@1.png").url()
Insert cell
androidImg = FileAttachment("androidImg.png").url()
Insert cell
googleCloudImg = FileAttachment("googleCloudImg.png").url()
Insert cell
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' */
events = await FileAttachment("purchase_data.csv").csv({typed:true})
Insert cell
// Aggregate the data
data = aq.from(events)
.derive({date: aq.escape(d => d3.utcDay(d.date))})
.groupby("date", "brand")
.rollup({value: d => aq.op.sum(d.price_in_usd), count: aq.op.count()})
.orderby("date", "brand")
.objects()
Insert cell
images = ([
{brand: "YouTube", imgUrl: youtubeImg},
{brand: "Google", imgUrl: googleImg},
{brand: "Google Cloud", imgUrl: googleCloudImg},
{brand: "Android", imgUrl: androidImg},
])
Insert cell
imageData = aq.from(data)
.groupby("brand")
.rollup({value: d => aq.op.sum(d.value)})
.join_right(aq.from(images))
Insert cell
// Get the sales on the first and last day for each brand for the arrow example
wideData = aq
.from(data)
.groupby("brand")
.filter((d) => d.date === aq.op.min(d.date) || d.date === aq.op.max(d.date))
.derive({
time: (d) => (d.date === aq.op.min(d.date) ? "startValue" : "endValue")
})
.derive({ startDate: (d) => aq.op.min(d.date) })
.derive({ endDate: (d) => aq.op.max(d.date) })
.select(aq.not("date", "count"))
.groupby("brand", "startDate", "endDate")
.pivot("time", "value")
Insert cell
// Compute the difference between dates to draw vectors
diffData = aq.from(data)
.groupby("brand")
.derive({diff: d => d.value - aq.op.lead(d.value)})
Insert cell
// Apply a scaling function to the diffData to compute degrees of rotation
changeData = diffData.derive({diffRotate: aq.escape(d => rotateScale(d.diff))})
Insert cell
Insert cell
// Exposing variables for evaluating code strings
evalCodeStr = (str) => {
const func = new Function("changeData", "wideData", "imageData", "data", "Plot", "width", "d3", `return ${str}`)
return func(changeData, wideData, imageData, data, Plot, width, d3);
}
Insert cell
introParagraph = () => htl.html`<p style="margin-top:0px;"><a href ="https://observablehq.com/@observablehq/plot?collection=@observablehq/plot" target="_blank">Observable Plot</a> creates visualizations by combining geometric shapes (e.g., lines, dots, areas) referred to as <a href="https://observablehq.com/@observablehq/plot-marks?collection=@observablehq/plot" target="_blank">marks</a>. This approach enables multiple layered representations of your data in the same chart. Use mark functions to create geometric shapes (e.g., <a href="https://observablehq.com/@observablehq/plot-line?collection=@observablehq/plot" target="_blank"><code>Plot.line</code></a>, <a href="https://observablehq.com/@observablehq/plot-dot?collection=@observablehq/plot" target="_blank"><code>Plot.dot</code></a>, <a href="https://observablehq.com/@observablehq/plot-area?collection=@observablehq/plot" target="_blank"><code>Plot.areaY</code></a>) and pass them to your <code>Plot.plot()</code> function.</p>`
Insert cell
marksExample = () => createDemo(rectY)
Insert cell
// Rotate
// value of 0 is 90 degrees
// largest negative value is 180
// largest positive value is 0
rotateScale = {
const domainSize = d3.max(d3.extent(diffData, (d) => d.diff))
return d3
.scaleLinear()
.domain([-domainSize, domainSize])
.range([0, 180]);
}
Insert cell
Insert cell
line = ({
controls: [
{ type: "text", value: "// Sales over time \nPlot.line(data, {" },
{ param: "x", value: "date" },
{ param: "y", value: "value" },
{ param: "stroke", value: "brand" },
{
param: "strokeWidth",
type: "range",
value: 2,
min: 0.1,
max: 4,
step: 0.1
},
{ param: "curve", ...curveOptions },
{ type: "text", value: "})" }
],
plot: (config) => `Plot.plot({
marks: [
Plot.line(data, {
x: "${config.x}",
y: "${config.y}",
stroke: "${config.stroke}",
strokeWidth: ${config.strokeWidth},
curve: "${config.curve}"
})
],
${plotConfig},
${legendConfig}
})`
})
Insert cell
arrow = ({
controls: [
{ type: "text", value: "// Start/end sales \nPlot.arrow(wideData, {" },
{ param: "x1", value: "startDate" },
{ param: "x2", value: "endDate" },
{ param: "y1", value: "startValue" },
{ param: "y2", value: "endValue" },
{ param: "bend", value: true, type: "toggle" },

{ type: "text", value: "})" }
],
plot: (config) => `Plot.plot({
marks: [
Plot.arrow(wideData, {
x1: "${config.x1}",
x2: "${config.x2}",
y1: "${config.y1}",
y2: "${config.y2}",
bend: ${config.bend},
stroke: "brand"
})
],
${plotConfig},
${legendConfig},
marginTop: 30,
height: 300,
caption: "Sales on the first and last day of the dataset"
})`
})
Insert cell
areaY = ({
controls: [
{ type: "text", value: "// Sales over time \nPlot.areaY(data, {" },
{ param: "x", value: "date" },
{ param: "y", value: "value" },
{ param: "order", value: "value" },
{ param: "fill", value: "brand" },
{
param: "fillOpacity",
value: 0.5,
type: "range",
min: 0.1,
max: 1,
step: 0.05
},
{ param: "curve", ...curveOptions },
{ type: "text", value: "})" }
],
plot: (config) => `Plot.plot({
marks: [
Plot.areaY(data, {
x: "${config.x}",
y: "${config.y}",
order: "${config.order}",
fill: "${config.fill}",
fillOpacity: ${config.fillOpacity},
curve: "${config.curve}"
})
],
${plotConfig}
})`
})
Insert cell
areaX = ({
controls: [
{ type: "text", value: "// Sales over time \nPlot.areaX(data, {" },
{ param: "x", value: "value" },
{ param: "y", value: "date" },
{ param: "order", value: "value" },
{ param: "fill", value: "brand" },
{
param: "fillOpacity",
value: 0.5,
type: "range",
min: 0.1,
max: 1,
step: 0.05
},
{ param: "curve", ...curveOptions },
{ type: "text", value: "})" }
],
plot: (config) => `Plot.plot({
marks: [
Plot.areaX(data, {
x: "${config.x}",
y: "${config.y}",
order: "${config.order}",
fill: "${config.fill}",
fillOpacity: ${config.fillOpacity},
curve: "${config.curve}"
})
],
width: ${plotWidth},
marginLeft: 80,
y: { reverse: true },
height: 200
})`
})
Insert cell
dot = ({
controls: [
{ type: "text", value: "// Sales each day \nPlot.dot(data, {" },
{ param: "x", value: "date" },
{ param: "y", value: "value" },
{
param: "fill",
type: "select",
options: ["blue", "red", "brand"],
value: "brand"
},
{
param: "stroke",
type: "color",
value: "#544f4f"
},
{
param: "strokeWidth",
type: "range",
value: 1,
min: 0.1,
max: 4,
step: 0.1
},
{ type: "text", value: "})" }
],
plot: (config) => `Plot.plot({
marks: [
Plot.dot(data, {
x: "${config.x}",
y: "${config.y}",
fill: "${config.fill}",
stroke: "${config.stroke}",
strokeWidth: ${config.strokeWidth}
})
],
${plotConfig}
})`
})
Insert cell
Insert cell
barY =( {
controls: [
{
type: "text",
value: `// Sales by day \nPlot.barY(data, `
},
{
type: "text",
value: `Plot.groupX({`,
indent: 1
},
{ param: "reducer", label: "y", value: "sum" },
{
type: "text",
value: `}, {`,
indent: 1
},
{ param: "x", value: "..." },
{ param: "y", value: "value" },
{ param: "stroke", value: "brand" },
{ type: "text", value: "})", indent: 1 },
{ type: "text", value: ")" }
],
plot: () => `Plot.plot({
marks: [
Plot.barY(
data,
Plot.groupX(
{ y: "sum" },
{ x: (d) => d3.utcFormat("%a")(d.date), y: "value", stroke: "brand" }
)
)
],
width: ${plotWidth},
height: 130,
x: { ticks: 3, domain: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] },
marginLeft: 50,
${legendConfig}
})`
})
Insert cell
barX = ({
controls: [
{
type: "text",
value: `// Sales by day \nPlot.barX(data, `
},
{
type: "text",
value: `Plot.groupY({`,
indent: 1
},
{ param: "reducer", label: "x", value: "sum" },
{
type: "text",
value: `}, {`,
indent: 1
},
{ param: "x", value: `value` },
{ param: "y", value: "..." },
{ param: "stroke", value: "brand" },
{ type: "text", value: "})", indent: 1 },
{ type: "text", value: ")" }
],
plot: () => `Plot.plot({
marks: [
Plot.barX(
data,
Plot.groupY(
{ x: "sum" },
{ x: "value", y: (d) => d3.utcFormat("%a")(d.date), stroke: "brand" }
)
)
],
width: ${plotWidth},
height: 130,
marginLeft: 50,
y: { domain: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] }
})`
})
Insert cell
rectY = ({
// Control panel (the code on the left): an array of controls, one for each line (including text)
controls: [
{ type: "text", value: "// Sales histogram \nPlot.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: "y", value: "value" },
{
param: "fill",
options: ["blue", "brand", "red"],
type: "select"
},
{
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: "value",
fill: "${config.fill}",
fillOpacity: ${config.fillOpacity},
thresholds: ${config.thresholds}
}
)
)
],
marginLeft: 100,
height: 200,
width: ${plotWidth}
})`
})
Insert cell
rectX = ({
// Control panel (the code on the left): an array of controls, one for each line (including text)
controls: [
{ type: "text", value: "// Sales histogram \nPlot.rectX(data,", indent: 0 },
{ type: "text", value: "Plot.binY({", indent: 1 },

{
param: "reducer",
label: "x",
type: "select",
options: ["sum", "count"],
value: "sum"
},
{ type: "text", value: "},{", indent: 1 },
{ param: "y", value: "date" },
{
param: "fill",
options: ["blue", "brand", "red"],
type: "select"
},
{
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.rectX(
data,
Plot.binY(
{ x: "${config.reducer}" },
{
x: "value",
y: "date",
fill: "${config.fill}",
fillOpacity: ${config.fillOpacity},
thresholds: ${config.thresholds}
}
)
)
],
marginLeft: 100,
width: ${plotWidth},
y: { reverse: true },
height: 200
})`
})
Insert cell
cell = ({
controls: [
{
type: "text",
value: `// Create a heatmap \nPlot.cell(data, {`
},
{ param: "x", value: "date" },
{ param: "y", value: "brand" },
{ param: "fill", value: "value" },
// { type: "text", value: "})", indent: 1 },
{ type: "text", value: "}), \n color: {" },
{
param: "scheme",
options: ["blues", "greens", "greys", "oranges", "purples", "reds"],
value: "blues",
type: "select"
},
{ param: "reverse", type: "toggle" },
{ type: "text", value: "}" }
],
plot: (config) => `Plot.plot({
marks: [Plot.cell(data, { x: "${config.x}", y: "${config.y}", fill: "${config.fill}" })],
color: { scheme: "${config.scheme}", legend: true, reverse: ${config.reverse} },
marginLeft: 100,
x: { tickFormat: null },
width: ${plotWidth}
})`
})
Insert cell
tickY = ({
controls: [
{ type: "text", value: "// Draw each sale \nPlot.tickY(data, {" },
{ param: "x", value: "brand" },
{ param: "y", value: "date" },
{ param: "stroke", value: "brand" },
{
param: "strokeWidth",
type: "range",
min: 0.1,
max: 2,
value: 0.5,
step: 0.1
},
{ type: "text", value: "})" }
],
plot: (config) => `Plot.plot({
marks: [
Plot.tickY(data, {
x: "${config.x}",
y: "${config.y}",
stroke: "${config.stroke}",
strokeWidth: ${config.strokeWidth}
})
],
width: ${plotWidth},
height: 200,
marginLeft: 100,
y: { reverse: true }
})`
})
Insert cell
tickX = ({
controls: [
{ type: "text", value: "// Draw each sale \nPlot.tickX(data, {" },
{ param: "x", value: "date" },
{ param: "y", value: "brand" },
{ param: "stroke", value: "brand" },
{
param: "strokeWidth",
type: "range",
min: 0.1,
max: 2,
value: 0.5,
step: 0.1
},
{ type: "text", value: "})" }
],
plot: (config) => `Plot.plot({
marks: [
Plot.tickX(data, {
x: "${config.x}",
y: "${config.y}",
stroke: "${config.stroke}",
strokeWidth: ${config.strokeWidth}
})
],
width: ${plotWidth},
height: 200,
marginLeft: 100,
y: { reverse: true }
})`
})
Insert cell
text = ({
controls: [
{ type: "text", value: "// Add text lables \nPlot.text(data, " },
{ type: "text", value: "Plot.groupY({", indent: 1 },
{ param: "reducer", label: "x", value: "sum" },
{ param: "textReducer", label: "text", value: "first" },
{ type: "text", value: "}, {", indent: 1 },
{ param: "x", value: "value" },
{ param: "y", value: "brand" },
{ param: "text", value: "brand" },
{ param: "fill", value: "brand" },
{ type: "text", value: "}))" }
],
plot: (params) => `Plot.plot({
marks: [
Plot.textX(
data,
Plot.groupY(
{ x: "sum", text: "first" },
{
x: "value",
y: "brand",
text: "brand",
textAnchor: "start",
fill: "brand"
}
)
),
Plot.barX(
data,
Plot.groupY(
{ x: "sum", text: "first" },
{
x: "value",
y: "brand",
text: "brand",
textAnchor: "start",
fill: "brand"
}
)
)
],
marginRight: 50,
marginLeft: 10,
width: ${plotWidth},
y: {
label: null,
tickFormat: null,
tickSize: 0
}
})`
})
Insert cell
ruleY = ({
controls: [
{ type: "text", value: "// Add horizontal lines \nPlot.ruleY(data, " },
{ type: "text", value: "Plot.groupZ({", indent: 1 },
{
param: "reducer",
label: "y",
type: "select",
options: ["mean", "median", "min", "max"],
value: "mean"
},
{ type: "text", value: "{...}", indent: 1 },
{ type: "text", value: "})" }
],
plot: (params) => `Plot.plot({
marks: [
Plot.dot(data, {
x: "date",
y: "value",
stroke: "brand",
strokeOpacity: 0.4
}),
Plot.ruleY(
data,
Plot.groupZ({ y: "${params.reducer}" }, { y: "value", stroke: "brand" })
),
Plot.text(
data,
Plot.groupZ(
{ y: "${params.reducer}", text: "first", x: () => d3.max(data, (dd) => dd.date) },
{
y: "value",
x: "date",
fill: "brand",
text: "brand",
textAnchor: "start",
dx: 0
}
)
)
],
width: ${plotWidth},
height: 230,
x: { ticks: 3 },
y: { type: "log", label: "Log total sales" },
marginLeft: 50,
marginRight: 100
})`
})
Insert cell
ruleX = ({
controls: [
{ type: "text", value: "// Add vertical lines \nPlot.ruleX(data, " },
{ type: "text", value: "Plot.groupZ({", indent: 1 },
{
param: "reducer",
label: "x",
type: "select",
options: ["mean", "median", "min", "max"],
value: "mean"
},
{ type: "text", value: "{...}", indent: 1 },
{ type: "text", value: "})" }
],
plot: (params) => `Plot.plot({
marks: [
Plot.dot(data, { x: "value", fill: "brand", fillOpacity: 0.3 }),
Plot.ruleX(
data,
Plot.groupZ({ x: "${params.reducer}" }, { x: "value", stroke: "brand" })
)
],
width: ${plotWidth},
height: 150,
facet: {
data,
y: "brand",
marginLeft: 100
},
fy: {
axis: "left"
},
marginRight: 100
})`
})
Insert cell
image = ({
controls: [
{ type: "text", value: "// Display images" },
{ type: "text", value: "Plot.image(imageData, {", indent: 1 },
{ param: "x", value: "value" },
{ param: "y", value: "brand" },
{ param: "src", value: "imgUrl" },
{ param: "height", value: 40, type: "range", min: 5, max: 100, step: 1 },
{ type: "text", value: "})" }
],
plot: (config) => `Plot.plot({
marks: [
Plot.image(imageData, {
x: "value",
y: "brand",
src: "imgUrl",
height: ${config.height}
}),
Plot.bar
],
marginLeft: 100,
height: 200,
insetLeft: 30,
width: ${plotWidth}
})`
})
Insert cell
vector = ({
controls: [
{ type: "text", value: "// Sales slope \nPlot.vector(changeData, {" },
{ param: "x", value: "date" },
{ param: "y", value: "value" },
{ param: "rotate", value: "diffRotate" },
{ type: "text", value: "})" }
],
plot: (config) => `Plot.plot({
marks: [
Plot.vector(changeData, {
x: "date",
y: "value",
rotate: "diffRotate",
stroke: "brand"
}),
Plot.line(data, {
x: "date",
y: "value",
stroke: "brand",
strokeOpacity: 0.3
})
],
${plotConfig},
${legendConfig},
height: 300
})`
})
Insert cell
// Regression points for the scatter plot
regressionLine = ({
x1: 0,
y1: linearRegression.b,
x2: 450,
y2: linearRegression.m * 450 + linearRegression.b
})
Insert cell
// Linear regression for the link plot example
linearRegression = ss.linearRegression(data.map(d => [d.count, d.value]))
Insert cell
// Default plot properties
plotConfig = `width: ${plotWidth},
height: 130,
x: { ticks: 3 },
marginLeft: 50`
Insert cell
// Default legend properties
legendConfig = `color: {
legend: true,
width: ${plotWidth},
columns: "120px"
}`
Insert cell
// Options for curves
curveOptions = ({
type: "select",
details: "set curve",
options: [
"linear",
"step",
"step-after",
"step-before",
"basis",
"cardinal",
"catmull-rom",
"monotone-x",
"bump-x",
"natural"
],
value: "linear"
})
Insert cell
Insert cell
inputStyles
Insert cell
<style>
.demo-wrapper {
margin-top:8px !important;
}
</style>
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