From 80b2f9238d2eff12f2013132cc3ef3c586587587 Mon Sep 17 00:00:00 2001 From: Daniel Wood Date: Thu, 4 Feb 2021 13:50:38 -0500 Subject: [PATCH 1/6] add stacked area chart --- stacked_area_chart/graphic.js | 80 +++++ stacked_area_chart/graphic.less | 31 ++ stacked_area_chart/index.html | 37 ++ stacked_area_chart/lib/_foot.html | 2 + stacked_area_chart/lib/_head.html | 13 + stacked_area_chart/lib/analytics.js | 88 +++++ stacked_area_chart/lib/base.less | 339 ++++++++++++++++++ stacked_area_chart/lib/breakpoints.js | 4 + stacked_area_chart/lib/debounce.js | 11 + stacked_area_chart/lib/delegate.js | 10 + stacked_area_chart/lib/dot.js | 11 + stacked_area_chart/lib/helpers/classify.js | 8 + stacked_area_chart/lib/helpers/colors.js | 7 + stacked_area_chart/lib/helpers/fmtComma.js | 1 + stacked_area_chart/lib/helpers/formatDate.js | 18 + stacked_area_chart/lib/helpers/formatStyle.js | 9 + stacked_area_chart/lib/helpers/getAPMonth.js | 5 + stacked_area_chart/lib/helpers/getLocation.js | 5 + .../lib/helpers/getParameterByName.js | 3 + stacked_area_chart/lib/helpers/index.js | 18 + .../lib/helpers/isProduction.js | 16 + .../lib/helpers/makeTranslate.js | 1 + .../lib/helpers/urlToLocation.js | 5 + stacked_area_chart/lib/helpers/wrapText.js | 61 ++++ stacked_area_chart/lib/pym.js | 37 ++ stacked_area_chart/lib/qsa.js | 5 + stacked_area_chart/lib/webfonts.js | 19 + stacked_area_chart/lib/xhr.js | 17 + stacked_area_chart/manifest.json | 52 +++ stacked_area_chart/renderLineChart.js | 265 ++++++++++++++ 30 files changed, 1178 insertions(+) create mode 100644 stacked_area_chart/graphic.js create mode 100644 stacked_area_chart/graphic.less create mode 100644 stacked_area_chart/index.html create mode 100644 stacked_area_chart/lib/_foot.html create mode 100644 stacked_area_chart/lib/_head.html create mode 100644 stacked_area_chart/lib/analytics.js create mode 100644 stacked_area_chart/lib/base.less create mode 100644 stacked_area_chart/lib/breakpoints.js create mode 100644 stacked_area_chart/lib/debounce.js create mode 100644 stacked_area_chart/lib/delegate.js create mode 100644 stacked_area_chart/lib/dot.js create mode 100644 stacked_area_chart/lib/helpers/classify.js create mode 100644 stacked_area_chart/lib/helpers/colors.js create mode 100644 stacked_area_chart/lib/helpers/fmtComma.js create mode 100644 stacked_area_chart/lib/helpers/formatDate.js create mode 100644 stacked_area_chart/lib/helpers/formatStyle.js create mode 100644 stacked_area_chart/lib/helpers/getAPMonth.js create mode 100644 stacked_area_chart/lib/helpers/getLocation.js create mode 100644 stacked_area_chart/lib/helpers/getParameterByName.js create mode 100644 stacked_area_chart/lib/helpers/index.js create mode 100644 stacked_area_chart/lib/helpers/isProduction.js create mode 100644 stacked_area_chart/lib/helpers/makeTranslate.js create mode 100644 stacked_area_chart/lib/helpers/urlToLocation.js create mode 100644 stacked_area_chart/lib/helpers/wrapText.js create mode 100644 stacked_area_chart/lib/pym.js create mode 100644 stacked_area_chart/lib/qsa.js create mode 100644 stacked_area_chart/lib/webfonts.js create mode 100644 stacked_area_chart/lib/xhr.js create mode 100644 stacked_area_chart/manifest.json create mode 100644 stacked_area_chart/renderLineChart.js diff --git a/stacked_area_chart/graphic.js b/stacked_area_chart/graphic.js new file mode 100644 index 0000000..508a9e7 --- /dev/null +++ b/stacked_area_chart/graphic.js @@ -0,0 +1,80 @@ +var d3 = { + ...require("d3-shape/dist/d3-shape.min") +}; + +var pym = require("./lib/pym"); +require("./lib/webfonts"); + +var pymChild; +var renderLineChart = require("./renderLineChart"); + +//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) { + var series = []; + + 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); + }); + + // Restructure tabular data for easier charting. + for (var column in data[0]) { + if (column == "date") continue; + + series.push({ + name: column, + values: data.map(d => ({ + date: d.date, + amt: d[column] + })) + }); + } + + var dataKeys = Object.keys(data[0]).slice(1); + var stackedData = d3.stack().keys(dataKeys)(data); + + // for (var i = 0; i < stackedData.length; i++) { + // stackedData[i] + // } + + return stackedData; +}; + +// Render the graphic(s). Called by pym with the container width. +var render = function(data) { + // Render the chart! + var container = "#line-chart"; + var element = document.querySelector(container); + var width = element.offsetWidth; + renderLineChart({ + 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..6db58a7 --- /dev/null +++ b/stacked_area_chart/graphic.less @@ -0,0 +1,31 @@ +@import "./lib/base"; + +.lines { + fill: none; + stroke-width: 3px; + stroke: #ccc; +} + +.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..5167fef --- /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) %>

