Public
Edited
Dec 18, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
tsla = d3.csvParse(
await FileAttachment("tsla.csv").text(),
d3.autoType
)
Insert cell
Insert cell
Plot.plot({
style: { fontFamily: "Roboto", fontSize: 12 },
height: 270,
y: { nice: 5 },
marks: [
Plot.lineY(tsla, {
x: "Date",
y: "Close",
stroke: "#34A853",
strokeWidth: 2
}),
// Grid and horizontal line at y=0
Plot.ruleY([0], { stroke: "lightgray" }),
Plot.gridY({ stroke: "#E8EAED", strokeWidth: 1, strokeOpacity: 0.5 }),
]
})
Insert cell
Insert cell
function withGradient({color, id = DOM.uid("gradient").id}, callback) {
return [
callback(`url(#${id})`),
() => htl.svg`<defs>
<linearGradient id=${id} gradientTransform="rotate(90)">
<stop offset=0% stop-color=${color} stop-opacity=0.3 />
<stop offset=30% stop-color=${color} stop-opacity=0.2 />
<stop offset=60% stop-color=${color} stop-opacity=0.1 />
<stop offset=80% stop-color=${color} stop-opacity=0 />
</linearGradient>
</defs>`
];
}
Insert cell
Plot.plot({
style: { fontFamily: "Roboto", fontSize: 12 },
height: 270,
y: { nice: 5 },
marks: [
// Line Chart
Plot.lineY(tsla, {
x: "Date",
y: "Close",
stroke: "#34A853",
strokeWidth: 2
}),
// Grid and horizontal line at y=0
Plot.ruleY([0], { stroke: "lightgray" }),
Plot.gridY({ stroke: "#E8EAED", strokeWidth: 1, strokeOpacity: 0.5 }),
// Area Chart
withGradient({color: "#34A853"}, fill => Plot.areaY(tsla, {
x: "Date",
y: "Close",
fill,
})),
]
})
Insert cell
Insert cell
function plotCustomPointer(data, {x, y}) {
return Plot.marks(
Plot.ruleX(
data,
Plot.pointerX({
x: x,
py: y,
stroke: "#202124",
strokeDasharray: "3,4",
opacity: 0.55,
strokeWidth: 2
})
),
Plot.dot(
data,
Plot.pointerX({
x: x,
y: y,
r: 5,
fill: "#34A853"
})
)
);
}
Insert cell
Insert cell
Plot.plot({
style: { fontFamily: "Roboto", fontSize: 12 },
height: 270,
y: { nice: 5 },
marks: [
// Line Chart
Plot.lineY(tsla, {
x: "Date",
y: "Close",
stroke: "#34A853",
strokeWidth: 2
}),
// Grid and horizontal line at y=0
Plot.ruleY([0], { stroke: "lightgray" }),
Plot.gridY({ stroke: "#E8EAED", strokeWidth: 1, strokeOpacity: 0.5 }),
// Area Chart
withGradient({color: "#34A853"}, fill => Plot.areaY(tsla, {
x: "Date",
y: "Close",
fill,
})),
// Custom Pointer
plotCustomPointer(tsla, {x: "Date", y: "Close"}),
]
})
Insert cell
Insert cell
Insert cell
htmlTooltip = ({ price, date, currency, width, height }) =>
`
<foreignObject width=${width} height=${height}>
<div style="width: ${width}px;" class="tooltip">
<span class="tooltip-price">${price} ${currency}</span><span class="tooltip-time">${date}</span>
</div>
<foreignObject/>
`
Insert cell
Insert cell
<style>
.tooltip {
background-color: #fff;
font-weight: 400;
font-size: 12px;
height: 28px;
line-height: 28px;
padding-left: 8px;
padding-right: 8px;
position: absolute;
top: 0px;
left: 0px;
border-radius: 2px;
box-shadow: 0 1px 3px rgba(0,0,0,.2), 0 1px 1px rgba(0,0,0,.14);
z-index: 2;
}

.tooltip-price {
margin-right: 5px;
color: #202124;
}

.tooltip-time {
color: rgba(0,0,0,.67);
}
</style>
Insert cell
Insert cell
Insert cell
Insert cell
function plotCustomTooltip(
data,
{ tooltipHeight, tooltipWidth, x, y, currency }
) {
return Plot.marks(
Plot.tip(
data,
Plot.pointerX({
x: x,
y: y,
// extend the tip mark render function
render: (index, scales, values, dimensions, context) => {
const g = d3.select(context.ownerSVGElement).append("g");
const [i] = index;
if (i !== undefined) {
const formatTime = d3.utcFormat("%d %b %Y");
const priceDate = formatTime(values.channels.x.value[i]);
const closePrice = values.channels.y.value[i];
// The tooltip starts after the left margin in the x-axis to avoid blocking the y-axis labels
// We start to translate the tooltip when the pointer is halfway the initial tooltip location
// We stop moving the tooltip when there's not enough width available within the container
const currentXPosition = values.x[i];
const tooltipStartPosition = dimensions.marginLeft;
const tooltipDefaultPosition = currentXPosition - tooltipWidth / 2;
const tooltipEndPosition =
dimensions.width - dimensions.marginRight - tooltipWidth;
g.attr(
"transform",
`translate(${Math.min(
currentXPosition <= tooltipWidth / 2 + dimensions.marginLeft
? tooltipStartPosition
: tooltipDefaultPosition,
tooltipEndPosition
)}, 0)`
).append(
() =>
svg`${htmlTooltip({
price: closePrice,
date: priceDate,
currency: currency,
width: tooltipWidth,
height: tooltipHeight
})}`
);
}
return g.node();
}
})
)
);
}
Insert cell
Insert cell
Plot.plot({
style: { fontFamily: "Roboto", fontSize: 12 },
height: 270,
y: { nice: 5 },
marks: [
// Line Chart and horizontal grid
Plot.ruleY([0], { stroke: "lightgray" }), // draw an horizontal line at 0
Plot.gridY({ stroke: "#E8EAED", strokeWidth: 1, strokeOpacity: 0.5 }),
Plot.lineY(tsla, {
x: "Date",
y: "Close",
stroke: "#34A853",
strokeWidth: 2
}),
// Area Chart
withGradient({color: "#34A853"}, fill => Plot.areaY(tsla, {
x: "Date",
y: "Close",
fill,
})),
// Custom Vertical Pointer
plotCustomPointer(tsla, {x: "Date", y: "Close"}),
// Custom HTML tooltip
plotCustomTooltip(tsla, {tooltipHeight: 30, tooltipWidth: 146, x: "Date", y: "Close", currency: "USD"})
]
})
Insert cell
Insert cell
<h2>Appendix</h2>
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');

blockquote {
border-left: 5px solid #eee;
padding-left: 10px;
margin-left: 0;
}
</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