Plot
A node library to display charts in popup windows and save them as pngs. Supports observablehq/plot, vega-lite and plotly out of the box.
Install / Use
/learn @mhkeller/PlotREADME
Plot
A small node library to display charts in popup windows and save them as pngs. Supports Observablehq/plot, Vega-lite, Vega-lite-api and Plotly out of the box.
- Motivation
- Installing
- Plotting Vega-Lite charts
- Plotting Observablehq/plot charts
- Plotting histograms
- Generic plotting
- Examples
- A note on
undefinedvariables - How it works
Motivation
In notebook-based systems or IDEs like RStudio, it's nice to create a quick chart or map from your data. There aren't that many similar solutions for Node.js, however. This library is a small bridge that allows you to take advantage of these or similar charting libraries such as observablehq/plot, vega-lite, vega-lite-api and plotly or others to renders charts in a browser environment directly from a Node script, see the results in a minimal popup window and save the image.
Vega example

Observablehq/plot example

Histogram example

Plotly (choropleth map) example

Installing
npm install @mhkeller/plot
Plotting Vega-Lite charts
plotVega( chartConfig: Object, options: Object )
Arguments
- chartConfig
{Object}(required)- A Vega-Lite-API chart or a Vega-Lite spec..
- options
{Object}- An options object.
- options.outPath
{String}- If specified, an image will be written to this path as a png.
- options.view
{Boolean=true}- If true, show the chart in a popup window.
- options.title
{String='Chart'}- If
viewis true, add a title to the window's page. A timestamp will be appended to this.
- If
- options.css
{String}- Any CSS that you want injected into the page to tweak styles.
- options.debug
{Boolean = false}- Whether to run the screenshot browser in headfull mode.
import { plotVega } from '@mhkeller/plot'
import * as vl from 'vega-lite-api';
const data = [
{ category: 'A', value: 28 },
{ category: 'B', value: 55 },
{ category: 'C', value: 43 },
{ category: 'D', value: 91 },
];
const chart = vl
.markBar()
.description('A simple bar chart.')
.data(data)
.encode(
vl.x().fieldO('category'),
vl.y().fieldQ('value')
);
await plotVega(chart);
You can also supply a Vega-Lite JSON spec:
import { plotVega } from '@mhkeller/plot'
const data = {
values: [
{ category: 'A', value: 28 },
{ category: 'B', value: 55 },
{ category: 'C', value: 43 },
{ category: 'D', value: 91 },
]
};
const spec = {
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
description: 'A simple bar chart.',
data,
mark: 'bar',
encoding: {
x: { field: 'category', type: 'ordinal' },
y: { field: 'value', type: 'quantitative' }
}
};
await plotVega(spec);
Plotting Observablehq/plot charts
Note: You have to pass in a
documentelement using the exportedcreateDocumentfunction. See the example below.
plotObservable( chart: Function, data: Array, options: Object )
Arguments
- chart
{Function}required- An @observablehq/plot function. The first argument should be your dataset.
- data
Arrayrequired- The data to pass in to the chart function.
- options
{Object}- An options object.
- options.outPath
{String}- If specified, an image will be written to this path as a png.
- options.view
{Boolean=true}- If true, show the chart in a popup window.
- options.title
{String='Chart'}- If
viewis true, add a title to the window's page. A timestamp will be appended to this.
- If
- options.css
{String}- Any CSS that you want injected into the page to tweak styles.
- options.debug
{Boolean = false}- Whether to run the screenshot browser in headfull mode.
import { readDataSync } from 'indian-ocean';
import * as aq from 'arquero';
import { plotObservable, createDocument } from '@mhkeller/plot';
const events = readDataSync('./test/data/purchase_data.csv');
const data = aq
.from(events)
.derive({ date: aq.escape(d => new Date(d.date.split('T')[0])) })
.groupby('date', 'brand')
.rollup({ value: d => aq.op.sum(d.price_in_usd) })
.orderby('date', 'brand')
.objects();
const chart = Plot.plot({
document: createDocument(),
marks: [
Plot.line(data, {
x: 'date',
y: 'value',
stroke: 'brand'
})
],
color: {
legend: true
}
});
await plotObservable(chart);
Plotting histograms
plotHistogram( data: Array, { facetBy: String[], fields: String{}, outDir: String[, name: String, fill: String='#000', css: String, view: false] } } )
A more specific function that takes data, a list of fields to facet by and a list of fields to compute values for. Writes a screenshot.
import { plotHistogram } from '@mhkeller/plot`;
plotHistogram(data, {
facetBy: ['group'],
fields: ['value', 'value2'],
outDir: 'out_images',
name: 'my-charts',
fill: 'group',
view: true
});
Arguments
- data
{Array}(required)- Your data to render.
- options
{Object}- An options object.
- options.facetBy
{String[]}(required)- An array of field names to facet by. These facets are not combined, it's just a shorthand for running multiple facets at a time, done separately in succession.
- options.fields
{String[]}(required)- An array of fields to compute histogram values for.
- options.outDir
{String}(required)- The directory – not a specific file name – to write the various files out to.
- Filenames are generated according to the convention:
- With a
namesupplied:${name}_by__${facet}_${field}.png; - With no
namesupplied:by__${facet}_${field}.png; - If
breakoutFields=false_${field}is a concatenation of all fields separated by a|character. - If
columns=false, the file name will end in_lines.png.
- With a
- options.fill
{String}- A hex code or field name. Defaults to
'#000'.
- A hex code or field name. Defaults to
- options.view
{Boolean=true}- If true, show the chart in a popup window.
- options.css
{String}- Any CSS that you want injected into the page to tweak styles.
- options.breakoutFields
{Boolean=true}- For each field passed into
options.fieldswrite out a separate PNG. Set this to false to put everything on the same scale.
- For each field passed into
- options.columns
{Boolean=true}- Draw the histogram as columns, like a regular histogram. If this is
false, just draw semi-opaque lines, which can be useful for seeing density.
- Draw the histogram as columns, like a regular histogram. If this is
- options.debug
{Boolean = false}- Whether to run the screenshot browser in headfull mode.
Generic plotting
plot( plotFunction: Function, args: Array, options: Object )
A generic function to render HTML, view and screenshot it.
If your plot function requires a DOM element ID to render into (as Plotly does), a #body element is added to the page for you to use.
The plotFunction can return data specific to the chart library you're using:
- Vega-lite: Return the JSON spec, it will be passed to
vegaEmbed. - Vega-lite-api: Return the chart object. It will be called with
toSpec()and then passed tovegaEmbed. - ObservableHq/Plot: Return the chart object. It will be called and the HTML will be appended to the
import { plot } from '@mhkeller/plot';
const dataset = {
values: [
{ a: 'A', b: 28 },
{ a: 'B', b: 55 },
{ a: 'C', b: 43 },
{ a: 'D', b: 91 },
]
};
const chart = data => {
return {
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
description: 'A simple bar chart with embedded data.',
data,
mark: 'bar',
encoding: {
x: {field: 'a', type: 'ordinal'},
y: {field: 'b', type: 'quantitative'}
}
};
}
// Or, if using vega-lite-api
const chart = data => {
return vl.markBar()
.description('A simple bar chart with embedded data.')
.data(data)
.encode(
vl.x().fieldO('a'),
vl.y().fieldQ('b')
)
}
await plot(chart, [dataset], {
library: 'vega-lite',
// library: 'vega-lite-api',
view: true,
title: 'Vega line chart'
outPath: 'test/tmp/vega-lite_line-plot.png',
});
If the plot function simply creates HTML, then this function can simply return from that function and the HTML will be appended to the #body element automatically such as in this Observablehq/plot example.
import { plot } from '@mhkeller/plot`;
const dataset = /* read in your dataset ... */
// Create a function that returns html
const chart = data => {
return Plot.plot({
marks: [
Plot.rectY(
data,
Plot.binX(
{ y: 'count' },
{
x: 'date',
y: 'value',
fill: 'blue',
thresholds: 10
}
)
)
]
});
}
await plot(chart, [data], {
outPath: 'chart.png',
view: true,
library: 'observablehq/plot' // default
});
Note: If your plot function requires a DOM element ID to render into, a #body element is added to the page for you to use.
Arguments
- chart
{Function}(required)- A function that accepts a dataset and returns a function that renders a chart.
- arguments
{Function}(required)- An array of arguments that go into your chart function. This will be the data
