diff --git a/stacked_area_chart/graphic.js b/stacked_area_chart/graphic.js
new file mode 100644
index 0000000..98f3cc1
--- /dev/null
+++ b/stacked_area_chart/graphic.js
@@ -0,0 +1,79 @@
+var d3 = {
+ ...require("d3-shape/dist/d3-shape.min")
+};
+
+var pym = require("./lib/pym");
+require("./lib/webfonts");
+
+var pymChild;
+var renderAreaChart = require("./renderAreaChart");
+
+//Initialize graphic
+var onWindowLoaded = function() {
+ var series = formatData(window.DATA);
+ render(series);
+
+ window.addEventListener("resize", () => render(series));
+
+ pym.then(child => {
+ pymChild = child;
+ child.sendHeight();
+ });
+};
+
+//Format graphic data for processing by D3.
+var formatData = function(data) {
+ data.forEach(function(d) {
+ if (d.date instanceof Date) return;
+ var [m, day, y] = d.date.split("/").map(Number);
+ y = y > 50 ? 1900 + y : 2000 + y;
+ d.date = new Date(y, m - 1, day);
+
+ let total_amount = 0;
+ for (item in d) {
+ if (item != "date") {
+ total_amount += +d[item];
+ }
+ }
+ d.total_amount = total_amount;
+ });
+
+ // Restructure tabular data for easier charting.
+ var dataKeys = Object.keys(data[0]);
+ var removeItems = ["date","total_amount"];
+ for (var i = 0; i < removeItems.length; i++) {
+
+ let index = dataKeys.indexOf(removeItems[i]);
+ if (index > -1) {
+ dataKeys.splice(index,1)
+ }
+ }
+
+ var stackedData = d3.stack().keys(dataKeys)(data);
+
+ return stackedData;
+};
+
+// Render the graphic(s). Called by pym with the container width.
+var render = function(data) {
+ // Render the chart!
+ var container = "#stacked-area-chart";
+ var element = document.querySelector(container);
+ var width = element.offsetWidth;
+ renderAreaChart({
+ container,
+ width,
+ data,
+ dateColumn: "date",
+ valueColumn: "amt"
+ });
+
+ // Update iframe
+ if (pymChild) {
+ pymChild.sendHeight();
+ }
+};
+
+//Initially load the graphic
+// (NB: Use window.load to ensure all images have loaded)
+window.onload = onWindowLoaded;
diff --git a/stacked_area_chart/graphic.less b/stacked_area_chart/graphic.less
new file mode 100644
index 0000000..9acbab9
--- /dev/null
+++ b/stacked_area_chart/graphic.less
@@ -0,0 +1,25 @@
+@import "./lib/base";
+
+.value text {
+ font-size: 12px;
+ font-weight: bold;
+ fill: #999;
+}
+
+@media screen and (max-width: 500px) {
+ .value text {
+ font-size: 10px;
+ }
+}
+
+@media screen and (min-width: 500px) {
+ .key {
+ display: none;
+ }
+}
+
+@media screen and (max-width: 500px) {
+ .key.one-line {
+ display: none;
+ }
+}
\ No newline at end of file
diff --git a/stacked_area_chart/index.html b/stacked_area_chart/index.html
new file mode 100644
index 0000000..ee498f4
--- /dev/null
+++ b/stacked_area_chart/index.html
@@ -0,0 +1,37 @@
+<%= await t.include("lib/_head.html") %>
+
+<% if (COPY.labels.headline) { %>
+
<%= t.smarty(COPY.labels.headline) %>
+<% } %>
+
+<% if (COPY.labels.subhed) { %>
+<%= t.smarty(COPY.labels.subhed) %>
+<% } %>
+
+
+ aria-label="<%- COPY.labels.screenreader %>"
+<% } %>
+>
+
+
+
+<% if (COPY.labels.footnote) { %>
+
+<% } %>
+
+
+
+
+
+
+
+<%= await t.include("lib/_foot.html") %>
\ No newline at end of file
diff --git a/stacked_area_chart/manifest.json b/stacked_area_chart/manifest.json
new file mode 100644
index 0000000..9601c97
--- /dev/null
+++ b/stacked_area_chart/manifest.json
@@ -0,0 +1,52 @@
+{
+ "templateSheet": "1DLxMcQRpyp1rqGJTjC28jJH5Df1GYrJrJnBl2PW9-MU",
+ "files": [
+ "*.html",
+ "!_*.html",
+ "graphic.js",
+ "graphic.less",
+ "*.png",
+ "*.jpg",
+ "*.gif",
+ "*.json",
+ "!manifest.json",
+ "*.geojson",
+ "*.csv"
+ ],
+ "sheet": "1hvYBq5P5ud1OEHxEDLqLbnUawnQXRmYKfK_3MWQcExs",
+ "installedPackagesAtCreation": {
+ "component-leaflet-map": "0.0.17",
+ "d3": "^5.7.0",
+ "d3-array": "^2.0.3",
+ "d3-axis": "^1.0.12",
+ "d3-color": "^1.2.3",
+ "d3-dispatch": "^1.0.5",
+ "d3-ease": "^1.0.5",
+ "d3-fetch": "^1.1.2",
+ "d3-force": "^2.0.1",
+ "d3-geo": "^1.11.3",
+ "d3-geo-projection": "^2.6.0",
+ "d3-hierarchy": "^1.1.9",
+ "d3-path": "^1.0.7",
+ "d3-sankey": "^0.7.1",
+ "d3-scale": "^2.2.2",
+ "d3-scale-chromatic": "^1.5.0",
+ "d3-selection": "^1.4.0",
+ "d3-shape": "^1.2.2",
+ "d3-svg": "^0.2.2",
+ "d3-time": "^1.0.10",
+ "d3-time-format": "^2.2.3",
+ "d3-timer": "^1.0.9",
+ "d3-transform": "^1.0.5",
+ "d3-transition": "^1.2.0",
+ "jquery": "^3.3.1",
+ "mapshaper": "^0.4.154",
+ "readjson": "^1.1.4",
+ "tablesort": "^5.1.0",
+ "textures": "^1.2.2",
+ "topojson": "^3.0.2",
+ "topojson-client": "^3.0.1",
+ "wherewolf": "^1.0.3",
+ "world-atlas": "^2.0.2"
+ }
+}
\ No newline at end of file
diff --git a/stacked_area_chart/renderAreaChart.js b/stacked_area_chart/renderAreaChart.js
new file mode 100644
index 0000000..99617a7
--- /dev/null
+++ b/stacked_area_chart/renderAreaChart.js
@@ -0,0 +1,225 @@
+var d3 = {
+ ...require("d3-axis/dist/d3-axis.min"),
+ ...require("d3-scale/dist/d3-scale.min"),
+ ...require("d3-selection/dist/d3-selection.min"),
+ ...require("d3-shape/dist/d3-shape.min"),
+ ...require("d3-interpolate/dist/d3-interpolate.min")
+};
+
+var { COLORS, classify, makeTranslate } = require("./lib/helpers");
+var { yearFull, yearAbbrev } = require("./lib/helpers/formatDate");
+var { isMobile } = require("./lib/breakpoints");
+
+// Render a area chart.
+module.exports = function(config) {
+
+ // Setup
+ var { dateColumn, valueColumn } = config;
+
+ var aspectWidth = isMobile.matches ? 4 : 16;
+ var aspectHeight = isMobile.matches ? 3 : 9;
+
+ var margins = {
+ top: 5,
+ right: 75,
+ bottom: 20,
+ left: 30
+ };
+
+ var ticksX = 10;
+ var ticksY = 10;
+ var roundTicksFactor = 5;
+
+ // Mobile
+ if (isMobile.matches) {
+ ticksX = 5;
+ ticksY = 5;
+ margins.right = 25;
+ }
+
+ // Calculate actual chart dimensions
+ var chartWidth = config.width - margins.left - margins.right;
+ var chartHeight =
+ Math.ceil((config.width * aspectHeight) / aspectWidth) -
+ margins.top -
+ margins.bottom;
+
+ // Clear existing graphic (for redraw)
+ var containerElement = d3.select(config.container);
+ containerElement.html("");
+
+ var dates = config.data[0].map(d => {
+ return d.data.date
+ })
+
+ var extent = [dates[0], dates[dates.length - 1]];
+
+ var xScale = d3
+ .scaleTime()
+ .domain(extent)
+ .range([0, chartWidth]);
+
+ var values = config.data[0].map(d => d.data.total_amount);
+
+ var floors = values.map(
+ v => Math.floor(v / roundTicksFactor) * roundTicksFactor
+ );
+ var min = Math.min.apply(null, floors);
+
+ if (min > 0) {
+ min = 0;
+ }
+
+ var ceilings = values.map(
+ v => Math.ceil(v / roundTicksFactor) * roundTicksFactor
+ );
+ var max = Math.max.apply(null, ceilings);
+
+ if (min > 0) {
+ min = 0;
+ }
+
+ var yScale = d3
+ .scaleLinear()
+ .domain([min, max])
+ .range([chartHeight, 0]);
+
+ var colorScale = d3
+ .scaleOrdinal()
+ .domain(
+ config.data.map(function(d) {
+ return d.name;
+ })
+ )
+ .range([
+ COLORS.red3,
+ COLORS.yellow3,
+ COLORS.blue3,
+ COLORS.orange3,
+ COLORS.teal3
+ ]);
+
+ // Render the HTML legend.
+
+ var oneLine = config.data.length > 1 ? "" : " one-line";
+
+ var legend = containerElement
+ .append("ul")
+ .attr("class", "key" + oneLine)
+ .selectAll("g")
+ .data(config.data)
+ .enter()
+ .append("li")
+ .attr("class", d => "key-item " + classify(d.key));
+
+ legend.append("b").style("background-color", d => colorScale(d.key));
+
+ legend.append("label").text(d => d.key);
+
+ // Create the root SVG element.
+
+ var chartWrapper = containerElement
+ .append("div")
+ .attr("class", "graphic-wrapper");
+
+ var chartElement = chartWrapper
+ .append("svg")
+ .attr("width", chartWidth + margins.left + margins.right)
+ .attr("height", chartHeight + margins.top + margins.bottom)
+ .append("g")
+ .attr("transform", `translate(${margins.left},${margins.top})`);
+
+ // Create D3 axes.
+
+ var xAxis = d3
+ .axisBottom()
+ .scale(xScale)
+ .ticks(ticksX)
+ .tickFormat(function(d, i) {
+ if (isMobile.matches) {
+ return "\u2019" + yearAbbrev(d);
+ } else {
+ return yearFull(d);
+ }
+ });
+
+ var yAxis = d3
+ .axisLeft()
+ .scale(yScale)
+ .ticks(ticksY);
+
+ // Render axes to chart.
+
+ chartElement
+ .append("g")
+ .attr("class", "x axis")
+ .attr("transform", makeTranslate(0, chartHeight))
+ .call(xAxis);
+
+ chartElement
+ .append("g")
+ .attr("class", "y axis")
+ .call(yAxis);
+
+ // Render grid to chart.
+
+ var xAxisGrid = function() {
+ return xAxis;
+ };
+
+ var yAxisGrid = function() {
+ return yAxis;
+ };
+
+ chartElement
+ .append("g")
+ .attr("class", "x grid")
+ .attr("transform", makeTranslate(0, chartHeight))
+ .call(
+ xAxisGrid()
+ .tickSize(-chartHeight, 0, 0)
+ .tickFormat("")
+ );
+
+ chartElement
+ .append("g")
+ .attr("class", "y grid")
+ .call(
+ yAxisGrid()
+ .tickSize(-chartWidth, 0, 0)
+ .tickFormat("")
+ );
+
+ // Render 0 value line.
+
+ if (min < 0) {
+ chartElement
+ .append("line")
+ .attr("class", "zero-line")
+ .attr("x1", 0)
+ .attr("x2", chartWidth)
+ .attr("y1", yScale(0))
+ .attr("y2", yScale(0));
+ }
+
+ // Render areas to chart.
+
+ var areaGen = d3
+ .area()
+ // .curve(d3.curveStepBefore)
+ .x(d => xScale(d.data[dateColumn]))
+ .y0(function (d) {
+ return yScale(d[0]);
+ })
+ .y1(d => yScale(d[1]));
+
+ chartElement
+ .append("g")
+ .attr("class","areas")
+ .selectAll("path")
+ .data(config.data)
+ .join("path")
+ .attr("fill", d => colorScale(d.key))
+ .attr("d", areaGen)
+
+};