# Generate static png or svg at build time with Framework

**URL:** <https://talk.observablehq.com/t/generate-static-png-or-svg-at-build-time-with-framework/8833>\
**Category:** Help\
**Created:** [February 21, 2024, 5:03pm UTC](https://talk.observablehq.com/t/generate-static-png-or-svg-at-build-time-with-framework/8833 "2024-02-21T17:03:35Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![matvil](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/matvil/32/7356_2.png) [@matvil](https://talk.observablehq.com/u/matvil)\
**Post date:** [February 21, 2024, 5:03pm UTC](https://talk.observablehq.com/t/generate-static-png-or-svg-at-build-time-with-framework/8833/1 "2024-02-21T17:03:35Z")

</div>

In my Framework project I have a javascript function that returns an Observable Plot object. I would like to run the function during **build** to produce a static png or svg that I can then display on the dashboard at load time. Is a data loader the way to go here? If so how is this done? The docs ([Data loaders | Observable Framework](https://observablehq.com/framework/loaders)) seems to mention this, but has no examples.

Thanks!

---

<div class="post-metadata">

**Author:** ![Fil](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/fil/32/207_2.png) [@Fil](https://talk.observablehq.com/u/Fil)\
**Post date:** [February 21, 2024, 6:25pm UTC](https://talk.observablehq.com/t/generate-static-png-or-svg-at-build-time-with-framework/8833/2 "2024-02-21T18:25:10Z")

</div>

Sure! Here’s an example that builds a chart with Plot (in “server-side rendering” mode, using linkedom instead of the browser DOM).

First, add the necessary packages to your project:

```bash
yarn add d3 @observablehq/plot linkedom

```

Then create the file:

```auto
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";
import {parseHTML} from "linkedom";

const barley = await d3.csv(
  "https://raw.githubusercontent.com/observablehq/plot/main/test/data/barley.csv",
  d3.autoType
);

function beckerBarley() {
  const {document} = parseHTML("<a>");
  const chart = Plot.plot({
    document,
    marginLeft: 110,
    height: 800,
    grid: true,
    x: {nice: true},
    y: {inset: 5},
    color: {type: "categorical"},
    facet: {marginRight: 90},
    marks: [
      Plot.frame(),
      Plot.dot(barley, {
        x: "yield",
        y: "variety",
        fy: "site",
        stroke: "year",
        sort: {fy: "x", y: "x", reduce: "median", reverse: true}
      })
    ]
  });
  return `${chart}`.replace(
    /^<svg /,
    '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" '
  );
}

process.stdout.write(beckerBarley());

```

This data loader is written in JavaScript and emits svg, it needs a double extension . **svg**.js (save it under `becker.svg.js`).

In a page named `becker.md`, call it like this:

````auto
```js
display(await FileAttachment("becker.svg").image());
```

````

You should then see:

 ![Capture d’écran 2024-02-21 à 19.24.02](https://canada1.discourse-cdn.com/flex030/uploads/observablehq/original/2X/d/d119a04baebd8f9adea3cf4c91621ce5ae108326.png)

To output png instead of svg, you need a supplementary step. We’re going to use [resvg](https://github.com/yisibl/resvg-js):

```bash
yarn add @resvg/resvg-j

```

The data loader must be adapted a little:

```js
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";
import {parseHTML} from "linkedom";
import {Resvg} from "@resvg/resvg-js";

const barley = await d3.csv(
  "https://raw.githubusercontent.com/observablehq/plot/main/test/data/barley.csv",
  d3.autoType
);

function beckerBarley() {
  const {document} = parseHTML("<a>");
  const chart = Plot.plot({
    document,
    marginLeft: 110,
    height: 800,
    grid: true,
    x: {nice: true},
    y: {inset: 5},
    color: {type: "categorical"},
    facet: {marginRight: 90},
    marks: [
      Plot.frame(),
      Plot.dot(barley, {
        x: "yield",
        y: "variety",
        fy: "site",
        stroke: "year",
        sort: {fy: "x", y: "x", reduce: "median", reverse: true}
      })
    ]
  });
  chart.setAttribute("xmlns", "http://www.w3.org/2000/svg");

  return new Resvg(chart.outerHTML, {
    fitTo: {
      mode: "width",
      value: 2 * chart.getAttribute("width")
    }
  })
    .render()
    .asPng();
}

process.stdout.write(beckerBarley());

```

Save this as `becker.png.js` then use:

````auto
```js
display(await FileAttachment("becker.png").image({style: "max-width: 100%;"}));
```

````

I understand that this is a lot of code, and we’ll want to make helper functions to make this easier. But… it works!

---

<div class="post-metadata">

**Author:** ![matvil](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/matvil/32/7356_2.png) [@matvil](https://talk.observablehq.com/u/matvil)\
**Post date:** [February 21, 2024, 7:10pm UTC](https://talk.observablehq.com/t/generate-static-png-or-svg-at-build-time-with-framework/8833/3 "2024-02-21T19:10:42Z")

</div>

Thanks!

Was at first baffled by the http links, but I have now learnt about xml namespaces!

> **[Namespaces crash course - SVG: Scalable Vector Graphics | MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Namespaces_Crash_Course)**
>
> As an XML dialect, SVG is namespaced. It is important to understand the concept of namespaces and how they are used if you plan to author SVG content. Namespaces are essential to user agents that support multiple XML dialects; browsers must be very...
