🏠 back to Observable

Rewrite observable example in raw HTML + JS

Hello,

I’m trying to make very basic graph with some user interactivity.
I’ve read whole Learn D3 but as this tutorial is centered around using Observable I quickly hit problems when trying to make something on my own (Observable is nice, but I really do not like working with interactive documents while learning stuff and I much rather work with raw HTML + JS to understand how it actually works).

After some troubles I managed to write this very basic example with line plot:

<!DOCTYPE html>
<html>

<head>
    <!-- Load d3.js -->
    <script src="d3.min.js"></script>
    <script src="d3-fetch@3.js"></script>

    <link rel="stylesheet" href="style.css">
</head>

<body>
    <div id="container">
        <svg id="graph"></svg>
    </div>
</body>

<script src="main.js"></script>

</html>
(async function () {

    const data = await d3.dsv(",", "data.csv", (d) => {
        return {
            date: new Date(d.date),
            close: +d.close
        };
    });

    // Select the svg area
    const svg = d3.select("#graph");

    let margin = ({ top: 20, right: 30, bottom: 30, left: 40 });
    let width = 1800;
    let height = 900;

    let x = d3.scaleUtc()
        .domain(d3.extent(data, d => d.date))
        .range([margin.left, width - margin.right]);

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

    let xAxis = g => g
        .attr("transform", `translate(0,${height - margin.bottom})`)
        .call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0));

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

    let line = d3.line().x(d => x(d.date)).y(d => y(d.close));

    svg.append("path")
        .attr("fill", "none")
        .attr("stroke", "steelblue")
        .attr("stroke-width", 1.5)
        .attr("stroke-miterlimit", 1)
        .attr("d", line(data));

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

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

})()

It basically recreates graph from the section Interaction from the tutorial, but without tooltips.

Later I saw this interactive example Focus + Context and I would like to implement something similar but I’m not sure how to rewrite this named cells inside raw JS? It should be some kind of function that return… what? svg.node();?

I don’t what to do next or where to look for explanations.

There happens to be an old pre-Observable plain JavaScript version of that example:

In general, Observable cells render content by returning a DOM node which Observable inserts into the page above that cell’s code editor. (If a cell returns a DOM node, Observable inserts it; if a cell returns any other kind of JavaScript value, Observable shows an inspector view of it.)

To rewrite an Observable cell in plain JavaScript you have to (among other things) insert the DOM node yourself, e.g. with appendChild. For example, if there’s an Observable notebook with two cells:

data = [10, 20, 30, 50, 80]
chart = {
  const sel = d3.create("svg");
  sel.selectAll("circle")
    .data(data)
    .join("circle")
    .attr("opacity", 0.2)
    .attr("r", (d) => d);
  return sel.node();
}

Then you could rewrite that in plain JavaScript as:

// add const keyword
const data = [10, 20, 30, 50, 80]

// if a cell references other cells, make it a function where the 
// parameters are the names of the cells it references, here "data"
function chart(data) {
  const sel = d3.create("svg");
  sel.selectAll("circle")
    .data(data)
    .join("circle")
    .attr("opacity", 0.2)
    .attr("r", (d) => d);
  return sel.node();
}

// call chart with data and append it to the document
document.body.appendChild(chart(data))

That’s a very simplified example meant to just illustrate a couple of the general principles of translating to plain JavaScript. In more complicated notebooks, cells may be asynchronous and/or generators. Also, many notebooks use functions from Observable’s open-source standard library, which you’d have to load or replace.

You can also use the Observable runtime to render notebooks in plain JavaScript without rewriting the notebook at all.