Replies: 3 comments
-
| Not sure what's happening here; it looks like the Plot.dot mark is not preserved between the two contexts? Note: I tend to use linkedom to generate SVG in node (see for example https://www.val.town/v/fil/beckerBarley). (I realize this comment is probably not helpful.) | 
Beta Was this translation helpful? Give feedback.
-
| I don’t think you can pass Plot options through the page.evaluate boundary (because Mark instances won’t be serializable). You could pass in a string and evaluate that, maybe. We use node-canvas in our tests to rasterize plots. | 
Beta Was this translation helpful? Give feedback.
-
| Thanks for the suggestions! I tweaked the code to pass a string to page.evaluate. It worked great except with dates, of course. So, I also convert dates to their ms value before stringifying. And then I convert them back once in the browser. And it works! 🥳 We pass the data, a function wrapping the Plot code, and a path. const data = [
    { salary: 75000, hireDate: new Date("2022-12-15") },
    { salary: 82000, hireDate: new Date("2022-11-20") },
    { salary: 60000, hireDate: new Date("2022-10-25") },
    { salary: 90000, hireDate: new Date("2022-09-30") },
    { salary: 72000, hireDate: new Date("2022-08-15") },
    { salary: 55000, hireDate: new Date("2022-07-20") },
    { salary: 68000, hireDate: new Date("2022-06-25") },
    { salary: 48000, hireDate: new Date("2022-05-30") },
    { salary: 77000, hireDate: new Date("2022-04-15") },
    { salary: 88000, hireDate: new Date("2022-03-20") },
]
await savePlotChart(
    data,
    (data) =>
        Plot.plot({
            marginLeft: 75,
            color: { legend: true },
            marks: [
                Plot.dot(data, {
                    x: "hireDate",
                    y: "salary",
                    stroke: "salary",
                }),
            ],
        }),
    "./my-chart.png"
)And this is what savePlotChart does. import puppeteer from "puppeteer"
export default async function savePlotChart(
    data: { [key: string]: unknown }[],
    makeChart: (
        data: { [key: string]: unknown }[]
    ) => SVGSVGElement | HTMLElement,
    path: string
) {
    // We check which keys hold dates, based on the first data item.
    const keysWithDates = Object.keys(data[0]).filter(
        (key) => data[0][key] instanceof Date
    )
    // And we convert the dates to ms in the data. They will be easier to convert back to dates this way.
    if (keysWithDates.length > 0) {
        for (const d of data) {
            for (const key of keysWithDates) {
                d[key] = (d[key] as Date).getTime()
            }
        }
    }
    const browser = await puppeteer.launch({ headless: "new" })
    const page = await browser.newPage()
    // For better screenshot resolution
    await page.setViewport({ width: 1000, height: 1000, deviceScaleFactor: 2 })
    // We inject the d3 and plot code.
    await page.addScriptTag({
        path: "node_modules/d3/dist/d3.js",
    })
    await page.addScriptTag({
        path: "node_modules/@observablehq/plot/dist/plot.umd.js",
    })
    // We create an empty div with a display flex to avoid blank space in the screenshot.
    await page.setContent("<div id='dataviz' style='display:flex;'></div>")
    // We convert back dates, generate the chart and append it to our div.
    await page.evaluate(`
        const data = ${JSON.stringify(data)}
        const keysWithDates = ${JSON.stringify(keysWithDates)}
        for (const key of keysWithDates) {
            for (const d of data) {
                d[key] = new Date(d[key])
            }
        }
        const makeChart = ${makeChart.toString()}
        const chart = makeChart(data)
        const div = document.querySelector("#dataviz")
        if (!div) {
            throw new Error("No div with id dataviz")
        }
        div.append(chart)`)
    // We select the generated figure or svg and save a screenshot of it.
    const dataviz = await page.$("#dataviz > figure, #dataviz > svg")
    if (!dataviz) {
        throw new Error("No dataviz element.")
    }
    await dataviz.screenshot({ path })
    await browser.close()
}Here's the output! I published the code in the journalism library. I intend to use it with the simple-data-analysis library. Again, thanks for the help. :) Cheers | 
Beta Was this translation helpful? Give feedback.

Uh oh!
There was an error while loading. Please reload this page.
-
Hello!
I already use Plot a lot in my observable notebooks, but I often need to do some work with NodeJS. I want to use Plot with NodeJS and save the charts as images.
Here's how I intend to use a function I named savePlotChart that takes Plot options as a parameter:
I am trying to use puppeteer to render the chart and save it. Here's the function savePlotChart:
But when I run this, I get this error:
TypeError: invalid mark; missing render function new Render (node_modules/@observablehq/plot/dist/plot.umd.js:7649:45)And here's what's at node_modules/@observablehq/plot/dist/plot.umd.js:7649:45:
Do you have any ideas or suggestions on how to make it work?
Thanks! :)
Beta Was this translation helpful? Give feedback.
All reactions