Skip to content

Commit

Permalink
feat(visualization): Create higher-order-component and gatsby plugin …
Browse files Browse the repository at this point in the history
…to render components to png
  • Loading branch information
PhastPhood committed Jul 7, 2020
1 parent 53a3cab commit 6440e2d
Show file tree
Hide file tree
Showing 15 changed files with 2,770 additions and 4,380 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ module.exports = {
],
'import/resolver': {
alias: [
['~plugins', './plugins'],
['~components', './src/components'],
['~context', './src/context'],
['~data', './src/data'],
Expand Down
7 changes: 7 additions & 0 deletions gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ const gatsbyConfig = {
demographics: `${__dirname}/src/data/race/counties/demographics.json`,
},
},
{
resolve: 'gatsby-render-components',
options: {
path: `${__dirname}/public/img`
},
},
{
resolve: 'gatsby-source-contentful',
options: {
Expand Down Expand Up @@ -238,6 +244,7 @@ const gatsbyConfig = {
resolve: `gatsby-plugin-alias-imports`,
options: {
alias: {
'~plugins': 'plugins',
'~components': 'src/components',
'~context': 'src/context',
'~data': 'src/data',
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
moduleNameMapper: {
'.+\\.(css|styl|less|sass|scss)$': `identity-obj-proxy`,
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': `<rootDir>/__mocks__/file-mock.js`,
'~plugins/(.*)$': '<rootDir>/plugins/$1',
'~components/(.*)$': '<rootDir>/src/components/$1',
'~context/(.*)$': '<rootDir>/src/context/$1',
'~data/(.*)$': '<rootDir>/src/data/$1',
Expand Down
6,918 changes: 2,545 additions & 4,373 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"emotion-theming": "^10.0.27",
"eslint-config-airbnb": "^18.1.0",
"eslint-loader": "^3.0.3",
"file-system-cache": "^1.0.5",
"gatsby": "^2.22.15",
"gatsby-image": "^2.2.44",
"gatsby-plugin-algolia": "^0.7.0",
Expand Down Expand Up @@ -73,9 +74,11 @@
"lodash": "^4.17.15",
"luxon": "^1.22.0",
"marked": "^0.8.2",
"mkdirp": "^1.0.4",
"node-sass": "^4.14.1",
"nprogress": "^0.2.0",
"prop-types": "^15.7.2",
"puppeteer": "^2.1.1",
"query-string": "^6.12.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
Expand Down
69 changes: 69 additions & 0 deletions plugins/gatsby-render-components/gatsby-node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const puppeteer = require('puppeteer')
const mkdirp = require('mkdirp')
const path = require('path')
const Cache = require('file-system-cache').default

const cache = Cache({
basePath: './.cache/component-rendering',
ns: 'component-rendering',
})

exports.onPostBootstrap = () => cache.clear()

exports.onPostBuild = async (_, pluginOptions) => {
const browser = await puppeteer.launch()
const page = await browser.newPage()

const styles = await cache.get('_styles')
const stylesHTML = styles.styles
.map(styleHTML => `<style>${styleHTML}</style>`)
.reduce((acc, cur) => acc + cur, '')

const allCacheFiles = await cache.load()

// disable because we can't render more than one at a time without spinning up
// a ton of chromium pages
/* eslint-disable no-await-in-loop */
for (let i = 0; i < allCacheFiles.files.length; i += 1) {
const entry = allCacheFiles.files[i].value
if (entry.isRenderable) {
try {
const { bodyHTML, width, height, dir, filename } = entry

const intWidth = Math.floor(width)
const intHeight = Math.floor(height)
await page.setViewport({
width: intWidth,
height: intHeight,
})

const pageHTML = `<!DOCTYPE html>
<meta charset="utf-8">${stylesHTML}
<body>${bodyHTML}</body>`

await page.setContent(pageHTML)

const renderPath = path.join(pluginOptions.path, dir)
await mkdirp(renderPath)
const screenshotPath = path.format({ dir: renderPath, base: filename })
await page.screenshot({
type: 'png',
clip: {
x: 0,
y: 0,
width: intWidth,
height: intHeight,
},
omitBackground: true,
path: screenshotPath,
})
} catch (e) {
console.error(e)
}
}
}
/* eslint-enable no-await-in-loop */

await page.close()
await browser.close()
}
62 changes: 62 additions & 0 deletions plugins/gatsby-render-components/gatsby-ssr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const Cache = require('file-system-cache').default
const path = require('path')
const { createElement } = require('react')
const { renderToStaticMarkup } = require('react-dom/server')

const prettifyUrl = require('./prettify-url')
const { setAddToRenderQueueCallback } = require('./rendered-component')

const cache = Cache({
basePath: './.cache/component-rendering',
ns: 'component-rendering',
})

/**
* Adds component HTML to the build cache so that gatsby-node can use puppeteer
* to render the markup
*/
setAddToRenderQueueCallback(
({ Component, props, width, height, relativePath, filename }) => {
try {
const modifiedProps = { ...props, width, height }
const element = createElement(Component, modifiedProps)
const bodyHTML = renderToStaticMarkup(element)

const prettyPath = prettifyUrl(relativePath)
const prettyFileName = prettifyUrl(filename)
const componentPath = path.resolve(prettyPath, prettyFileName)

cache.setSync(componentPath, {
bodyHTML,
width,
height,
dir: prettyPath,
filename: prettyFileName,
isRenderable: true,
})
} catch (e) {
console.error(e)
}
},
)

/**
* Grabs CSS information from the render so that it can be used for rendering
* components
*/
exports.onPreRenderHTML = ({ getHeadComponents }) => {
const headComponents = getHeadComponents()
const styles = headComponents.filter(headComponent => {
/* eslint-disable no-underscore-dangle */
return headComponent.type === 'style' &&
headComponent.props &&
headComponent.props.dangerouslySetInnerHTML &&
headComponent.props.dangerouslySetInnerHTML.__html
/* eslint-enable no-underscore-dangle */
}).map(headComponent => headComponent.props.dangerouslySetInnerHTML.__html)

// cache.fileExists is asynchronous, so use getSync instead
if (cache.getSync('_styles') === undefined) {
cache.setSync('_styles', { styles, isRenderable: false })
}
}
1 change: 1 addition & 0 deletions plugins/gatsby-render-components/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports.renderedComponent = require('./rendered-component').default
14 changes: 14 additions & 0 deletions plugins/gatsby-render-components/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "gatsby-render-components",
"version": "1.0.0",
"description": "A Gatsby plugin for generating a png file from a wrapped component",
"author": "",
"license": "Apache-2.0",
"dependencies": {
"file-system-cache": "^1.0.5",
"mkdirp": "^1.0.4",
"puppeteer": "^2.1.1",
"react": "^16.12.0",
"react-dom": "^16.12.0"
}
}
6 changes: 6 additions & 0 deletions plugins/gatsby-render-components/prettify-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const prettifyUrl = url => {
const spacesReplaced = url.replace(' ', '-')
return spacesReplaced.replace(/[^a-zA-Z0-9-_.]/g, '').toLowerCase()
}

module.exports = prettifyUrl
32 changes: 32 additions & 0 deletions plugins/gatsby-render-components/rendered-component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react'

let addToRenderQueue = () => undefined

export const setAddToRenderQueueCallback = callback => {
addToRenderQueue = callback
}

const renderedComponent = Component => {
return class extends React.Component {
constructor(props) {
super(props)
const { renderOptions, ...passThroughProps } = props
const { width, height, relativePath, filename } = renderOptions
addToRenderQueue({
Component,
props: passThroughProps,
width,
height,
relativePath,
filename,
})
}

render() {
const { renderOptions, ...passThroughProps } = this.props
return <Component {...passThroughProps} />
}
}
}

export default renderedComponent
2 changes: 1 addition & 1 deletion plugins/gatsby-source-covid-tracking-counties/index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
//noop
// noop
2 changes: 1 addition & 1 deletion plugins/gatsby-transformer-covid-census/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const onCreateNode = async (
id: createNodeId(`${node.id}.population >>> CENSUS`),
children: [],
parent: node.id,
population: population,
population,
internal: {
contentDigest: createContentDigest(digest),
type: 'population',
Expand Down
4 changes: 4 additions & 0 deletions src/components/charts/bar-chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { formatDate, formatNumber } from '~utilities/visualization'
import chartStyles from './charts.module.scss'

import colors from '~scss/colors.module.scss'
import { renderedComponent } from '~plugins/gatsby-render-components'

const BarChart = ({
data,
Expand Down Expand Up @@ -252,4 +253,7 @@ BarChart.propTypes = {
yMax: PropTypes.number,
yTicks: PropTypes.number,
}

export const RenderedBarChart = renderedComponent(BarChart)

export default BarChart
28 changes: 23 additions & 5 deletions src/components/common/summary-charts.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@reach/disclosure'
import { DateTime } from 'luxon'
import Container from '~components/common/container'
import BarChart from '~components/charts/bar-chart'
import { RenderedBarChart } from '~components/charts/bar-chart'
import { parseDate } from '~utilities/visualization'
import { Row, Col } from '~components/common/grid'
import Toggle from '~components/common/toggle'
Expand Down Expand Up @@ -188,6 +188,11 @@ export default ({ name = 'National', history, usHistory, annotations }) => {

const showTodaysChartTick =
DateTime.fromISO(data[data.length - 1].date).day > 10
const baseRenderOptions = {
relativePath: `${name}`,
width: 600,
height: 400,
}
return (
<>
<h2>{name} overview</h2>
Expand Down Expand Up @@ -221,13 +226,14 @@ export default ({ name = 'National', history, usHistory, annotations }) => {
openDisclosure={() => setDisclosureOpen(true)}
/>
</h5>
<BarChart
<RenderedBarChart
data={getDataForField(data, testField)}
lineData={dailyAverage(data, testField)}
refLineData={dailyAverage(usData, testField)}
fill={colors.colorPlum200}
lineColor={colors.colorPlum700}
lastXTick={showTodaysChartTick}
renderOptions={{ filename: 'new-tests.png', ...baseRenderOptions }}
{...props}
/>
</Col>
Expand All @@ -241,13 +247,17 @@ export default ({ name = 'National', history, usHistory, annotations }) => {
/>
</h5>
{hasData(positiveField) ? (
<BarChart
<RenderedBarChart
data={getDataForField(data, positiveField)}
lineData={dailyAverage(data, positiveField)}
refLineData={dailyAverage(usData, positiveField)}
fill={colors.colorStrawberry100}
lineColor={colors.colorStrawberry200}
lastXTick={showTodaysChartTick}
renderOptions={{
filename: 'new-cases.png',
...baseRenderOptions,
}}
{...props}
/>
) : (
Expand All @@ -265,13 +275,17 @@ export default ({ name = 'National', history, usHistory, annotations }) => {
</h5>

{hasData(hospitalizedField) ? (
<BarChart
<RenderedBarChart
data={getDataForField(data, hospitalizedField)}
lineData={dailyAverage(data, hospitalizedField)}
refLineData={dailyAverage(usData, hospitalizedField)}
fill={colors.colorBlueberry200}
lineColor={colors.colorBlueberry400}
lastXTick={showTodaysChartTick}
renderOptions={{
filename: 'hospitalizations.png',
...baseRenderOptions,
}}
{...props}
/>
) : (
Expand All @@ -288,13 +302,17 @@ export default ({ name = 'National', history, usHistory, annotations }) => {
/>
</h5>
{hasData(deathField) ? (
<BarChart
<RenderedBarChart
data={getDataForField(data, deathField)}
lineData={dailyAverage(data, deathField)}
refLineData={dailyAverage(usData, deathField)}
fill={colors.colorSlate300}
lineColor={colors.colorSlate700}
lastXTick={showTodaysChartTick}
renderOptions={{
filename: 'new-deaths.png',
...baseRenderOptions,
}}
{...props}
/>
) : (
Expand Down

0 comments on commit 6440e2d

Please sign in to comment.