Export observablehq js to vanilla js

I have basic bar chart which is working in observablehq. Now trying to add the cells to my existing code does not seems to work. Any help on this ?

In my files:

HTML:

<script src="https://d3js.org/d3.v5.js"></script>

JS:

function drawBarChart() {

    var data = analytics; // from another global var

    var margin = ({ top: 20, right: 0, bottom: 30, left: 40 });

    var height = 500;
    var width = 600;

    yAxis = g => g
        .attr("transform", `translate(${margin.left},0)`)
        .call(d3.axisLeft(y))
        .call(g => g.select(".domain").remove())

    xAxis = g => g
        .attr("transform", `translate(0,${height - margin.bottom})`)
        .call(d3.axisBottom(x).tickFormat(i => data[i].name).tickSizeOuter(0))

    y = d3.scaleLinear()
        .domain([0, d3.max(data, d => d.value)]).nice()
        .range([height - margin.bottom, margin.top])

    x = d3.scaleBand()
        .domain(d3.range(data.length))
        .range([margin.left, width - margin.right])
        .padding(0.1)


    const svg = d3.select("#graph")
        .attr("viewBox", [0, 0, width, height]);

    svg.append("g")
        .attr("fill", "steelblue")
        .selectAll("rect")
        .data(data)
        .join("rect")
        .attr("x", (d, i) => x(i))
        .attr("y", d => y(d.value))
        .attr("height", d => y(0) - y(d.value))
        .attr("width", x.bandwidth());

    svg.append("g")
        .call(xAxis);

    svg.append("g")
        .call(yAxis);

    return svg.node();

}

Will there be less code if I did write my own d3.js code without observablehq ? I used this template because it fit exactly my need.

Thanks.

1 Like

Hi Kogam, there is a really helpful notebook about embedding and downloading observable notebooks to your code: https://observablehq.com/@observablehq/downloading-and-embedding-notebooks

But if all you want to do is embed this to your web page, try clicking on the embed option next to the cell, the code snippet when placed on your webpage will embed that particular cell.

But is it easier to just create it without observable and will it be less code? Most probably: yes, if you look at a blocks example or something on codepen, the only dependency you have is d3, but with the notebook you will get the observable run time, its not huge, it runs fast and is very easy to embed and modify certain cells programatically. Depending on where you are currently on the learning curve for JS+D3 it might be easier to learn with a simple block and master them, and with some additional build steps you should be able to create a workflow that integrates the notebooks and your production code.

2 Likes

I agree with @a10k that it would be well worth exploring the the notebook on embedding notebooks. I think thatā€™s probably the preferred way to go.

Having said that, you probably want to fully understand client side development anyway and itā€™s not particularly hard to translate your code to a version that works in a webpage. Iā€™ve typed out a minimal but fully functional HTML document thatā€™s quite close to your Observable code. The <script> portion is copied almost verbatim from your notebook, with a few small changes:

  1. I defined width at the beginning. Of course, thatā€™s a dynamic variable in the Observable runtime.
  2. I wrapped the definition of chart in a self-invoking function call.
  3. I appended chart to a div thatā€™s created in the HTML.

The more you use Observable, though, the more youā€™ll use features of the Runtime and the more involved this process would become. So, again, look into embedding!


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Observable to HTML</title>
    <script src="https://d3js.org/d3.v5.js"></script>
  </head>
  <body>
    <h1>Observable to HMTL</h1>
    <div id='container' style="width:800px; height:500px">
    <script>
      width = 800;
      height = 500;
      margin = ({top: 20, right: 0, bottom: 30, left: 40});

      data = ([
        {"name": "angry", "value": 222},
        {"name": "disgust", "value": 6},
        {"name": "fear", "value": 7},
        {"name": "happy", "value": 9},
        {"name": "sad", "value": 10},
        {"name": "surprise", "value": 15},
        {"name": "neutral", "value": 13},
      ]);

      x = d3.scaleBand()
        .domain(d3.range(data.length))
        .range([margin.left, width - margin.right])
        .padding(0.1);
      y = d3.scaleLinear()
        .domain([0, d3.max(data, d => d.value)]).nice()
        .range([height - margin.bottom, margin.top]);
      xAxis = g => g
        .attr("transform", `translate(0,${height - margin.bottom})`)
        .call(d3.axisBottom(x).tickFormat(i => data[i].name).tickSizeOuter(0));
      yAxis = g => g
          .attr("transform", `translate(${margin.left},0)`)
          .call(d3.axisLeft(y))
          .call(g => g.select(".domain").remove());

      chart = (function(){
        const svg = d3.create("svg")
            .attr("viewBox", [0, 0, width, height]);

        svg.append("g")
            .attr("fill", "steelblue")
          .selectAll("rect")
          .data(data)
          .join("rect")
            .attr("x", (d, i) => x(i))
            .attr("y", d => y(d.value))
            .attr("height", d => y(0) - y(d.value))
            .attr("width", x.bandwidth());

        svg.append("g")
            .call(xAxis);

        svg.append("g")
            .call(yAxis);

        return svg.node();
    })();

    d3.select('#container').append(() => chart)
  </script>
  </body>
</html>
2 Likes

Iā€™m not having any problems running your codeā€¦

http://bl.ocks.org/mbostock/raw/d40393e1eac233fc3aff8ad8d91d29c6/

But as others have said, I wouldnā€™t recommend using Observable this way: Observable is not JavaScript, so you shouldnā€™t expect to be able to copy-and-paste Observable cells into vanilla JavaScript and expect it to work. It can work if you know what youā€™re doing, but if you use the Embed code action in the cell menu as described in the Downloading and Embedding notebook, itā€™ll work automatically and you wonā€™t have to understand everything thatā€™s happening under the hood.

And when you use embedding, you can override the data (or any other variable!) with your local definition, too. See module.redefine.

3 Likes

I did read that earlier but what put me off is that Iā€™ll be introducing much than just a bar chart + d3.js CDN into my code.

Exactly. I think Iā€™ll try getting myself familiarized with the way observable woks in the near future.

@mcmcclur I will have to thank you for the huge help you provided with the explanation and the working code.

Thanks again everyone. Have a nice day.

Edit: @mbostock Thanks for the info :slight_smile:

1 Like

Are you concerned about the runtime? Itā€™s 16KB gzipped.

You can use your own self-hosted copy of D3 if you donā€™t want to load it from jsDelivr or d3js.org (and in either case I recommend using the minified version in production, which require gives you, because itā€™ll save you 40KB).

2 Likes

I shall give it a try then. Thanks for the prompt reply.