🏠 back to Observable

Using Observable code outside of Observable

In this notebook for stacked bar chart, under “To use this chart outside of Observable” they write “To render a chart, pass StackedBarChart an array of data and any desired options; it will return an SVG element that you can insert into the DOM.”

I’m working outside of Observable using Visual Studio Code, inserting the SVG element into the DOM but I’m not getting a figure.

Would it be possible for someone to share a full example of using this code outside Observable, including the index.html as well as script.js?

Here’s an example in CodePen:

I’ve copy-pasted the StackedBarChart function, the (preprocessed) data, and the array of ages, and I’ve defined a width. I invoke StackedBarChart in the same way as in the notebook, assign it to const chart, and then insert it into the DOM with this line:

document.body.appendChild(chart);

Let me know if you have any questions.

1 Like

Here is an example using the @hpcc-js/observable-md:

<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title></title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@hpcc-js/common/font-awesome/css/font-awesome.min.css">
    <style>
    body {
        padding: 0px;
        margin: 8px;
        background: white;
        color: black;
    }
    #placeholder {
        position: absolute;
        left: 8px;
        top: 8px;
        right: 8px;
        bottom: 8px;
        max-width: 480px;
    }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/@hpcc-js/observable-md/dist/index.full.js" type="text/javascript" charset="utf-8"></script>
    <script>
        var omdMod = window["@hpcc-js/observable-md"]
    </script>

</head>