+<% } %> + + + +<% if (COPY.labels.footnote) { %> +
+

Notes

+

<%= COPY.labels.footnote %>

+
+<% } %> + + + + + + + +<%= await t.include("lib/_foot.html") %> \ No newline at end of file diff --git a/stacked_area_chart/lib/_foot.html b/stacked_area_chart/lib/_foot.html new file mode 100644 index 0000000..691287b --- /dev/null +++ b/stacked_area_chart/lib/_foot.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/stacked_area_chart/lib/_head.html b/stacked_area_chart/lib/_head.html new file mode 100644 index 0000000..adbf375 --- /dev/null +++ b/stacked_area_chart/lib/_head.html @@ -0,0 +1,13 @@ + + + + + + <%= slug %> + + + + + + + \ No newline at end of file diff --git a/stacked_area_chart/lib/analytics.js b/stacked_area_chart/lib/analytics.js new file mode 100644 index 0000000..4f75032 --- /dev/null +++ b/stacked_area_chart/lib/analytics.js @@ -0,0 +1,88 @@ +/* + * Module for tracking standardized analytics. + */ + +var { getParameterByName, urlToLocation } = require("./helpers"); + +var ANALYTICS = (function () { + /* + * Google Analytics + */ + var DIMENSION_PARENT_URL = 'dimension1'; + var DIMENSION_PARENT_HOSTNAME = 'dimension2'; + var DIMENSION_PARENT_INITIAL_WIDTH = 'dimension3'; + + var setupGoogle = function() { + (function(i,s,o,g,r,a,m) { + i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); + + ga('create', window.GOOGLE_ANALYTICS_ID, 'auto'); + + // By default Google tracks the query string, but we want to ignore it. + var location = window.location.protocol + + '//' + window.location.hostname + + window.location.pathname; + + ga('set', 'location', location); + ga('set', 'page', window.location.pathname); + + // Custom dimensions & metrics + var parentUrl = getParameterByName('parentUrl') || ''; + var parentHostname = ''; + + if (parentUrl) { + parentHostname = urlToLocation(parentUrl).hostname; + } + + var initialWidth = getParameterByName('initialWidth') || ''; + + var customData = {}; + customData[DIMENSION_PARENT_URL] = parentUrl; + customData[DIMENSION_PARENT_HOSTNAME] = parentHostname; + customData[DIMENSION_PARENT_INITIAL_WIDTH] = initialWidth; + + // Track pageview + ga('send', 'pageview', customData); + } + + /* + * Event tracking. + */ + var trackEvent = function(eventName, label, value) { + var eventData = { + 'hitType': 'event', + 'eventCategory': document.title, + 'eventAction': eventName + } + + if (label) { + eventData['eventLabel'] = label; + } + + if (value) { + eventData['eventValue'] = value + } + + // Track details about the parent with each event + var parentUrl = getParameterByName('parentUrl') || ''; + var parentHostname = ''; + if (parentUrl) { + parentHostname = urlToLocation(parentUrl).hostname; + } + eventData[DIMENSION_PARENT_URL] = parentUrl; + eventData[DIMENSION_PARENT_HOSTNAME] = parentHostname; + + ga('send', eventData); + } + + setupGoogle(); + + return { + 'trackEvent': trackEvent + }; +}()); + +module.exports = ANALYTICS; \ No newline at end of file diff --git a/stacked_area_chart/lib/base.less b/stacked_area_chart/lib/base.less new file mode 100644 index 0000000..7df8d1a --- /dev/null +++ b/stacked_area_chart/lib/base.less @@ -0,0 +1,339 @@ +// Media queries +@screen-medium-above: ~"screen and (min-width: 651px)"; +@screen-mobile-above: ~"screen and (min-width: 501px)"; +@screen-mobile: ~"screen and (max-width: 500px)"; + +// Colors +@red1 : #6C2315; +@red2 : #A23520; +@red3 : #D8472B; +@red4 : #E27560; +@red5 : #ECA395; +@red6 : #F5D1CA; + +@orange1 : #714616; +@orange2 : #AA6A21; +@orange3 : #E38D2C; +@orange4 : #EAAA61; +@orange5 : #F1C696; +@orange6 : #F8E2CA; + +@yellow1 : #77631B; +@yellow2 : #B39429; +@yellow3 : #EFC637; +@yellow4 : #F3D469; +@yellow5 : #F7E39B; +@yellow6 : #FBF1CD; + +@teal1 : #0B403F; +@teal2 : #11605E; +@teal3 : #17807E; +@teal4 : #51A09E; +@teal5 : #8BC0BF; +@teal6 : #C5DFDF; + +@blue1 : #28556F; +@blue2 : #3D7FA6; +@blue3 : #51AADE; +@blue4 : #7DBFE6; +@blue5 : #A8D5EF; +@blue6 : #D3EAF7; + +// Fonts +.gotham() { + font-family: 'Gotham SSm',Helvetica,Arial,sans-serif; + font-weight: normal; + font-weight: 400; +} + +// Normal Knockout +.knockout() { + font-family: 'Knockout 31 4r','Helvetica Neue', 'Helvetica', 'Arial', sans-serif; + font-weight: normal; +} + +// Knockout, uppercased +.knockout-upper() { + .knockout(); + text-transform: uppercase; +} + +// Knockout for headings +.knockout-header() { + .knockout-upper(); + letter-spacing: 0.05em; + -webkit-font-smoothing: antialiased; +} + +.sans-serif() { + font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; +} + +// Mixins +.clearfix() { + &::after { + content: ""; + display: block; + height: 0; + clear: both; + } +} + +// NPR multimedia-template +.mmedia-constrained() { + margin-left: auto; + margin-right: auto; + max-width: 650px; +} + +.mmedia-constrained-centered() { + .mmedia-constrained(); + text-align: center; +} + +// Base styles +* { + box-sizing: border-box; + //Safari, you're the worst + -webkit-tap-highlight-color: transparent; +} + +html { -webkit-text-size-adjust: none; } + +body { + margin: 0; + padding: 33px 0; + font: 14px/1.4 Helvetica, Arial, sans-serif; + color: #555; + //remove 300ms click delay in Safari, AKA still the worst + touch-action: manipulation; +} + +img, svg { + max-width: 100%; +} + +h1 { + margin: 0 0 33px 0; + font-size: 20px; + color: #666; + font-family: 'Gotham SSm',Helvetica,Arial,sans-serif; + font-weight: normal; + line-height: 1.3; + font-weight: 400; + -webkit-font-smoothing: antialiased; +} + +h2 { + font-weight: normal; + color: #777; + font-size: 12px; + margin: -22px 0 22px 0; + line-height: 1.6; +} + +h3 { + margin: 0 0 15px 0; + font-family: 'Knockout 31 4r'; + font-weight: normal; + text-transform: uppercase; + padding-top: 0; + line-height: 1.2; + letter-spacing: 0.05em; + -webkit-font-smoothing: antialiased; + font-size: 12px; + color: #333; +} + +.nowrap { + white-space: nowrap; +} + +.footnotes { + margin-bottom: 20px; + + h4 { + margin: 2px 0 7px 0; + color: #666; + font-size: 11px; + } +} + +.footnotes p, +.footer p { + margin: 2px 0 0 0; + font-size: 11px; + line-height: 1.7; + color: #999; +} + +.footer p { font-style: italic; } +.footer p em { font-style: normal; } +.footnotes p strong { color: #666; } + +a, a:link, a:visited { + color: #4774CC; + text-decoration: none; +} + +a:hover, a:active { color: #bccae5; } + +// Standard graphic styles +.graphic-wrapper { + position: relative; +} + +.graphic { + position: relative; + margin-bottom: 11px; + .clearfix(); + + img { + max-width: 100%; + height: auto; + } +} + +.key { + margin: -11px 0 33px 0; + padding: 0; + list-style-type: none; + + .key-item { + display: inline-block; + margin: 0 18px 0 0; + padding: 0; + line-height: 15px; + + b { + display: inline-block; + width: 15px; + height: 15px; + margin-right: 6px; + float: left; + } + + label { + white-space: nowrap; + font-size: 12px; + color: #666; + font-weight: normal; + -webkit-font-smoothing: antialiased; + } + } +} + +svg { overflow: hidden; } + +.axis { + font-size: 11px; + -webkit-font-smoothing: antialiased; + fill: #999; + + path, + line { + fill: none; + stroke: #ccc; + shape-rendering: crispEdges; + } + + &.y { + path { display: none; } + .tick line { display: none; } + } +} + +.grid { + path { display: none; } + + .tick { + stroke: #eee; + color: #eee; + stroke-width: 1px; + shape-rendering: crispEdges; + } + + &.y { + g:first-child line { display: none; } + } +} + +.zero-line { + stroke: #666; + stroke-width: 1px; + shape-rendering: crispEdges; +} + +line, +rect { + shape-rendering: crispEdges; +} + +.bars rect { fill: @teal3; } + +.labels { + position: absolute; + margin: 0; + padding: 0; + list-style-type: none; + border: none; + + li { + position: absolute; + text-align: right; + font-size: 12px; + line-height: 1.3; + color: #666; + display: table; + -webkit-font-smoothing: antialiased; + + span { + display: table-cell; + vertical-align: middle; + } + } +} + +.value text { + font-size: 10px; + -webkit-font-smoothing: antialiased; + + &.in { fill: #fff; } + &.out { fill: #999; } +} + +// HOMEPAGE +// * styles for when a project is embedded on the homepage +// * (if someone clicked the "This code will be embedded on the NPR homepage." +// * checkbox when pulling the embed code.) +body.hp { + padding-top: 0; + padding-bottom: 10px; +} + +// CHILDLINK +// * Direct links to the child page (iOS app workaround link) +body.childlink { + margin-left: auto; + margin-right: auto; + max-width: 800px; +} + +// Accessibility styles + +img:not([alt]) { + outline: 3px solid red; + + [role="img"] & { + outline: none; + } +} + +.sr-only { + opacity: 0; + position: absolute; + left: -1000px; + clip: inset(0, 0, 0, 0); + width: 1px; + height: 1px; +} diff --git a/stacked_area_chart/lib/breakpoints.js b/stacked_area_chart/lib/breakpoints.js new file mode 100644 index 0000000..3c71275 --- /dev/null +++ b/stacked_area_chart/lib/breakpoints.js @@ -0,0 +1,4 @@ +module.exports = { + isMobile: window.matchMedia("(max-width: 500px)"), + isDesktop: window.matchMedia("(min-width: 501px)") +}; \ No newline at end of file diff --git a/stacked_area_chart/lib/debounce.js b/stacked_area_chart/lib/debounce.js new file mode 100644 index 0000000..2a3c4e9 --- /dev/null +++ b/stacked_area_chart/lib/debounce.js @@ -0,0 +1,11 @@ +module.exports = function(fn, duration = 100) { + var timeout; + + return function(...args) { + if (timeout) return; + timeout = setTimeout(function() { + timeout = null; + fn.apply(null, args); + }, duration); + } +}; \ No newline at end of file diff --git a/stacked_area_chart/lib/delegate.js b/stacked_area_chart/lib/delegate.js new file mode 100644 index 0000000..084f6e7 --- /dev/null +++ b/stacked_area_chart/lib/delegate.js @@ -0,0 +1,10 @@ +var delegate = function(root, event, selector, callback) { + root.addEventListener(event, function(e) { + var matching = e.target.closest(selector); + if (matching && root.contains(matching)) { + callback.call(matching, e); + } + }); +}; + +module.exports = delegate; \ No newline at end of file diff --git a/stacked_area_chart/lib/dot.js b/stacked_area_chart/lib/dot.js new file mode 100644 index 0000000..0017022 --- /dev/null +++ b/stacked_area_chart/lib/dot.js @@ -0,0 +1,11 @@ +// NOTE: install dot from NPM before using this module +// duplicates EJS templating for the client, so we can share with the build process + +var dot = require("dot"); + +dot.templateSettings.varname = "data"; +dot.templateSettings.selfcontained = true; +dot.templateSettings.evaluate = /<%([\s\S]+?)%>/g; +dot.templateSettings.interpolate = /<%=([\s\S]+?)%>/g; + +module.exports = dot; \ No newline at end of file diff --git a/stacked_area_chart/lib/helpers/classify.js b/stacked_area_chart/lib/helpers/classify.js new file mode 100644 index 0000000..9eefc12 --- /dev/null +++ b/stacked_area_chart/lib/helpers/classify.js @@ -0,0 +1,8 @@ +module.exports = function(str) { + return (str + "").toLowerCase() + .replace(/\s+/g, '-') // Replace spaces with - + .replace(/[^\w\-]+/g, '') // Remove all non-word chars + .replace(/\-\-+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, ''); // Trim - from end of text +}; \ No newline at end of file diff --git a/stacked_area_chart/lib/helpers/colors.js b/stacked_area_chart/lib/helpers/colors.js new file mode 100644 index 0000000..dbae7be --- /dev/null +++ b/stacked_area_chart/lib/helpers/colors.js @@ -0,0 +1,7 @@ +module.exports = { + "red1": "#6C2315", "red2": "#A23520", "red3": "#D8472B", "red4": "#E27560", "red5": "#ECA395", "red6": "#F5D1CA", + "orange1": "#714616", "orange2": "#AA6A21", "orange3": "#E38D2C", "orange4": "#EAAA61", "orange5": "#F1C696", "orange6": "#F8E2CA", + "yellow1": "#77631B", "yellow2": "#B39429", "yellow3": "#EFC637", "yellow4": "#F3D469", "yellow5": "#F7E39B", "yellow6": "#FBF1CD", + "teal1": "#0B403F", "teal2": "#11605E", "teal3": "#17807E", "teal4": "#51A09E", "teal5": "#8BC0BF", "teal6": "#C5DFDF", + "blue1": "#28556F", "blue2": "#3D7FA6", "blue3": "#51AADE", "blue4": "#7DBFE6", "blue5": "#A8D5EF", "blue6": "#D3EAF7" +}; \ No newline at end of file diff --git a/stacked_area_chart/lib/helpers/fmtComma.js b/stacked_area_chart/lib/helpers/fmtComma.js new file mode 100644 index 0000000..9f0c86d --- /dev/null +++ b/stacked_area_chart/lib/helpers/fmtComma.js @@ -0,0 +1 @@ +module.exports = s => s.toLocaleString("en-US").replace(/\.0+$/, ""); \ No newline at end of file diff --git a/stacked_area_chart/lib/helpers/formatDate.js b/stacked_area_chart/lib/helpers/formatDate.js new file mode 100644 index 0000000..53e0480 --- /dev/null +++ b/stacked_area_chart/lib/helpers/formatDate.js @@ -0,0 +1,18 @@ +// Given November 7, 1981... + +var getAPMonth = require("./getAPMonth"); + +var formatters = { + // 81 + yearAbbrev: d => (d.getFullYear() + "").slice(-2), + // 1981 + yearFull: d => d.getFullYear(), + // 7, 1981 + dayYear: d => d.getDate() + ", " + d.getFullYear(), + // Nov. 7 + monthDay: d => getAPMonth(d) + " " + d.getDate(), + // Nov. 7, 1981 + dateFull: d => getAPMonth(d) + " " + formatters.dayYear(d) +}; + +module.exports = formatters; diff --git a/stacked_area_chart/lib/helpers/formatStyle.js b/stacked_area_chart/lib/helpers/formatStyle.js new file mode 100644 index 0000000..9366c98 --- /dev/null +++ b/stacked_area_chart/lib/helpers/formatStyle.js @@ -0,0 +1,9 @@ +module.exports = function(props) { + var s = ""; + + for (var key in props) { + s += `${key}: ${props[key].toString()}; `; + } + + return s; +}; \ No newline at end of file diff --git a/stacked_area_chart/lib/helpers/getAPMonth.js b/stacked_area_chart/lib/helpers/getAPMonth.js new file mode 100644 index 0000000..9d0401a --- /dev/null +++ b/stacked_area_chart/lib/helpers/getAPMonth.js @@ -0,0 +1,5 @@ +module.exports = function(date) { + var apMonths = [ "Jan.", "Feb.", "March", "April", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec." ]; + var thisMonth = date.getMonth(); + return apMonths[thisMonth]; +}; diff --git a/stacked_area_chart/lib/helpers/getLocation.js b/stacked_area_chart/lib/helpers/getLocation.js new file mode 100644 index 0000000..fcbccd8 --- /dev/null +++ b/stacked_area_chart/lib/helpers/getLocation.js @@ -0,0 +1,5 @@ +module.exports = function(href) { + var l = document.createElement("a"); + l.href = href; + return l; +}; \ No newline at end of file diff --git a/stacked_area_chart/lib/helpers/getParameterByName.js b/stacked_area_chart/lib/helpers/getParameterByName.js new file mode 100644 index 0000000..188dd82 --- /dev/null +++ b/stacked_area_chart/lib/helpers/getParameterByName.js @@ -0,0 +1,3 @@ +module.exports = function(name) { + return new URLSearchParams(window.location.search).get(name); +}; \ No newline at end of file diff --git a/stacked_area_chart/lib/helpers/index.js b/stacked_area_chart/lib/helpers/index.js new file mode 100644 index 0000000..5dfb6cf --- /dev/null +++ b/stacked_area_chart/lib/helpers/index.js @@ -0,0 +1,18 @@ +/* + * Basic Javascript helpers used in analytics.js and graphics code. + */ + +module.exports = { + classify: require("./classify"), + COLORS: require("./colors"), + fmtComma: require("./fmtComma"), + formatDate: require("./formatDate"), + formatStyle: require("./formatStyle"), + getAPMonth: require("./getAPMonth"), + getLocation: require("./getLocation"), + getParameterByName: require("./getParameterByName"), + isProduction: require("./isProduction"), + makeTranslate: require("./makeTranslate"), + urlToLocation: require("./urlToLocation"), + wrapText: require("./wrapText") +} diff --git a/stacked_area_chart/lib/helpers/isProduction.js b/stacked_area_chart/lib/helpers/isProduction.js new file mode 100644 index 0000000..3c243e1 --- /dev/null +++ b/stacked_area_chart/lib/helpers/isProduction.js @@ -0,0 +1,16 @@ +/* + * Checks if we are in production based on the url hostname + * When embedded with pym it checks the parentUrl param + * - If a url is given checks that + * - If no url is given checks window.location.href + */ + +module.exports = function(u = window.location.href) { + var url = new URL(u); + var parentURL = url.searchParams.get("parentUrl"); + if (parentURL) { + var parent = new URL(parentURL); + return !parent.hostname.match(/^localhost|^stage-|^www-s1/i); + } + return true; +}; \ No newline at end of file diff --git a/stacked_area_chart/lib/helpers/makeTranslate.js b/stacked_area_chart/lib/helpers/makeTranslate.js new file mode 100644 index 0000000..21c5f53 --- /dev/null +++ b/stacked_area_chart/lib/helpers/makeTranslate.js @@ -0,0 +1 @@ +module.exports = (x, y) => `translate(${x}, ${y})`; \ No newline at end of file diff --git a/stacked_area_chart/lib/helpers/urlToLocation.js b/stacked_area_chart/lib/helpers/urlToLocation.js new file mode 100644 index 0000000..dfd9ad6 --- /dev/null +++ b/stacked_area_chart/lib/helpers/urlToLocation.js @@ -0,0 +1,5 @@ +module.exports = function(url) { + var a = document.createElement('a'); + a.href = url; + return a; +}; \ No newline at end of file diff --git a/stacked_area_chart/lib/helpers/wrapText.js b/stacked_area_chart/lib/helpers/wrapText.js new file mode 100644 index 0000000..37d6bd5 --- /dev/null +++ b/stacked_area_chart/lib/helpers/wrapText.js @@ -0,0 +1,61 @@ +/* +* Wrap a block of SVG text to a given width +* adapted from http://bl.ocks.org/mbostock/7555321 +*/ +module.exports = function(texts, width, lineHeight) { + + var eachText = function(text) { + // work with arrays as well + var words = text.textContent.split(/\s+/).reverse(); + + var word = null; + var line = []; + var lineNumber = 0; + + var x = text.getAttribute("x") || 0; + var y = text.getAttribute("y") || 0; + + var dx = parseFloat(text.getAttribute("dx")) || 0; + var dy = parseFloat(text.getAttribute("dy")) || 0; + + text.textContent = ""; + + var NS = "http://www.w3.org/2000/svg"; + var tspan = document.createElementNS(NS, "tspan"); + text.appendChild(tspan); + + var attrs = { x, y, dx: dx + "px", dy: dy + "px" }; + for (var k in attrs) { + tspan.setAttribute(k, attrs[k]); + } + + while (word = words.pop()) { + line.push(word); + tspan.textContent = line.join(" "); + + if (tspan.getComputedTextLength() > width) { + line.pop(); + tspan.textContent = line.join(" "); + line = [word]; + + lineNumber += 1; + + tspan = document.createElementNS(NS, "tspan"); + text.appendChild(tspan); + + var attrs = { x, y, dx: dx + "px", dy: (lineNumber * lineHeight) + dy + "px" }; + for (var k in attrs) { + tspan.setAttribute(k, attrs[k]); + } + tspan.textContent = word; + } + } + }; + + // convert D3 to array + if ("each" in texts) { + // call D3-style + texts = texts.nodes(); + } + texts.forEach(eachText); +}; diff --git a/stacked_area_chart/lib/pym.js b/stacked_area_chart/lib/pym.js new file mode 100644 index 0000000..5ffd762 --- /dev/null +++ b/stacked_area_chart/lib/pym.js @@ -0,0 +1,37 @@ +var { getParameterByName } = require("./helpers"); +var analytics = require("./analytics"); + +module.exports = new Promise(ok => { + var url = "https://pym.nprapps.org/pym.v1.min.js"; + var script = document.createElement("script"); + script.src = url; + document.head.appendChild(script); + + script.onload = function() { + + var child = new pym.Child(); + + // child.onMessage("on-screen", function(bucket) { + // analytics.trackEvent("on-screen", bucket); + // }); + // child.onMessage("scroll-depth", function(data) { + // data = JSON.parse(data); + // analytics.trackEvent("scroll-depth", data.percent, data.seconds); + // }); + + ok(child); + } +}); + +switch (getParameterByName("mode")) { + // Homepage (if someone clicked the "This code will be embedded + // on the NPR homepage." checkbox when pulling the embed code.) + case "hp": + document.body.classList.add("hp"); + isHomepage = true; + break; + // Direct links to the child page (iOS app workaround link) + case "childlink": + document.body.classList.add("childlink"); + break; +}; \ No newline at end of file diff --git a/stacked_area_chart/lib/qsa.js b/stacked_area_chart/lib/qsa.js new file mode 100644 index 0000000..c2e930f --- /dev/null +++ b/stacked_area_chart/lib/qsa.js @@ -0,0 +1,5 @@ +var $ = (s, d = document) => Array.from(d.querySelectorAll(s)); + +$.one = (s, d = document) => d.querySelector(s); + +module.exports = $; \ No newline at end of file diff --git a/stacked_area_chart/lib/webfonts.js b/stacked_area_chart/lib/webfonts.js new file mode 100644 index 0000000..e3468b1 --- /dev/null +++ b/stacked_area_chart/lib/webfonts.js @@ -0,0 +1,19 @@ +var url = "https://apps.npr.org/dailygraphics/graphics/fonts/js/lib/webfont.js"; +var script = document.createElement("script"); +script.src = url; +document.head.appendChild(script); +script.onload = function() { + WebFont.load({ + custom: { + families: [ + 'Gotham SSm:n4,n7', + 'Knockout 31 4r:n4' + ], + urls: [ + 'https://s.npr.org/templates/css/fonts/GothamSSm.css', + 'https://s.npr.org/templates/css/fonts/Knockout.css' + ] + }, + timeout: 10000 + }); +}; \ No newline at end of file diff --git a/stacked_area_chart/lib/xhr.js b/stacked_area_chart/lib/xhr.js new file mode 100644 index 0000000..c86dac8 --- /dev/null +++ b/stacked_area_chart/lib/xhr.js @@ -0,0 +1,17 @@ +module.exports = function(url, callback) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url); + xhr.onload = xhr.onerror = function(e) { + if (e.type !== "load" || xhr.status >= 400) { + return callback(xhr); + } + var data = xhr.responseText; + if (url.match(/json/)) { + try { + data = JSON.parse(data); + } catch (err) { /* oh well */ } + } + callback(null, data); + } + xhr.send(); +} \ 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/renderLineChart.js b/stacked_area_chart/renderLineChart.js new file mode 100644 index 0000000..d17d611 --- /dev/null +++ b/stacked_area_chart/renderLineChart.js @@ -0,0 +1,265 @@ +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 line 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.reduce( + // (acc, d) => acc.concat(d.values.map(v => v[valueColumn])), + // [] + // ); + + // 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); + + var min = 0; + var max = 20; + + + 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 lines to chart. + var line = d3 + .line() + .x(d => xScale(d[dateColumn])) + .y(d => yScale(d[valueColumn])); + + 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", "lines") + // .selectAll("path") + // .data(config.data) + // .enter() + // .append("path") + // .attr("class", d => "line " + classify(d.name)) + // .attr("stroke", d => colorScale(d.name)) + // .attr("d", d => line(d.values)); + + chartElement + .append("g") + .attr("class","areas") + .selectAll("path") + .data(config.data) + .join("path") + .attr("fill", d => colorScale(d.key)) + .attr("d", areaGen) + + // var lastItem = d => d.values[d.values.length - 1]; + + // chartElement + // .append("g") + // .attr("class", "value") + // .selectAll("text") + // .data(config.data) + // .enter() + // .append("text") + // .attr("x", d => xScale(lastItem(d)[dateColumn]) + 5) + // .attr("y", d => yScale(lastItem(d)[valueColumn]) + 3) + // .text(function(d) { + // var item = lastItem(d); + // var value = item[valueColumn]; + // var label = value.toFixed(1); + + // if (!isMobile.matches) { + // label = d.name + ": " + label; + // } + + // return label; + // }); +}; From 8680002eed481ffc38d9b90e495989b98903c31a Mon Sep 17 00:00:00 2001 From: Daniel Wood Date: Thu, 4 Feb 2021 15:22:18 -0500 Subject: [PATCH 2/6] remove crud --- stacked_area_chart/graphic.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/stacked_area_chart/graphic.js b/stacked_area_chart/graphic.js index 508a9e7..da970c9 100644 --- a/stacked_area_chart/graphic.js +++ b/stacked_area_chart/graphic.js @@ -23,7 +23,6 @@ var onWindowLoaded = function() { //Format graphic data for processing by D3. var formatData = function(data) { - var series = []; data.forEach(function(d) { if (d.date instanceof Date) return; @@ -33,25 +32,9 @@ var formatData = function(data) { }); // Restructure tabular data for easier charting. - for (var column in data[0]) { - if (column == "date") continue; - - series.push({ - name: column, - values: data.map(d => ({ - date: d.date, - amt: d[column] - })) - }); - } - var dataKeys = Object.keys(data[0]).slice(1); var stackedData = d3.stack().keys(dataKeys)(data); - // for (var i = 0; i < stackedData.length; i++) { - // stackedData[i] - // } - return stackedData; }; From ce6bb6c807929e75babd1c626815bbdca9130d71 Mon Sep 17 00:00:00 2001 From: Daniel Wood Date: Tue, 9 Mar 2021 14:17:16 -0500 Subject: [PATCH 3/6] adjust formatdata --- line_chart/graphic.js | 34 +++++++++----- line_chart/renderLineChart.js | 88 ++++++++++++++++++++--------------- 2 files changed, 72 insertions(+), 50 deletions(-) diff --git a/line_chart/graphic.js b/line_chart/graphic.js index e322b32..1b06335 100644 --- a/line_chart/graphic.js +++ b/line_chart/graphic.js @@ -1,3 +1,7 @@ +var d3 = { + ...require("d3-shape/dist/d3-shape.min") +}; + var pym = require("./lib/pym"); require("./lib/webfonts"); @@ -19,29 +23,35 @@ var onWindowLoaded = function() { //Format graphic data for processing by D3. var formatData = function(data) { - var series = []; - 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. - for (var column in data[0]) { - if (column == "date") continue; + var dataKeys = Object.keys(data[0]); + var removeItems = ["date","total_amount"]; + for (var i = 0; i < removeItems.length; i++) { - series.push({ - name: column, - values: data.map(d => ({ - date: d.date, - amt: d[column] - })) - }); + let index = dataKeys.indexOf(removeItems[i]); + if (index > -1) { + dataKeys.splice(index,1) + } } - return series; + var stackedData = d3.stack().keys(dataKeys)(data); + + return stackedData; }; // Render the graphic(s). Called by pym with the container width. diff --git a/line_chart/renderLineChart.js b/line_chart/renderLineChart.js index 1943f6e..ef75ba1 100644 --- a/line_chart/renderLineChart.js +++ b/line_chart/renderLineChart.js @@ -12,6 +12,7 @@ var { isMobile } = require("./lib/breakpoints"); // Render a line chart. module.exports = function(config) { + // Setup var { dateColumn, valueColumn } = config; @@ -47,18 +48,18 @@ module.exports = function(config) { var containerElement = d3.select(config.container); containerElement.html(""); - var dates = config.data[0].values.map(d => d.date); + 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.reduce( - (acc, d) => acc.concat(d.values.map(v => v[valueColumn])), - [] - ); + + var values = config.data[0].map(d => d.data.total_amount); var floors = values.map( v => Math.floor(v / roundTicksFactor) * roundTicksFactor @@ -74,6 +75,10 @@ module.exports = function(config) { ); var max = Math.max.apply(null, ceilings); + if (min > 0) { + min = 0; + } + var yScale = d3 .scaleLinear() .domain([min, max]) @@ -105,11 +110,11 @@ module.exports = function(config) { .data(config.data) .enter() .append("li") - .attr("class", d => "key-item " + classify(d.name)); + .attr("class", d => "key-item " + classify(d.key)); - legend.append("b").style("background-color", d => colorScale(d.name)); + legend.append("b").style("background-color", d => colorScale(d.key)); - legend.append("label").text(d => d.name); + legend.append("label").text(d => d.key); // Create the root SVG element. @@ -203,37 +208,44 @@ module.exports = function(config) { .x(d => xScale(d[dateColumn])) .y(d => yScale(d[valueColumn])); - chartElement - .append("g") - .attr("class", "lines") - .selectAll("path") - .data(config.data) - .enter() - .append("path") - .attr("class", d => "line " + classify(d.name)) - .attr("stroke", d => colorScale(d.name)) - .attr("d", d => line(d.values)); - - var lastItem = d => d.values[d.values.length - 1]; + 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", "value") - .selectAll("text") + .attr("class","areas") + .selectAll("path") .data(config.data) - .enter() - .append("text") - .attr("x", d => xScale(lastItem(d)[dateColumn]) + 5) - .attr("y", d => yScale(lastItem(d)[valueColumn]) + 3) - .text(function(d) { - var item = lastItem(d); - var value = item[valueColumn]; - var label = value.toFixed(1); - - if (!isMobile.matches) { - label = d.name + ": " + label; - } - - return label; - }); + .join("path") + .attr("fill", d => colorScale(d.key)) + .attr("d", areaGen) + + // var lastItem = d => d.values[d.values.length - 1]; + + // chartElement + // .append("g") + // .attr("class", "value") + // .selectAll("text") + // .data(config.data) + // .enter() + // .append("text") + // .attr("x", d => xScale(lastItem(d)[dateColumn]) + 5) + // .attr("y", d => yScale(lastItem(d)[valueColumn]) + 3) + // .text(function(d) { + // var item = lastItem(d); + // var value = item[valueColumn]; + // var label = value.toFixed(1); + + // if (!isMobile.matches) { + // label = d.name + ": " + label; + // } + + // return label; + // }); }; From fdbd42c86849cedd5d10208f7094c7e2154b8102 Mon Sep 17 00:00:00 2001 From: Daniel Wood Date: Tue, 9 Mar 2021 14:32:40 -0500 Subject: [PATCH 4/6] fix line_chart, add stacked area --- line_chart/graphic.js | 34 ++++------- line_chart/renderLineChart.js | 88 ++++++++++++--------------- stacked_area_chart/graphic.js | 20 +++++- stacked_area_chart/renderLineChart.js | 46 +++++--------- 4 files changed, 84 insertions(+), 104 deletions(-) diff --git a/line_chart/graphic.js b/line_chart/graphic.js index 1b06335..e322b32 100644 --- a/line_chart/graphic.js +++ b/line_chart/graphic.js @@ -1,7 +1,3 @@ -var d3 = { - ...require("d3-shape/dist/d3-shape.min") -}; - var pym = require("./lib/pym"); require("./lib/webfonts"); @@ -23,35 +19,29 @@ var onWindowLoaded = function() { //Format graphic data for processing by D3. var formatData = function(data) { + var series = []; + 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++) { + for (var column in data[0]) { + if (column == "date") continue; - let index = dataKeys.indexOf(removeItems[i]); - if (index > -1) { - dataKeys.splice(index,1) - } + series.push({ + name: column, + values: data.map(d => ({ + date: d.date, + amt: d[column] + })) + }); } - var stackedData = d3.stack().keys(dataKeys)(data); - - return stackedData; + return series; }; // Render the graphic(s). Called by pym with the container width. diff --git a/line_chart/renderLineChart.js b/line_chart/renderLineChart.js index ef75ba1..1943f6e 100644 --- a/line_chart/renderLineChart.js +++ b/line_chart/renderLineChart.js @@ -12,7 +12,6 @@ var { isMobile } = require("./lib/breakpoints"); // Render a line chart. module.exports = function(config) { - // Setup var { dateColumn, valueColumn } = config; @@ -48,18 +47,18 @@ module.exports = function(config) { var containerElement = d3.select(config.container); containerElement.html(""); - var dates = config.data[0].map(d => { - return d.data.date - }) - + var dates = config.data[0].values.map(d => d.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 values = config.data.reduce( + (acc, d) => acc.concat(d.values.map(v => v[valueColumn])), + [] + ); var floors = values.map( v => Math.floor(v / roundTicksFactor) * roundTicksFactor @@ -75,10 +74,6 @@ module.exports = function(config) { ); var max = Math.max.apply(null, ceilings); - if (min > 0) { - min = 0; - } - var yScale = d3 .scaleLinear() .domain([min, max]) @@ -110,11 +105,11 @@ module.exports = function(config) { .data(config.data) .enter() .append("li") - .attr("class", d => "key-item " + classify(d.key)); + .attr("class", d => "key-item " + classify(d.name)); - legend.append("b").style("background-color", d => colorScale(d.key)); + legend.append("b").style("background-color", d => colorScale(d.name)); - legend.append("label").text(d => d.key); + legend.append("label").text(d => d.name); // Create the root SVG element. @@ -208,44 +203,37 @@ module.exports = function(config) { .x(d => xScale(d[dateColumn])) .y(d => yScale(d[valueColumn])); - 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") + .attr("class", "lines") .selectAll("path") .data(config.data) - .join("path") - .attr("fill", d => colorScale(d.key)) - .attr("d", areaGen) - - // var lastItem = d => d.values[d.values.length - 1]; - - // chartElement - // .append("g") - // .attr("class", "value") - // .selectAll("text") - // .data(config.data) - // .enter() - // .append("text") - // .attr("x", d => xScale(lastItem(d)[dateColumn]) + 5) - // .attr("y", d => yScale(lastItem(d)[valueColumn]) + 3) - // .text(function(d) { - // var item = lastItem(d); - // var value = item[valueColumn]; - // var label = value.toFixed(1); - - // if (!isMobile.matches) { - // label = d.name + ": " + label; - // } - - // return label; - // }); + .enter() + .append("path") + .attr("class", d => "line " + classify(d.name)) + .attr("stroke", d => colorScale(d.name)) + .attr("d", d => line(d.values)); + + var lastItem = d => d.values[d.values.length - 1]; + + chartElement + .append("g") + .attr("class", "value") + .selectAll("text") + .data(config.data) + .enter() + .append("text") + .attr("x", d => xScale(lastItem(d)[dateColumn]) + 5) + .attr("y", d => yScale(lastItem(d)[valueColumn]) + 3) + .text(function(d) { + var item = lastItem(d); + var value = item[valueColumn]; + var label = value.toFixed(1); + + if (!isMobile.matches) { + label = d.name + ": " + label; + } + + return label; + }); }; diff --git a/stacked_area_chart/graphic.js b/stacked_area_chart/graphic.js index da970c9..1b06335 100644 --- a/stacked_area_chart/graphic.js +++ b/stacked_area_chart/graphic.js @@ -23,16 +23,32 @@ var onWindowLoaded = function() { //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]).slice(1); + 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; diff --git a/stacked_area_chart/renderLineChart.js b/stacked_area_chart/renderLineChart.js index d17d611..ef75ba1 100644 --- a/stacked_area_chart/renderLineChart.js +++ b/stacked_area_chart/renderLineChart.js @@ -58,29 +58,26 @@ module.exports = function(config) { .scaleTime() .domain(extent) .range([0, chartWidth]); + + var values = config.data[0].map(d => d.data.total_amount); - // var values = config.data.reduce( - // (acc, d) => acc.concat(d.values.map(v => v[valueColumn])), - // [] - // ); + var floors = values.map( + v => Math.floor(v / roundTicksFactor) * roundTicksFactor + ); + var min = Math.min.apply(null, floors); - // 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 min = 0; - var max = 20; + 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() @@ -220,17 +217,6 @@ module.exports = function(config) { }) .y1(d => yScale(d[1])); - // chartElement - // .append("g") - // .attr("class", "lines") - // .selectAll("path") - // .data(config.data) - // .enter() - // .append("path") - // .attr("class", d => "line " + classify(d.name)) - // .attr("stroke", d => colorScale(d.name)) - // .attr("d", d => line(d.values)); - chartElement .append("g") .attr("class","areas") From 6dbd0a29745d101998bfa259a0dae5dbf39652d6 Mon Sep 17 00:00:00 2001 From: Daniel Wood Date: Wed, 10 Mar 2021 17:03:46 -0500 Subject: [PATCH 5/6] remove duplicate files --- stacked_area_chart/graphic.js | 4 +- stacked_area_chart/index.html | 2 +- stacked_area_chart/lib/_foot.html | 2 - stacked_area_chart/lib/_head.html | 13 - stacked_area_chart/lib/analytics.js | 88 ----- stacked_area_chart/lib/base.less | 339 ------------------ stacked_area_chart/lib/breakpoints.js | 4 - stacked_area_chart/lib/debounce.js | 11 - stacked_area_chart/lib/delegate.js | 10 - stacked_area_chart/lib/dot.js | 11 - stacked_area_chart/lib/helpers/classify.js | 8 - stacked_area_chart/lib/helpers/colors.js | 7 - stacked_area_chart/lib/helpers/fmtComma.js | 1 - stacked_area_chart/lib/helpers/formatDate.js | 18 - stacked_area_chart/lib/helpers/formatStyle.js | 9 - stacked_area_chart/lib/helpers/getAPMonth.js | 5 - stacked_area_chart/lib/helpers/getLocation.js | 5 - .../lib/helpers/getParameterByName.js | 3 - stacked_area_chart/lib/helpers/index.js | 18 - .../lib/helpers/isProduction.js | 16 - .../lib/helpers/makeTranslate.js | 1 - .../lib/helpers/urlToLocation.js | 5 - stacked_area_chart/lib/helpers/wrapText.js | 61 ---- stacked_area_chart/lib/pym.js | 37 -- stacked_area_chart/lib/qsa.js | 5 - stacked_area_chart/lib/webfonts.js | 19 - stacked_area_chart/lib/xhr.js | 17 - ...{renderLineChart.js => renderAreaChart.js} | 8 +- 28 files changed, 5 insertions(+), 722 deletions(-) delete mode 100644 stacked_area_chart/lib/_foot.html delete mode 100644 stacked_area_chart/lib/_head.html delete mode 100644 stacked_area_chart/lib/analytics.js delete mode 100644 stacked_area_chart/lib/base.less delete mode 100644 stacked_area_chart/lib/breakpoints.js delete mode 100644 stacked_area_chart/lib/debounce.js delete mode 100644 stacked_area_chart/lib/delegate.js delete mode 100644 stacked_area_chart/lib/dot.js delete mode 100644 stacked_area_chart/lib/helpers/classify.js delete mode 100644 stacked_area_chart/lib/helpers/colors.js delete mode 100644 stacked_area_chart/lib/helpers/fmtComma.js delete mode 100644 stacked_area_chart/lib/helpers/formatDate.js delete mode 100644 stacked_area_chart/lib/helpers/formatStyle.js delete mode 100644 stacked_area_chart/lib/helpers/getAPMonth.js delete mode 100644 stacked_area_chart/lib/helpers/getLocation.js delete mode 100644 stacked_area_chart/lib/helpers/getParameterByName.js delete mode 100644 stacked_area_chart/lib/helpers/index.js delete mode 100644 stacked_area_chart/lib/helpers/isProduction.js delete mode 100644 stacked_area_chart/lib/helpers/makeTranslate.js delete mode 100644 stacked_area_chart/lib/helpers/urlToLocation.js delete mode 100644 stacked_area_chart/lib/helpers/wrapText.js delete mode 100644 stacked_area_chart/lib/pym.js delete mode 100644 stacked_area_chart/lib/qsa.js delete mode 100644 stacked_area_chart/lib/webfonts.js delete mode 100644 stacked_area_chart/lib/xhr.js rename stacked_area_chart/{renderLineChart.js => renderAreaChart.js} (97%) diff --git a/stacked_area_chart/graphic.js b/stacked_area_chart/graphic.js index 1b06335..d670f45 100644 --- a/stacked_area_chart/graphic.js +++ b/stacked_area_chart/graphic.js @@ -6,7 +6,7 @@ var pym = require("./lib/pym"); require("./lib/webfonts"); var pymChild; -var renderLineChart = require("./renderLineChart"); +var renderAreaChart = require("./renderAreaChart"); //Initialize graphic var onWindowLoaded = function() { @@ -60,7 +60,7 @@ var render = function(data) { var container = "#line-chart"; var element = document.querySelector(container); var width = element.offsetWidth; - renderLineChart({ + renderAreaChart({ container, width, data, diff --git a/stacked_area_chart/index.html b/stacked_area_chart/index.html index 5167fef..ee498f4 100644 --- a/stacked_area_chart/index.html +++ b/stacked_area_chart/index.html @@ -8,7 +8,7 @@

<%= t.smarty(COPY.labels.headline) %>

<%= t.smarty(COPY.labels.subhed) %>

<% } %> -