Index
---
https://vitepress.dev/reference/default-theme-home-page
layout: home
titleTemplate: "The JavaScript library for exploratory data visualization"
head:
- - link
- rel: canonical
href: https://observablehq.com/plot/
- - meta
- name: title
content: Observable Plot
- - meta
- name: description
content: The JavaScript library for exploratory data visualization
- - meta
- name: twitter:card
content: summary_large_image
- - meta
- name: twitter:site
content: "@observablehq"
- - meta
- property: og:description
content: The JavaScript library for exploratory data visualization
- - meta
- property: og:image
content: https://static.observableusercontent.com/thumbnail/64f414fef8a91248865f5759641b0cf537bc87c0aaf57dc368ffe673013eccaa.jpg
- - meta
- property: og:site_name
content: Observable
- - meta
- property: og:title
content: Observable Plot
- - meta
- property: og:type
content: article
- - meta
- property: og:url
content: https://observablehq.com/plot/
hero:
name: "Observable Plot"
text: "The JavaScript library for exploratory data visualization"
tagline: "Create expressive charts with concise code"
image:
src: /plot.svg
alt: Observable Plot
actions:
- theme: brand
text: Get started
link: /getting-started
- theme: alt
text: What is Plot?
link: /what-is-plot
- theme: alt
text: Examples
link: https://observablehq.com/@observablehq/plot-gallery
features:
- title: Marks
details: "Plot doesn’t have chart types. Instead, it has layered geometric shapes such as bars, dots, and lines."
link: /features/marks
- title: Scales
details: "Scales map an abstract value such as time or temperature to a visual value such as position or color."
link: /features/scales
- title: Transforms
details: "Derive data on-the-fly while plotting, say to bin quantitative values or compute a rolling average."
link: /features/transforms
- title: Facets
details: "Small multiples facilitate comparison by repeating a plot across partitions of data."
link: /features/facets
- title: Projections
details: "Plot supports GeoJSON and D3’s spherical projection system for geographic maps."
link: /features/projections
- title: Built with D3
details: "Plot is built by the same team as D3. If you know some D3, you’ll be right at home with Plot."
link: https://d3js.org
linkText: Visit D3
- title: Plot without code
details: With Observable’s chart cell, quickly create plots with a GUI, then eject to code to customize.
link: https://observablehq.com/@observablehq/chart-cell
linkText: Try chart cell
- title: Built by Observable
details: Plot is developed by Observable, the platform for collaborative data analysis.
link: https://observablehq.com
linkText: Visit Observable
---
<style>
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: linear-gradient(-30deg, var(--hero-brand-contrast), var(--vp-c-brand-1));
}
:root.dark .VPHero .VPImage {
filter: drop-shadow(0 4px 8px black);
}
</style>
<script setup>
import {onMounted} from "vue";
onMounted(() => {
const p = document.querySelector(".VPHero .text");
const s = document.querySelector("#hero-text");
if (!p || !s) return;
while (p.lastChild) p.lastChild.remove();
p.append(s);
});
</script>
<template>
<div id="hero-text">The JavaScript library for
<span style="display: inline-block; position: relative;">exploratory<svg style="color: var(--vp-c-brand-3); position: absolute; z-index: -1; top: 1em; left: 0.2em; width: calc(100% - 0.7em); height: auto;" width="240" height="11" viewBox="0 0 240 11" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M20.766 10.187c.939-.024.386-.885.552-1.401 1.105-.301.553.626.962 1.061.685-.263 1.171-1.1 1.696-1.085.044.144.15.191.044.378.697-.736 2.21-.134 2.995-1.052a.55.55 0 0 1 .127.215 3.35 3.35 0 0 1-.204-1.204c.42-.034.751-.593.94 0-.255 0-.266.23-.377.416l.426-.273c.448.813-.586.316-.553.927.84-.306 1.802-1.037 2.476-.831.182.803-1.525.339-.608 1.023l-1.033-.268c.85 1.248-.625-.057.171 1.276 1.348.177 1.47-.478 2.818-.3.276-.479-.132-.66.144-1.124 1.857-.885 1.602 1.984 2.94.846.337-.555.42-1.582 1.442-1.08l-.276.889c1.298.038.668-1.348 2.06-.784-.226.368-1.005.344-.8.444.917.689.59-.545 1.27-.569l.16.827c1.371-.181 2.863-.827 4.388-1.037-.072.249-.326.512.044.746 1.912-.478 4.123-.058 6.007.368l.68-.727c.05.015.095.04.132.074a.275.275 0 0 1 .077.118c.014.044.015.091.004.136a.27.27 0 0 1-.07.122c.74.243 0-.445.354-.732.414-.062.552.383.315.603 1.248-.636 3.586-1.401 4.973-.694l-.254.22c1.06.249 1.105-1.477 2.127-.855l-.182.129c2.293.23 4.785-.478 6.564.52.293-1.017 2.272.393 2.365-1.022 1.327.664.967.927 2.813 1.348.492.052.702-.899 1.299-1.061l.044.731.79-.794.47.87.552-.205a.66.66 0 0 1-.332-.2.517.517 0 0 1-.132-.33c.873-.354 2.177.477 2.21.831l2.078-.679c-.039.301-.387.411-.657.607 1.105-.779.226.77 1.232.053-.144-.163.06-.44.077-.588.553.435 1.691.416 2.547.205l-.149.512c1.558.1 3.271-.31 5.018-.335-.636-.224-.514-1.109 0-1.204l.226.774c.32-.478-.552-.282.122-.884.652.076.464.875.094 1.138l.784-.287c.056.23-.127.358-.165.655.309-.478 1.387.75 1.834-.096l.05.23c1.746-.03 2.53-.316 3.95-.383 0-.674.553-.535.984-1.085 1.05.196 2.21.707 3.482.63.878-.343.243-.568.635-.955.077.612 1.332.535.69.985a15.353 15.353 0 0 0 3.83-.68c-.21-.243-.447-.353-.331-.563a.738.738 0 0 1 .275.01c.09.02.173.058.245.11a.58.58 0 0 1 .169.188c.04.072.061.151.062.232l-.088.067c2.127-.956 4.973 1.706 6.669.41l-.099.068 1.763-.684c.817.1-.481.478.127.842 1.9-1.043 3.022.12 4.586-.574 1.243 1.793 4.327-.167 5.979.956l-.1-.42c.426-.421.52.234.835.33-.05-.33-.464-.378-.205-.613 3.598-.545 7.438.598 11.129.956 1.348.11.757-2.203 2.465-1.195l-.481.794c2.719-.956 5.564 0 8.233-.77-.154.182-.16.416-.425.416.552.574 2.083.034 2.094-.435.42.053.1.425.354.665.552.339 1.42-.732 1.718-.158.05.09-.16.186-.265.23.37-.278 1.719.076 1.365-.589 1 .32 1.917-.287 2.713.105.553-.736 1.713.364 1.884-.683-.077 1.08 1.752.875 2.387.377-.215.326.553.345.299.794.718 0 1.381-.206 1.265-.76 1.315 1.305 2.686-1.018 3.415.645a45.888 45.888 0 0 1 6.078-1.17c-.082 1.075-2.138.09-2.066 1.218 1.834-.425 2.906-1.343 4.719-1.066.47.153-.276.478-.437.65 1.835-.43 3.537.148 5.172-.42 0 .1-.182.21-.348.291.321-.033.741.167.713-.325l-.315.13c-.497-.718 1.304-1.468 1.365-1.841-.553 1.396 1.602.377.707 2.137a.73.73 0 0 0 .337-.263.58.58 0 0 0 .1-.383c.315.1.409.297.083.665 1.155-.254.757-.78 1.801-.75 0 .233-.221.324-.337.601.553-.478 1.078-.908 1.951-.697-.056.143.044.33-.216.325 1.509-.048 2.603-1.195 4.249-.722-.513 1.023.553.349.625 1.243l.895-.254-.348-.44c.785.034 1.492-.602 2.155-.296l-.591.354 1.47-.139-.824-.354c.807-.444-.055-1.132.978-.86-.21.086.785.029 1.177.56.398-.278.801-.57 1.376-.335.138.291-.149.984-.055 1.176.398-.736 1.834-.168 2.337-.956-.143.227-.192.49-.138.745l.337-.597c.359.2.409.296.337.669 1.105.134-.309-1.138.967-.626-.105.048-.055.138-.27.23 1.287.277 2.519-.335 3.702 0 .326.903-1.05.195-.669.955 1.724-.129 3.592-.999 5.25-.74l-.31-.106c.277-1.262 1.221.66 2.083.086-.21.086-.298.693-.237.555 1.105.234 2.343-.249 4.083-.603l-.226.32c.657.311 1.763.216 2.481.383.226-.315.641-.253.403-.731 2.166 1.912 4.305-.89 6.228.726-.238 0-.553.268-.387.273l1.702-.244c-.111-.554-.21-.34-.553-.784.124-.163.292-.298.489-.392.198-.094.419-.145.644-.148-.774.34-.028.884.287 1.205-.049-.173.072-.354.05-.526.846 1.008.199-1.11 1.376-.407l-.077.287c.458-.134.889-.478 1.37-.401.177.645-.492.282-.552.803.685 0 1.403-1.162 1.994-.507-.298.167-.718.158-1.016.325.641.77.729.583 1.221.717h-.044l1.138.378-.282-.21c.928-1.635 1.752-.25 2.951-1.3-1.166.994-.21.592-.332 1.309.288.21.724.454.586.65.553-.564.89.478 1.696-.34 0 .235.581.044.431.627.713-.163-.149-.411-.077-.703 1.133-.76 2.514 1.061 4.139.029 1.376-.397 1.658-1.171 2.94-1.515.403.392-.393.836-.393.836.267.161.581.255.906.27a1.97 1.97 0 0 0 .934-.184c-.138.196 0 .373.172.64.519-.038.386-.831 1.05-.477a3.24 3.24 0 0 1-.553.918c.619-.192 1.243-.603 1.884-.79.149.412-.409.603-.646.856.718-.153 1.851-.296 2.105-.927l-.442-.248c.26 0 .105.559-.094.669-.63.478-.862-.258-.884-.478l.459-.134c-.387-1.382-1.818.148-2.719.033l.431-.956-.973.784c-.182-.263-.287-.822.166-.956-.624-.516-.591.33-1.105-.239-.055-.086-.028-.134.033-.172l-.646.273c.132-.201-.072-.703.309-.545-1.105-.617-1.873.674-2.26-.096l.099-.057c-1.596.272-.193.721-1.414 1.534l-.713-1.83-.188.721c-.16-.033-.481-.1-.409-.387-.63.478.089.32-.287.78-.752-.699-2.172.229-2.293-.957-.31.545.729.478-.127.813-.183-1.258-.978.181-1.658-.416.254-.636.917-.273.226-.875-.486 1.076-1.386-.282-2-.096-.066.87-1.332.32-2.354.579.078-.292-1.89-.54-2.818-.885l.033-.148c-.221.87-1.182.674-1.901.832a.906.906 0 0 1 .132-.55c.102-.169.258-.31.449-.406h-.669a.979.979 0 0 1-.34.327 1.167 1.167 0 0 1-.478.151l.194-.65c-.885 0-1.813.712-2.94.244-.083.607.84 1.725-.381 2.103-.034-.335-.056-.899.27-1.028-.105.043-.381.263-.585.12l.502-.545c-.508-.258-.287.478-.701.397 0-.478-.293-.35-.221-.722.11-.038.359.205.525 0a1.931 1.931 0 0 1-.691-.264 1.649 1.649 0 0 1-.503-.487c.028.268-.028.636-.37.684-.89 0-.282-.574-.79-.832-.227.325-.78-.033-.824.674-.259 0-.293-.34-.387-.535-.469.3-2.149.033-1.657.793l.116.053s-.05 0-.078.033c-1.525.66-3.105-.478-4.608-.224V3.34c-.895.244-1.984.106-2.636.593a.711.711 0 0 1-.402-.28.553.553 0 0 1-.084-.442c-.691.158-.774.416-1.746 0 .701-.396-.221-.373.713-.287-.879-.224-1.067-.607-2.039 0 .342-.597-.641-.774-1.067-.602l.608.445c-.436.053-.88.039-1.31-.043l.254-.794c-1.784-1.004-3.315 1.578-4.647-.067-.497.545.973.411.553 1.052-.829-.124-1.658-1.286-1.929-1.29-1.132-.479-1.105 1.137-2.282.812a.818.818 0 0 1 .031.774.938.938 0 0 1-.264.323 1.11 1.11 0 0 1-.397.198c-.829-.124-.994-1.214-.464-1.434.205 0 .299.072.288.168.27-.096.629-.21.303-.526l-.116.282c-.403-.297-1.552-.292-1.271-.75-.635.257-.281.477.183.616-1.061-.435-1.658-.053-2.763-.344.171.162.326.478.155.478-1.608-.378-.724.526-1.824.636-.608-.445.249-1.033-.862-.684-.668-.306-.127-.755.149-.985-1.016.536-1.867-.387-2.442-.478l.553-.22a1.892 1.892 0 0 1-.846.12l.293.573c-.309-.105-.553-.11-.553-.348-.326.368.227.956-.42 1.434-.403-.297-1.265.286-1.392-.478 1.298.272-.127-.76.978-.866a1.102 1.102 0 0 1-.851.024c-.044-.086.044-.157.133-.2-1.233-.689-.592.846-1.879.807.171-.42-.287-.808-.497-.721.519 0 .237.712-.249 1.027-.823-.34-.906.235-1.337.187l.491.162c-.176.426-.585.364-1.165.478-.045-.33.524-.22.326-.368-.652.736-1.437-.793-2.338-.306-.409-.291-.027-.798-.387-.999-1.011.54-1.077-.588-2.133-.148.293.574.349.435-.403.985l1.735-.387-1.105.822c.525 0 1.105-.35 1.42-.249-.553.478-.481.316-.238.794-.701-.86-1.425.478-2.21-.1l.044-1.41c-1.232-.641-2.21.702-3.823.334l.513.248c-.221.56-.994.072-1.519.292.055-.478-.271-.645-.492-.956.028.349-1.177-.043-1.337.899l-.707-.627c-1.305-.267-1.503 1.33-2.763 1.157.381-.507-.183-.846.657-1.21-.414 0-.79-.095-.801.23-.276-.263-1.199.646-1.575.215-.182.206-.243.698-.713.655a.337.337 0 0 1 0-.234c0 .234-.735.31-.331.837-1.271-1.478-3.592.095-4.708-1.172-.936.165-1.883.277-2.835.335.05-.139 0-.234.16-.186-1.143-.44-.707 1.352-2.005.86-.664-.765.69-.411.276-.703-.171-1.553-1.564.21-2.437-.702l.21-.091c-.663-.555-1.608.564-2.713.454a.326.326 0 0 0 0-.234c-.746.784-2.155 1.051-3.205 1.271.326-.607.475-.32.276-.956-.47.091.138.99-.801 1.167-.304-.33-.984-.622-1.078-1.282l.89-.019c-.459-.85-1.149.034-1.613-.114l.055-.368c-1.36.124-1.376 1.06-2.835.999l.155.282c-.796.956-.674-.521-1.465.172l-.248-.956c-.871.453-1.797.82-2.763 1.094.552-.698 1.658-1.06 2.315-1.477-.519 0-1.774.072-2.044.54.21-.09.475-.325.685-.181a2.832 2.832 0 0 1-1.094.83 3.298 3.298 0 0 1-1.42.27c.171-1.832-2.713-.455-3.482-1.865-1.834.693-3.652-.258-5.796-.13.774 1.435-.625.049-.481 1.507-.497.1-.685.076-.729 0l-1.525-.86c-.365-.421.469-.326.42-.65-1.106-.106-.465-.618-1.194-1 .155.521-.37.75-1 .56l.901.659c-1.52.793-1.338-1.214-2.868-.43l.48-.478c-.79.277-2.917 0-3.674 1.204-.144-.167-.332-.564 0-.674-1.89-.148-4.183 1.31-5.664.612l.138-.358c-.348.105-.602.678-1.05.325 0-.148.138-.359 0-.378-.182.124-.923.64-1.392.44l.386-.411c-1.85-.44-2.807 1.023-4.343 1.29 0-1.051-1.475-1.376-2.21-1.53V.685c-2.15-.086-3.625.956-5.598 1.4-1.265-1.118-4.188-.392-6.194-.99.31.182 0 .818-.37.957-.475-.206-1.266.755-1.221-.21h.165c-.375-.957-1.326-.67-2.072-.675l-.083 1.267c-2.006-1.778-5.106.813-6.227-.803-.459.33-1.045.34-1.498.67v-.68a12.396 12.396 0 0 0-3.575 0l.31-.478c-.912 0-1.072 1.98-1.912 2.042l-.288-1c-1.591.053-3.232-.774-4.763.192 0-.148.055-.445.31-.478-.746 0-2.918-.588-2.587.788-.06-.903-1.657-.038-2.48.388l.104-.689c-.685.875-.701 1.11-1.696 1.377-.243-.076-.238-.526.088-.368-.812-.32-.59.655-1.574.33l.342-.435c-.823-.029-.746.2-1.177.707-.503.287-1.564-.114-1.713-.712-.094.368-.52.875-1.011.717a.38.38 0 0 1 .013-.245.442.442 0 0 1 .164-.2c-1.393-.406-2 .851-2.973.235a.553.553 0 0 0-.182-.392 9.431 9.431 0 0 1 1.89.028c0-.616-.912-.688-.255-1.563-.685.478-1.845 1.54-2.713 1.286a.84.84 0 0 1-.1-.215l.061-.072a.668.668 0 0 0-.295 0 .61.61 0 0 0-.257.125 1.992 1.992 0 0 0-.718-.158c-.128-.507-1.023-.234-1.465-.244.072.67-.508.583.06 1.119-.07-.048.078-.086.366-.125a.528.528 0 0 0 .188-.076l-.028.062c.287-.033.663-.062 1.105-.09-.332.358-.68.654-1.183.3-.204.445-.43.894-.552 1.11-.647-.914-1.83-1.377-2.022-1.946-1.321.43-3.145.368-3.918 1.663-.376.177-.459-.344-.614-.535.216-.139.476-.13.586-.316-.74.354-2.249.216-2.381 1.105-.984-.364.491-.837-.818-.636l.166-.277c-2.675-1.291-4.09 2.433-7.068.755.204.105.304.148.354.296-3.316-.645-6.709 1.038-10.018-.062-.94-.205-1 .359-1.531.818l-.249-.713-.906.88c-1.315.679-2.47-1.65-4.117-.411l.254.478c-.624-.058-1.939.387-1.873-.177-.055.09-.166.516-.425.272l-.044-.372-1.487.712c-1.199-.215.078-1.506-1.658-1.492C.895 5.105-.22 6.114.04 6.362c.178.01.347.073.478.179a.645.645 0 0 1 .24.4l-.558.225C.17 8.279-.194 9.44 1.304 10.144l.917-.732.36.521-.818.1c.513.479.784 0 1.105-.305.07.225.233.42.458.55l.907-1.114c.149.43-.376.884.292 1.094.426-.516-.502-.956.233-1.314.513.478.403.898.933.44a.447.447 0 0 1 .012.336.525.525 0 0 1-.233.27c.476-.367 1.304-.214 1.525-.817.553.598 1.658-.248 1.691.808.29-.433.74-.77 1.277-.956-.752 1.3 1.724 0 1.591 1.348.553-1.162 2.21-.617 3.255-1.3-.055.095-.16.282-.265.23.624.061.823.391 1.237.592 0-.956.967-1.195 1.448-1.797.812.87-.392 1.118-.1 1.974-.082-.755 1.272-.813.973-1.434.614.53.514.248.862 1.008.028-1.17.553-.22.962-.956.873.54.282 1.086 1.182.689.453.354-.342.808-.342.808Zm21.793-2.93-.447.057.447-.058Zm1.818-.091a7.552 7.552 0 0 0-.801 0c-.072-.23 0-.478.171-.478-.083.186.348.305.63.478Zm-4.128-4.49c.288-.109.393 0 .442.159-.172.02-.343.053-.508.1v.081a.973.973 0 0 1 .066-.34Z"/></svg>
</span> data visualization</div>
</template>
---
Api
<script setup>
import {data} from "./data/api.data";
</script>
API index
Methods
<ul :class="$style.oneline">
<li v-for="({name, href, comment}) in data.methods">
<span><a :href="${href}#${name}">{{ name }}</a> - {{ comment }}</span>
</li>
</ul>
Options
<ul>
<li v-for="[name, contexts] in data.options">
<b>{{ name }}</b> - <span v-for="({name: context, href}, index) in contexts"><a :href="href">{{ context }}</a><span v-if="index < contexts.length - 1">, </span></span>
</li>
</ul>
<style module>
ul.oneline span {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
---
Community
Community 🏠 {#community}
Learning Plot? Love data visualization? Don’t go it alone! Join our community to get help, be inspired, and do the same for others.
Staying up-to-date
:::tip
Please star ⭐️ our GitHub repo to show your support for us on GitHub!
:::
Plot is getting better all the time; catch up on recent releases by reading our CHANGELOG.
And of course, follow us on Observable, Mastodon, Twitter, and LinkedIn!
Getting help
We recommend asking for help on GitHub discussions.
We encourage you to share your work, no matter how messy, on Observable. Sharing live code is the easiest way to let people see what you see, and to debug your problem. Strive for a minimal, reproducible example — it helps people hone in on your problem more quickly.
When asking for help, don’t just post your code and ask people to fix it. Provide context, and say what you want help with. For example:
- What are you trying to achieve? What is your goal?
- What other solutions have you tried?
- What behavior are you currently seeing?
- Is the current behavior not what you expect?
If you think you’ve found a bug in Plot, please file a GitHub issue. But don’t use an issue to ask for help — you’ll have better luck on the forum or Slack.
Getting involved
We’d love for you to join the community! Here are some ways to participate:
* Share your work on Observable. Working in public is a great way to help others learn and be inspired. Don’t worry if your code is messy or unfinished; sharing drafts normalizes the challenges that everyone experiences doing data visualization.
* Upvote 👍 or comment on GitHub issues. We’d love your input on what to build next. If your desired feature isn’t already there, or if you’ve found a bug, file an issue and tell us about it.
* Answer questions or participate in discussions on GitHub. You’ll help others, and might learn something yourself, too.
* Join the Observable community Slack to meet others using Plot.
* Open a pull request! Read our guide to contributing.
Please help us maintain a positive environment for all by adhering to our code of conduct. Thank you!
---
What Is Plot
<script setup>
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";
import {computed, onMounted, shallowRef} from "vue";
import {useData} from "vitepress";
import PlotRender from "./components/PlotRender.js";
const olympians = shallowRef([
{weight: 31, height: 1.21, sex: "female"},
{weight: 170, height: 2.21, sex: "male"}
]);
onMounted(() => {
d3.csv("./data/athletes.csv", d3.autoType).then((data) => (olympians.value = data));
});
const {site: {value: {themeConfig: {sidebar}}}} = useData();
const paths = computed(() => {
const paths = [];
(function visit(node, path) {
paths.push({path, link: node.link && .${node.link}});
if (node.items) {
for (const item of node.items) {
visit(item, (path === "/" ? path : path + "/") + item.text);
}
}
})({items: sidebar}, "/Plot");
return paths;
});
// https://github.com/observablehq/plot/issues/1703
function computeTreeWidth(paths) {
const root = d3.tree().nodeSize([1, 1])(d3.stratify().path((d) => d.path)(paths));
const [x1, x2] = d3.extent(root, (d) => d.x);
return x2 - x1;
}
</script>
What is Plot?
Observable Plot is a free, open-source, JavaScript library for visualizing tabular data, focused on accelerating exploratory data analysis. It has a concise, memorable, yet expressive interface, featuring scales and layered marks in the grammar of graphics style popularized by Leland Wilkinson and Hadley Wickham and inspired by the earlier ideas of Jacques Bertin. And there are plenty of examples to learn from and copy-paste.
In the spirit of show don’t tell, here’s a scatterplot of body measurements of athletes from the 2016 Summer Olympics.
:::plot defer https://observablehq.com/@observablehq/plot-olympians-scatterplot
Plot
.dot(olympians, {x: "weight", y: "height", stroke: "sex"})
.plot({color: {legend: true}}):::
A plot specification assigns columns of data (weight, height, and sex) to visual properties of marks (x, y, and stroke). Plot does the rest! You can configure much more, if needed, but Plot’s goal is to help you get a meaningful visualization quickly to accelerate analysis.
This scatterplot suffers from overplotting: many dots are drawn in the same spot, so it’s hard to perceive density. We can fix this by applying a bin transform to group athletes of similar height and weight (and sex), and then use opacity to encode the number of athletes in the bin.
:::plot defer https://observablehq.com/@observablehq/plot-olympians-bins
Plot.rect(olympians, Plot.bin({fillOpacity: "count"}, {x: "weight", y: "height", fill: "sex", inset: 0})).plot():::
Or we could try the density mark.
:::plot defer https://observablehq.com/@observablehq/plot-olympians-density
Plot.density(olympians, {x: "weight", y: "height", stroke: "sex"}).plot():::
A simpler take on this data is to focus on one dimension: weight. We can use the bin transform again to make a histogram with weight on the x-axis and frequency on the y-axis. This plot uses a rect mark and an implicit stack transform.
:::plot defer https://observablehq.com/@observablehq/plot-vertical-histogram
Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight", fill: "sex"})).plot():::
Or if we’d prefer to show the two distributions separately as small multiples, we can facet the data along y (keeping the fill encoding for consistency, and adding grid lines and a rule at y = 0 to improve readability).
:::plot defer https://observablehq.com/@observablehq/plot-faceted-histogram
Plot.plot({
grid: true,
marks: [
Plot.rectY(olympians, Plot.binX({y: "count"}, {x: "weight", fill: "sex", fy: "sex"})),
Plot.ruleY([0])
]
}):::
What can Plot do?
Because marks are composable, and because you can extend Plot with custom marks, you can make almost anything with it — much more than the charts above! The following tree diagram of the documentation gives a sense of what’s ”in the box” with Plot. Peruse our gallery of examples for more inspiration.
<PlotRender :options='{
axis: null,
height: computeTreeWidth(paths) * 12,
marginTop: 4,
marginRight: 120,
marginBottom: 4,
marginLeft: 24,
marks: [
Plot.tree(paths, {path: "path", textStroke: "var(--vp-c-bg)", channels: {href: {value: "link", filter: null}}, treeSort: null})
]
}' />
---
Why Plot
<script setup>
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";
import aapl from "./data/aapl.ts";
import penguins from "./data/penguins.ts";
function arealineY(data, {color, fillOpacity = 0.1, ...options} = {}) {
return Plot.marks(
Plot.ruleY([0]),
Plot.areaY(data, {fill: color, fillOpacity, ...options}),
Plot.lineY(data, {stroke: color, ...options})
);
}
</script>
Why Plot?
Observable Plot is for exploratory data visualization. It’s for finding insights quickly. Its API, while expressive and configurable, optimizes for conciseness and memorability. We want the time to first chart to be as fast as possible.
And the speed doesn’t stop there: Plot helps you quickly pivot and refine your views of data. Our hope with Plot is that you’ll spend less time reading the docs, searching for code to copy-paste, and debugging — and more time asking questions of data.
Compared to other visualization tools, including low-level tools such as D3 and less expressive high-level tools such as chart templates, we think you’ll be more productive exploring data with Plot. You’ll spend more time “using vision to think” and less time wrangling the machinery of programming.
Or put more simply: with Plot, you’ll see more charts.
Plot is concise
You can make a meaningful chart in Plot with as little as one line of code.
:::plot https://observablehq.com/@observablehq/color-scatterplot
Plot.dot(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm", stroke: "species"}).plot():::
What makes Plot concise? In a word: defaults. If you specify the semantics — your data and the desired encodings — Plot will figure out the rest.
The beauty of defaults is that you can override them as needed. This is ideal for exploring: you invest minimally in the initial chart, and as you start to see something interesting, you progressively customize to improve the display. Perhaps the plot above would be easier to read with an aspect ratio proportional to the data, a grid, and a legend?
:::plot https://observablehq.com/@observablehq/plot-refined-color-scatterplot
Plot.plot({
grid: true,
aspectRatio: 1,
inset: 10,
x: {tickSpacing: 80, label: "Culmen length (mm)"},
y: {tickSpacing: 80, label: "Culmen depth (mm)"},
color: {legend: true},
marks: [
Plot.frame(),
Plot.dot(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm", stroke: "species"})
]
}):::
Plot transforms data
Munging data, not visualizing it, is often most of the work of data analysis. Plot’s transforms let you aggregate and derive data within your plot specification, reducing the time spent preparing data. For example, if you have a dataset of penguins, you can quickly count their frequency by species with the group transform.
:::plot https://observablehq.com/@observablehq/plot-groupy-transform
Plot.plot({
marginLeft: 80,
marginRight: 80,
marks: [
Plot.barX(penguins, Plot.groupY({x: "count"}, {y: "species"})),
Plot.ruleX([0])
]
}):::
Because transforms are integrated into Plot, they work automatically with other Plot features such as faceting. For example, to breakdown the chart above by island, we just add the fy (vertical facet) option.
:::plot https://observablehq.com/@observablehq/plot-groupy-transform/2
Plot.plot({
marginLeft: 80,
marginRight: 80,
marks: [
Plot.barX(penguins, Plot.groupY({x: "count"}, {fy: "island", y: "species"})),
Plot.ruleX([0])
]
}):::
And to color by sex, too? Add fill; the bar mark then applies an implicit stack transform.
:::plot https://observablehq.com/@observablehq/plot-groupy-transform/3
Plot.plot({
marginLeft: 80,
marginRight: 80,
color: {legend: true},
marks: [
Plot.barX(penguins, Plot.groupY({x: "count"}, {fy: "island", y: "species", fill: "sex"})),
Plot.ruleX([0])
]
}):::
Plot’s transforms can do powerful things, including normalizing series, computing moving averages, laying out trees, dodging, and hexagonal binning.
Plot is composable
Simple components gain power through composition, such as layering multiple marks into a single plot. Plot makes it easy to define custom composite marks, such as this one comprising a rule, area, and line:
function arealineY(data, {color, fillOpacity = 0.1, ...options} = {}) {
return Plot.marks(
Plot.ruleY([0]),
Plot.areaY(data, {fill: color, fillOpacity, ...options}),
Plot.lineY(data, {stroke: color, ...options})
);
}You can use this composite mark like any built-in mark:
:::plot https://observablehq.com/@observablehq/plot-arealiney-custom-mark
arealineY(aapl, {x: "Date", y: "Close", color: "blue"}).plot():::
Plot uses this technique internally: the axis mark and box mark are both composite marks.
:::plot https://observablehq.com/@observablehq/plot-penguins-horizontal-box-plot
Plot.boxX(penguins, {x: "body_mass_g", y: "species"}).plot({marginLeft: 60, y: {label: null}}):::
Plot’s transforms are composable, too: to apply multiple transforms, you simply pass the options from one transform to the next. Some marks even apply implicit transforms, say for stacking or binning as shown above. Mark options are plain JavaScript objects, so you can also share options across marks and inspect them to debug.
Plot is extensible
Plot isn’t a new language; it’s “just” vanilla JavaScript. Plot embraces JavaScript, letting you plug in your own functions for accessors, reducers, transforms… even custom marks! And Plot generates SVG, so you can style it with CSS and manipulate it just like you do with D3. (See Mike Freeman’s tooltip plugin for a great example of extending Plot this way.)
Plot builds on D3
Plot is informed by our more than ten years’ experience developing D3, the web’s most popular library for data visualization.
Plot uses D3 to implement a wide variety of features:
- scales (ticks, color schemes, number formatting)
- shapes (areas, lines, curves, symbols, stacks)
- planar geometry (Delaunay, Voronoi, contours, density estimation)
- spherical geometry (geographic projections)
- data manipulation (group, rollup, bin, statistics)
- tree diagrams
- … and more!
If you already know some D3, you’ll find many parts of Plot familiar.
We’ve long said that D3 makes things possible, not necessarily easy. And that’s true regardless of the task at hand. D3 makes hard and amazing things possible, yes, but even simple things that should be easy are often not. To paraphrase Amanda Cox: “Use D3 if you think it’s perfectly normal to write a hundred lines of code for a bar chart.”
Plot’s goal is to make the easy things easy, and fast, and then some.
:::tip
Whether or not Plot succeeds at this goal is up to you — so we’d love your feedback on what you find easy or hard to do with Plot. And we encourage you to ask for help when you get stuck. We learn a lot from helping!
:::
Since Plot and D3 have different goals, they make different trade-offs. Plot is more efficient: you can make charts quickly. But it is also necessarily less expressive: bespoke visualizations with extensive animation and interaction, advanced techniques like force-directed graph layout, or even developing your own charting library, are better done with D3’s low-level API.
We recommend D3 for bespoke data visualizations, if you decide the extra expressiveness of D3 is worth the time and effort. D3 makes sense for media organizations such as The New York Times or The Pudding, where a single graphic may be seen by a million readers, and where a team of editors can work together to advance the state of the art in visual communication; but is it the best tool for building your team’s private dashboard, or a one-off analysis? You may be surprised how far you can get with Plot.
---
.Vitepress/Config.Ts
import {fileURLToPath, URL} from "node:url";
import path from "node:path";
import {defineConfig} from "vitepress";
import plot from "./markdown-it-plot.js";
// https://vitepress.dev/reference/site-config
// prettier-ignore
export default defineConfig({
title: "Plot",
description: "The JavaScript library for exploratory data visualization",
appearance: "force-auto",
base: "/plot/",
cleanUrls: true,
vite: {
resolve: {
alias: [
{find: "@observablehq/plot", replacement: path.resolve("./src/index.js")},
{find: /^.*\/VPFooter\.vue$/, replacement: fileURLToPath(new URL("./theme/CustomFooter.vue", import.meta.url))}
]
},
define: {
__APP_VERSION__: JSON.stringify(process.env.npm_package_version)
}
},
vue: {
template: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith("observable-")
}
}
},
markdown: {
config: (md) => {
plot(md);
}
},
head: [
["link", {rel: "preconnect", href: "https://fonts.gstatic.com", crossorigin: ""}],
["link", {rel: "preload", as: "style", href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Spline+Sans+Mono:ital,wght@0,300..700;1,300..700&display=swap"}],
["link", {rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Spline+Sans+Mono:ital,wght@0,300..700;1,300..700&display=swap"}],
["link", {rel: "apple-touch-icon", href: "https://static.observablehq.com/favicon-512.0667824687f99c942a02e06e2db1a060911da0bf3606671676a255b1cf97b4fe.png"}],
["link", {rel: "icon", type: "image/png", href: "https://static.observablehq.com/favicon-512.0667824687f99c942a02e06e2db1a060911da0bf3606671676a255b1cf97b4fe.png", sizes: "512x512"}],
["script", {async: "", src: "https://www.googletagmanager.com/gtag/js?id=G-9B88TP6PKQ"}],
["script", {}, "window.dataLayer=window.dataLayer||[];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js',new Date());\ngtag('config','G-9B88TP6PKQ');"],
["script", {async: "", defer: "", src: "https://static.observablehq.com/assets/components/observable-made-by.js"}],
],
sitemap: {
hostname: "https://observablehq.com/plot/"
},
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
// Theme related configurations.
logo: {
light: "/observable-light.svg",
dark: "/observable-dark.svg"
},
sidebar: [
{
text: "Introduction",
items: [
{text: "What is Plot?", link: "/what-is-plot"},
{text: "Why Plot?", link: "/why-plot"},
{text: "Getting started", link: "/getting-started"},
{text: "Examples", link: "https://observablehq.com/@observablehq/plot-gallery"}
]
},
{
text: "Features",
collapsed: false,
items: [
{text: "Plots", link: "/features/plots"},
{text: "Marks", link: "/features/marks"},
{text: "Scales", link: "/features/scales"},
{text: "Projections", link: "/features/projections"},
{text: "Transforms", link: "/features/transforms"},
{text: "Interactions", link: "/features/interactions"},
{text: "Facets", link: "/features/facets"},
{text: "Legends", link: "/features/legends"},
{text: "Curves", link: "/features/curves"},
{text: "Formats", link: "/features/formats"},
{text: "Intervals", link: "/features/intervals"},
{text: "Markers", link: "/features/markers"},
{text: "Shorthand", link: "/features/shorthand"},
{text: "Accessibility", link: "/features/accessibility"}
]
},
{
text: "Marks",
collapsed: true,
items: [
{text: "Area", link: "/marks/area"},
{text: "Arrow", link: "/marks/arrow"},
{text: "Auto", link: "/marks/auto"},
{text: "Axis", link: "/marks/axis"},
{text: "Bar", link: "/marks/bar"},
{text: "Bollinger", link: "/marks/bollinger"},
{text: "Box", link: "/marks/box"},
{text: "Cell", link: "/marks/cell"},
{text: "Contour", link: "/marks/contour"},
{text: "Delaunay", link: "/marks/delaunay"},
{text: "Density", link: "/marks/density"},
{text: "Difference", link: "/marks/difference"},
{text: "Dot", link: "/marks/dot"},
{text: "Frame", link: "/marks/frame"},
{text: "Geo", link: "/marks/geo"},
{text: "Grid", link: "/marks/grid"},
{text: "Hexgrid", link: "/marks/hexgrid"},
{text: "Image", link: "/marks/image"},
{text: "Line", link: "/marks/line"},
{text: "Linear regression", link: "/marks/linear-regression"},
{text: "Link", link: "/marks/link"},
{text: "Raster", link: "/marks/raster"},
{text: "Rect", link: "/marks/rect"},
{text: "Rule", link: "/marks/rule"},
{text: "Text", link: "/marks/text"},
{text: "Tick", link: "/marks/tick"},
{text: "Tip", link: "/marks/tip"},
{text: "Tree", link: "/marks/tree"},
{text: "Vector", link: "/marks/vector"},
{text: "Waffle", link: "/marks/waffle"}
]
},
{
text: "Transforms",
collapsed: true,
items: [
{text: "Bin", link: "/transforms/bin"},
{text: "Centroid", link: "/transforms/centroid"},
{text: "Dodge", link: "/transforms/dodge"},
{text: "Filter", link: "/transforms/filter"},
{text: "Group", link: "/transforms/group"},
{text: "Hexbin", link: "/transforms/hexbin"},
{text: "Interval", link: "/transforms/interval"},
{text: "Map", link: "/transforms/map"},
{text: "Normalize", link: "/transforms/normalize"},
{text: "Select", link: "/transforms/select"},
{text: "Shift", link: "/transforms/shift"},
{text: "Sort", link: "/transforms/sort"},
{text: "Stack", link: "/transforms/stack"},
{text: "Tree", link: "/transforms/tree"},
{text: "Window", link: "/transforms/window"}
]
},
{
text: "Interactions",
collapsed: true,
items: [
{text: "Crosshair", link: "/interactions/crosshair"},
{text: "Pointer", link: "/interactions/pointer"}
]
},
{text: "API index", link: "/api"}
],
search: {
provider: "local"
},
footer: {
message: "Library released under <a style='text-decoration:underline;' href='https://github.com/observablehq/plot/blob/main/LICENSE'>ISC License</a>.",
copyright: Copyright 2020–${new Date().getUTCFullYear()} Observable, Inc.
}
}
});
---
CONTRIBUTING
Observable Plot - Contributing
Observable Plot is open source and released under the ISC license. You are welcome to send us pull requests to contribute bug fixes or new features. We also invite you to participate in issues and discussions. We use issues to track and diagnose bugs, as well as to debate and design enhancements to Plot. Discussions are intended for you to ask for help using Plot, or to share something cool you’ve built with Plot. You can also ask for help on the Observable Forum and the Observable community Slack.
We request that you abide by our code of conduct when contributing and participating in discussions.
Development
To contribute to Observable Plot, you’ll need a local development environment to make and test changes to Plot’s source code. To get started, follow GitHub’s tutorial on forking (and cloning) a repository. Once you’ve cloned your fork of the Plot repository, open a terminal and cd in your forked repository. Then run Yarn to install dependencies:
pnpm installYou may encounter an error installing node-canvas, such as:
node-pre-gyp ERR! install response status 404 Not Found on https://github.com/Automattic/node-canvas/releases/download/v2.9.1/canvas-v2.9.1-node-v93-darwin-unknown-arm64.tar.gz
node-pre-gyp WARN Pre-built binaries not installable for [email protected] and [email protected] (node-v93 ABI, unknown) (falling back to source compile with node-gyp)
node-pre-gyp WARN Hit error response status 404 Not Found on https://github.com/Automattic/node-canvas/releases/download/v2.9.1/canvas-v2.9.1-node-v93-darwin-unknown-arm64.tar.gzIf this happens, you will need to compile node-canvas from source. On macOS you can use Homebrew to install the needed dependencies:
brew install pkg-config cairo pango libpng jpeg giflib librsvgTesting
After making changes to Plot’s source code, run Plot’s test suite to verify that your code is doing what you expect and that you haven’t introduced any other unexpected changes in behavior. Plot has two types of tests: unit tests and snapshot tests. Tests are run automatically on pull requests (via GitHub Actions), but you’ll want to run them locally to verify your changes before opening a pull request. To run the tests:
pnpm run testThis will also run ESLint on Plot’s source to help catch simple mistakes, such as unused imports.
Please run Prettier before submitting any pull request. Check “format on save” in your code editor, or run:
pnpm exec prettier --write .A test coverage report can be generated with c8, in text and lcov formats, to help you identify which lines of code are not (yet!) covered by tests. Just run:
pnpm run test:coverageUnit tests
Unit tests live in test and have the -test.js file extension; see test/marks/area-test.js for example. Generally speaking, unit tests make specific, low-level assertions about the behavior of Plot’s API, including internals and helper methods. If you add a new feature, or change the behavior of an existing feature, please update the unit tests so that we can more easily maintain your contribution into the future. For example, here’s a unit test that tests how Plot formats months:
it("formatMonth(locale, format) does the right thing", () => {
assert.strictEqual(Plot.formatMonth("en", "long")(0), "January");
assert.strictEqual(Plot.formatMonth("en", "short")(0), "Jan");
assert.strictEqual(Plot.formatMonth("en", "narrow")(0), "J");
});Plot’s unit tests are written with Mocha.
If you like, you can also run Mocha in watch mode for a specific file, so that unit tests re-run automatically when you make changes. For example:
pnpm run test:vitest test/marks/bar-test.jsSnapshot tests
Snapshot tests live in test/plots and are registered in test/plots/index.ts; see test/plots/aapl-bollinger.ts for example. Unlike unit tests which only test individual methods, snapshot tests actually visualize data—they’re more representative of how we expect people will use Plot. Snapshot tests can also serve as examples of how to use the Plot API, though note that some of the examples intentionally test edge case of the API and may not embody best practices. Each snapshot test defines a plot by exporting a default async function. For example, here’s a line chart using BLS unemployment data:
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";export async function lineUnemployment() {
const bls = await d3.csv<any>("data/bls-metro-unemployment.csv", d3.autoType);
return Plot.plot({
marks: [
Plot.ruleY([0]),
Plot.lineY(bls, {x: "date", y: "unemployment", z: "division"})
]
});
}
When a snapshot test is run, its output is compared against the SVG or HTML snapshot saved in the test/output folder. This makes it easier to review the effect of code changes and to catch unintended changes. Snapshot tests must have deterministic, reproducible behavior; they should not depend on live data, external servers, the current time, the weather, etc. To use randomness in a test, use a seeded random number generator such as d3.randomLcg.
To add a new snapshot test, create a new JavaScript file in the test/plots folder using the pattern shown above. Then export your snapshot test function from test/plots/index.ts. For example:
export * from "./moby-dick.ts";The best thing about snapshot tests is that you can see the live result in your browser as you make changes to Plot’s source code! This lets you immediately assess visually what Plot is doing. To preview snapshot tests during development, Plot uses Vite. To start Vite:
pnpm run devThis will open http://localhost:8008/ in your browser where you can choose a snapshot test. As you edit the source, the current test will update live in your browser as you save changes. You can change the selected test from the drop-down menu. When the drop-down menu is focused, the left and right arrow keys cycle between tests.
When previewing snapshot tests, consider using your browser’s debugger or element inspector to assist development.
Running Plot’s snapshot tests will automatically generate any missing snapshots in test/output. You should git add these before committing your changes. (If you forget, your PR will fail in CI, and you’ll get a reminder.) Changed snapshots are saved alongside the originals with a -changed suffix for visual inspection. If your code intentionally changes some of the existing snapshots, simply blow away the existing snapshots and run the tests again. You can then review what’s changed using git diff.
rm -rf test/output
pnpm run test:vitestDocumentation
When submitting a pull request, please remember to update Plot’s documentation to reflect changes to the public API. You are also welcome to edit Plot’s CHANGELOG.md to assist with writing future release notes. In addition, please reference any related issues (or discussions) in your pull request description.
If you’d like to share a live demonstration or motivating example of your change to Plot, you can regenerate Plot’s release bundle like so:
pnpm run prepublishOnlyThe generated bundle dist/plot.umd.js can then be loaded like so:
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script src="plot.umd.js"></script>Alternatively, you can attach the dist/plot.umd.js file to an Observable notebook, and then load it like so:
Plot = require(await FileAttachment("plot.umd.js").url())---
CHANGELOG
Observable Plot - Changelog
Year: Current (2025) · 2024 · 2023 · 2022 · 2021
0.6.17
The clip mark option now supports GeoJSON objects 🌎 in addition to the named frame and sphere clipping methods, allowing the visual extent of marks to be limited to arbitrary polygons. For instance, this Voronoi mesh of world airports is clipped to land boundaries:
Plot.plot({
projection: {type: "orthographic", rotate: [110, -50]},
marks: [
Plot.dot(airports, {x: "longitude", y: "latitude", fill: "red", r: 1}),
Plot.voronoiMesh(airports, {x: "longitude", y: "latitude", clip: land}),
Plot.sphere(),
Plot.geo(land)
]
})The GeoJSON object passed to the clip option is rendered as a clipPath element using the same path data that a geo mark would produce, respecting the plot’s top-level projection option, if any. For performance, clipPath elements are shared by marks clipped with the same GeoJSON object. For example, the raster mark and contour mark below show atmospheric water vapor measurements across the United States from NASA Earth Observations; both marks are clipped to the nation’s boundary, censoring the (absurd) values that would otherwise be interpolated between Alaska, Southern California, and Hawai’i.
Plot.raster(vapor, {
fill: Plot.identity,
width: 360,
height: 180,
x1: -180, y1: 90, x2: 180, y2: -90,
interpolate: "barycentric",
blur: 10,
clip: nation
}).plot()[The code for the map above is too long to reproduce here in its entirety; click the image above for the complete code.]
The clip mark option can also be used to clip against arbitrary polygons, not just geographic boundaries. For example, to show the value of Math.atan2 over the unit circle:
Plot.raster({
x1: -1, x2: 1, y1: -1, y2: 1,
fill: (x, y) => Math.atan2(y, x),
clip: {
type: "Polygon",
coordinates: [
d3.range(0, 2 * Math.PI, 0.1).map((angle) => [Math.cos(angle), Math.sin(angle)])
]
}
}).plot({width: 300, aspectRatio: 1})The interactive tip associated with a waffle mark is now anchored to the “center” of the visual representation of the associated datum. That center depends on the shape that is referenced. For fun, here’s a chart from our unit tests showing these anchoring points for various amounts of waffling. Baffling!
<img src="./img/waffle-pointer-fractional.png" width="672" alt="waffle mark with the anchor position of each datum marked with its value">
---
For earlier changes, continue to the 2024 CHANGELOG.
---