<body onresize="doResize()">
    <div id="placeholder">
    </div>
    <script>
        var app = new omdMod.Observable()
            .target("placeholder")
            .showValues(true)
            .mode("omd")
            .text(`
# Stacked Bar Chart

This chart shows the estimated population by age and U.S. state. Compare to [horizontal stacked bars](/@d3/stacked-horizontal-bar-chart), [normalized stacked bars](/@d3/stacked-normalized-horizontal-bar), [grouped bars](https://observablehq.com/@d3/grouped-bar-chart) and a [dot plot](/@d3/dot-plot). Data: [American Community Survey](/@mbostock/working-with-the-census-api)
\`\`\`
key = Legend(chart.scales.color, {title: "Age (years)"}) // try also Swatches

chart = StackedBarChart(stateages, {
  x: d => d.state,
  y: d => d.population / 1e6,
  z: d => d.age,
  xDomain: d3.groupSort(stateages, D => d3.sum(D, d => -d.population), d => d.state),
  yLabel: "↑ Population (millions)",
  zDomain: ages,
  colors: d3.schemeSpectral[ages.length],
  width,
  height: 500
})

states = FileAttachment(/* "us-population-state-age.csv" */"https://static.observableusercontent.com/files/cacf3b872e296fd3cf25b9b8762dc0c3aa1863857ecba3f23e8da269c584a4cea9db2b5d390b103c7b386586a1104ce33e17eee81b5cc04ee86929f1ee599bfe").csv({typed: true})

ages = states.columns.slice(1)

stateages = ages.flatMap(age => states.map(d => ({state: d.name, age, population: d[age]}))) // pivot longer

howto("StackedBarChart")

altplot(\`Plot.plot({
  width,
  y: {tickFormat: "s"},
  color: {scheme: "spectral", domain: ages},
  marks: [
    Plot.barY(stateages, {
      x: "state",
      y: "population",
      fill: "age",
      sort: {x: "y", reverse: true}
    }),
    Plot.ruleY([0])
  ]
})\`)

// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/stacked-bar-chart
function StackedBarChart(data, {
  x = (d, i) => i, // given d in data, returns the (ordinal) x-value
  y = d => d, // given d in data, returns the (quantitative) y-value
  z = () => 1, // given d in data, returns the (categorical) z-value
  title, // given d in data, returns the title text
  marginTop = 30, // top margin, in pixels
  marginRight = 0, // right margin, in pixels
  marginBottom = 30, // bottom margin, in pixels
  marginLeft = 40, // left margin, in pixels
  width = 640, // outer width, in pixels
  height = 400, // outer height, in pixels
  xDomain, // array of x-values
  xRange = [marginLeft, width - marginRight], // [left, right]
  xPadding = 0.1, // amount of x-range to reserve to separate bars
  yType = d3.scaleLinear, // type of y-scale
  yDomain, // [ymin, ymax]
  yRange = [height - marginBottom, marginTop], // [bottom, top]
  zDomain, // array of z-values
  offset = d3.stackOffsetDiverging, // stack offset method
  order = d3.stackOrderNone, // stack order method
  yFormat, // a format specifier string for the y-axis
  yLabel, // a label for the y-axis
  colors = d3.schemeTableau10, // array of colors
} = {}) {
  // Compute values.
  const X = d3.map(data, x);
  const Y = d3.map(data, y);
  const Z = d3.map(data, z);

  // Compute default x- and z-domains, and unique them.
  if (xDomain === undefined) xDomain = X;
  if (zDomain === undefined) zDomain = Z;
  xDomain = new d3.InternSet(xDomain);
  zDomain = new d3.InternSet(zDomain);

  // Omit any data not present in the x- and z-domains.
  const I = d3.range(X.length).filter(i => xDomain.has(X[i]) && zDomain.has(Z[i]));

  // Compute a nested array of series where each series is [[y1, y2], [y1, y2],
  // [y1, y2], …] representing the y-extent of each stacked rect. In addition,
  // each tuple has an i (index) property so that we can refer back to the
  // original data point (data[i]). This code assumes that there is only one
  // data point for a given unique x- and z-value.
  const series = d3.stack()
      .keys(zDomain)
      .value(([x, I], z) => Y[I.get(z)])
      .order(order)
      .offset(offset)
    (d3.rollup(I, ([i]) => i, i => X[i], i => Z[i]))
    .map(s => s.map(d => Object.assign(d, {i: d.data[1].get(s.key)})));

  // Compute the default y-domain. Note: diverging stacks can be negative.
  if (yDomain === undefined) yDomain = d3.extent(series.flat(2));

  // Construct scales, axes, and formats.
  const xScale = d3.scaleBand(xDomain, xRange).paddingInner(xPadding);
  const yScale = yType(yDomain, yRange);
  const color = d3.scaleOrdinal(zDomain, colors);
  const xAxis = d3.axisBottom(xScale).tickSizeOuter(0);
  const yAxis = d3.axisLeft(yScale).ticks(height / 60, yFormat);

  // Compute titles.
  if (title === undefined) {
    const formatValue = yScale.tickFormat(100, yFormat);
    title = i => \`\${X[i]}\\n\${Z[i]}\\n\${formatValue(Y[i])}\`;
  } else {
    const O = d3.map(data, d => d);
    const T = title;
    title = i => T(O[i], i, data);
  }

  const svg = d3.create("svg")
      .attr("width", width)
      .attr("height", height)
      .attr("viewBox", [0, 0, width, height])
      .attr("style", "max-width: 100%; height: auto; height: intrinsic;");

  svg.append("g")
      .attr("transform", \`translate(\${marginLeft},0)\`)
      .call(yAxis)
      .call(g => g.select(".domain").remove())
      .call(g => g.selectAll(".tick line").clone()
          .attr("x2", width - marginLeft - marginRight)
          .attr("stroke-opacity", 0.1))
      .call(g => g.append("text")
          .attr("x", -marginLeft)
          .attr("y", 10)
          .attr("fill", "currentColor")
          .attr("text-anchor", "start")
          .text(yLabel));

  const bar = svg.append("g")
    .selectAll("g")
    .data(series)
    .join("g")
      .attr("fill", ([{i}]) => color(Z[i]))
    .selectAll("rect")
    .data(d => d)
    .join("rect")
      .attr("x", ({i}) => xScale(X[i]))
      .attr("y", ([y1, y2]) => Math.min(yScale(y1), yScale(y2)))
      .attr("height", ([y1, y2]) => Math.abs(yScale(y1) - yScale(y2)))
      .attr("width", xScale.bandwidth());

  if (title) bar.append("title")
      .text(({i}) => title(i));

  svg.append("g")
      .attr("transform", \`translate(0,\${yScale(0)})\`)
      .call(xAxis);

  return Object.assign(svg.node(), {scales: {color}});
}

import {Legend, Swatches} from "@d3/color-legend"

import {howto, altplot} from "@d3/example-components"
\`\`\``)
            ;

        doResize();

        function doResize() {
        if (app) {
            app
                .resize()
                .lazyRender()
                ;
        }
    }
    </script>
</body>

</html>

  1. Installed the following VS Code extension: Observable JS - Visual Studio Marketplace
  2. Created an empty test.omd file.
  3. Imported the notebook by URL
  4. Exported to html