diff --git a/examples/using-javascript-transforms/.gitignore b/examples/using-javascript-transforms/.gitignore new file mode 100644 index 0000000000000..8f5b35a4a9cbc --- /dev/null +++ b/examples/using-javascript-transforms/.gitignore @@ -0,0 +1,3 @@ +public +.cache +node_modules diff --git a/examples/using-javascript-transforms/README.md b/examples/using-javascript-transforms/README.md new file mode 100644 index 0000000000000..3c7efcd9aff4b --- /dev/null +++ b/examples/using-javascript-transforms/README.md @@ -0,0 +1,8 @@ +# Using Javascript Transforms +## Where we get fancy what we can do with gatsby +### An exploration of the javascript ecosystem in Gatsby +TODO, ALL OF THIS. + +It mixes javascript and remark, uses scss and bulma.io, has use case examples for graphql in layouts, and some "manual" page creation with the help of the jsFrontmatter transformer. + +For the time being, read the comments within the files themselves. diff --git a/examples/using-javascript-transforms/gatsby-config.js b/examples/using-javascript-transforms/gatsby-config.js new file mode 100644 index 0000000000000..459c1c177635b --- /dev/null +++ b/examples/using-javascript-transforms/gatsby-config.js @@ -0,0 +1,53 @@ +module.exports = { + siteMetadata: { + title: "Fancy Javascript Example", + siteDescr: "We get fancy with some javascript here.", + siteAuthor: "Jacob Bolda", + + siteEmailUrl: "me@x.com", + siteEmailPretty: "me@x.com", + siteTwitterUrl: "https://twitter.com/jacob_bolda", + siteTwitterPretty: "@jacob_bolda", + }, + plugins: [ + { + resolve: `gatsby-source-filesystem`, + options: { + name: `pages`, + path: `${__dirname}/src/mainPages/`, + }, + }, + { + resolve: `gatsby-source-filesystem`, + options: { + name: `articles`, + path: `${__dirname}/src/articles/`, + }, + }, + `gatsby-transformer-javascript-static-exports`, + `gatsby-transformer-yaml`, + { + resolve: `gatsby-transformer-remark`, + options: { + plugins: [ + { + resolve: `gatsby-remark-images`, + options: { + maxWidth: 690, + }, + }, + { + resolve: `gatsby-remark-responsive-iframe`, + options: {}, + }, + `gatsby-remark-prismjs`, + `gatsby-remark-copy-linked-files`, + `gatsby-remark-smartypants`, + ], + }, + }, + `gatsby-plugin-sharp`, + `gatsby-plugin-postcss-sass`, + `gatsby-plugin-offline`, + ], +} \ No newline at end of file diff --git a/examples/using-javascript-transforms/gatsby-node.js b/examples/using-javascript-transforms/gatsby-node.js new file mode 100644 index 0000000000000..8198ac851fdb4 --- /dev/null +++ b/examples/using-javascript-transforms/gatsby-node.js @@ -0,0 +1,125 @@ +const path = require('path') + +exports.onCreateNode = ({ node, boundActionCreators, getNode }) => { + const { createNodeField } = boundActionCreators + let slug + if (node.internal.type === `MarkdownRemark` || node.internal.type === `JSFrontmatter`) { + const fileNode = getNode(node.parent) + const parsedFilePath = path.parse(fileNode.relativePath) + if (parsedFilePath.name !== `index` && parsedFilePath.dir !== ``) { + slug = `/${parsedFilePath.dir}/${parsedFilePath.name}/` + } else if (parsedFilePath.dir === ``) { + slug = `/${parsedFilePath.name}/` + } else { + slug = `/${parsedFilePath.dir}/` + } + + // Add slug as a field on the node. + createNodeField({ node, name: `slug`, value: slug }) + } +} + +exports.createPages = ({ graphql, boundActionCreators }) => { + const { createPage } = boundActionCreators + + return new Promise((resolve, reject) => { + const pages = [] + const markdownTemplate = path.resolve("src/templates/markdown.js") + const jsTemplate = path.resolve("src/templates/javascript.js") + + // Query for all markdown "nodes" and for the slug we previously created. + resolve( + graphql( + ` + { + allMarkdownRemark { + edges { + node { + frontmatter { + layoutType + path + } + fields { + slug + } + } + } + } + allJsFrontmatter { + edges { + node { + fileAbsolutePath + data { + layoutType + path + } + fields { + slug + } + } + } + } + } + ` + ).then(result => { + if (result.errors) { + console.log(result.errors) + console.log(result) + reject(result.errors) + } + + // Create from markdown + result.data.allMarkdownRemark.edges.forEach(edge => { + let frontmatter = edge.node.frontmatter; + // ideally we would want to use layoutType to + // decide which (nested) layout to use, but + // gatsby currently doesnt support this. + if (frontmatter.layoutType === 'post' || + frontmatter.layoutType === 'page') { + createPage({ + path: frontmatter.path, // required + component: markdownTemplate, + context: { + layoutType: frontmatter.layoutType, + slug: edge.node.fields.slug, + }, + }) + } + }) + + // Create pages from javascript + // Gatsby will, by default, createPages for javascript in the + // /pages directory. We purposely don't have a folder with this name + // so that we can go full manual mode. + result.data.allJsFrontmatter.edges.forEach(edge => { + let frontmatter = edge.node.data; + // see above + if (frontmatter.layoutType === 'post' || + frontmatter.layoutType === 'page') { + createPage({ + path: frontmatter.path, // required + // Note, we can't have a template, but rather require the file directly. + // Templates are for converting non-react into react. jsFrontmatter + // picks up all of the javascript files. We have only written these in react. + component: path.resolve(edge.node.fileAbsolutePath), + context: { + layoutType: frontmatter.layoutType, + slug: edge.node.fields.slug, + }, + }) + } else if (edge.node.fields.slug === '/index/') { + createPage({ + path: '/', // required, we don't have frontmatter for this page hence separate if() + component: path.resolve(edge.node.fileAbsolutePath), + context: { + slug: edge.node.fields.slug, + }, + }) + } + }) + + return + }) + ) + }) +} diff --git a/examples/using-javascript-transforms/package.json b/examples/using-javascript-transforms/package.json new file mode 100644 index 0000000000000..7b179531f84c7 --- /dev/null +++ b/examples/using-javascript-transforms/package.json @@ -0,0 +1,50 @@ +{ + "name": "gatsby-example-using-javascript-transforms", + "version": "1.0.0", + "description": "example site and blog", + "main": "index.js", + "scripts": { + "develop": "node_modules/.bin/gatsby develop", + "dev:hard": "rm -rf .cache && rm -rf public && npm run develop", + "build": "npm run clean & .\\node_modules\\.bin\\gatsby build", + "serve-build": "node_modules/.bin/gatsby serve-build", + "clean:public": "rm -rf public & mkdir public", + "clean": "npm run clean:public", + "lint": "./node_modules/.bin/eslint --ext .js,.jsx --ignore-pattern public .", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "personal", + "blog" + ], + "author": "jbolda", + "license": "MIT", + "bugs": { + "url": "https://github.com/gatsbyjs/gatsby/issues" + }, + "homepage": "https://www.gatsbyjs.org", + "dependencies": { + "bulma": "0.4.2", + "d3": "4.9.1", + "gatsby": "^1.1.0", + "gatsby-link": "^1.0.9", + "gatsby-plugin-offline": "^1.0.1", + "gatsby-plugin-postcss-sass": "^1.0.1", + "gatsby-plugin-sharp": "^1.0.0", + "gatsby-remark-copy-linked-files": "^1.0.1", + "gatsby-remark-prismjs": "^1.1.0", + "gatsby-remark-responsive-iframe": "^1.0.1", + "gatsby-remark-images": "^1.1.0", + "gatsby-remark-smartypants": "^1.0.1", + "gatsby-source-filesystem": "^1.0.1", + "gatsby-source-contentful": "^1.1.0", + "gatsby-transformer-javascript-static-exports": "^1.0.1", + "gatsby-transformer-remark": "^1.1.0", + "gatsby-transformer-sharp": "^1.0.1", + "gatsby-transformer-yaml": "^1.0.0", + "moment": "^2.14.1", + "normalize.css": "^7.0.0", + "prop-types": "^15.5.8", + "react-helmet": "^3.1.0" + } +} diff --git a/examples/using-javascript-transforms/src/articles/2017-01-22-a-first-post/index.md b/examples/using-javascript-transforms/src/articles/2017-01-22-a-first-post/index.md new file mode 100644 index 0000000000000..6cdc33dda30f8 --- /dev/null +++ b/examples/using-javascript-transforms/src/articles/2017-01-22-a-first-post/index.md @@ -0,0 +1,19 @@ +--- +title: First Post About First Post +written: "2017-01-22" +updated: "2017-03-04" +layoutType: post +path: "a-first-post" +category: "Beginnings" +description: "From humble beginnings to... space?" +--- + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sed nisi eu quam ultrices malesuada. Vestibulum dictum aliquet turpis et lobortis. In a massa nec risus convallis accumsan sed a arcu. Sed lacus sapien, elementum et condimentum et, dictum eget sem. Suspendisse tellus mauris, elementum placerat commodo sit amet, iaculis vitae justo. Fusce sed orci feugiat, cursus ante consectetur, vulputate urna. Duis pharetra magna sed semper auctor. Nullam et nunc nulla. Donec nibh nibh, ornare a ipsum quis, condimentum faucibus nibh. Etiam venenatis nec nibh vitae porta. Aliquam erat volutpat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed auctor semper dolor eu rhoncus. Mauris sit amet lacus pulvinar nunc vehicula vulputate. Ut quis nisl hendrerit, elementum ante quis, maximus mi. + +Sed fermentum finibus mauris. Nullam posuere ornare purus eu viverra. Fusce placerat erat ac dolor tincidunt, vitae elementum lorem luctus. Donec molestie et urna eu aliquam. In aliquam mauris in justo ullamcorper commodo. Donec sodales viverra quam vitae interdum. Maecenas volutpat congue massa. Suspendisse congue massa lorem, vitae luctus mi semper quis. + +Etiam sodales felis at magna condimentum, eu placerat arcu pulvinar. Nulla facilisi. Mauris bibendum felis in ex sagittis ornare. Mauris varius luctus magna, sed mattis lorem blandit id. Fusce condimentum odio non dui semper, quis finibus diam vehicula. Pellentesque ac aliquam lorem. Aliquam ac neque augue. Phasellus tempus faucibus blandit. Nulla iaculis tortor felis, et luctus libero rhoncus blandit. Donec mollis tortor nec tellus imperdiet, in porttitor sem molestie. Duis ac arcu rutrum urna auctor mollis. Phasellus ante dolor, congue ac lacinia id, ultrices et urna. + +Nullam id mollis justo. Sed malesuada interdum purus id commodo. Fusce finibus porttitor dolor, in pretium nulla. Praesent eleifend ornare nibh, id dictum sapien blandit sodales. Vestibulum scelerisque dolor sit amet tincidunt tincidunt. Integer at auctor odio. Maecenas at mattis nisi. Proin lobortis, ex sed tincidunt imperdiet, lorem odio porta felis, ac consectetur orci metus vel nisi. Donec quis libero dapibus, pulvinar est vitae, ullamcorper neque. Sed vestibulum nulla sed turpis congue elementum. Sed nunc nibh, lacinia non venenatis eget, mattis at libero. Praesent venenatis nulla vitae magna ullamcorper fringilla. Mauris vestibulum nec nunc at elementum. + +Praesent neque lorem, auctor ut commodo ut, condimentum in justo. Nam metus odio, bibendum dignissim neque vel, finibus dapibus nisi. In hac habitasse platea dictumst. Ut viverra magna rhoncus neque placerat finibus. Aenean vestibulum, quam non congue vulputate, risus felis convallis magna, sit amet ullamcorper odio arcu in tellus. Vivamus aliquam metus vel ante venenatis, vitae efficitur tellus facilisis. Ut tempus cursus mi, ultricies fringilla nunc. Mauris ac aliquet tortor. Cras ut ornare justo, dignissim vestibulum est. Donec pulvinar nibh nec venenatis viverra. Sed consectetur volutpat metus in volutpat. Suspendisse vulputate placerat tortor nec ultricies. Integer sed felis euismod lectus lobortis molestie. Donec finibus velit ullamcorper, feugiat dolor eu, maximus sem. Aenean congue vulputate massa quis placerat. diff --git a/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/_choropleth.md b/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/_choropleth.md new file mode 100644 index 0000000000000..3bf16d3870bb6 --- /dev/null +++ b/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/_choropleth.md @@ -0,0 +1,13 @@ +--- +what: text for Choropleth on d3v4 +--- + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sed nisi eu quam ultrices malesuada. Vestibulum dictum aliquet turpis et lobortis. In a massa nec risus convallis accumsan sed a arcu. Sed lacus sapien, elementum et condimentum et, dictum eget sem. Suspendisse tellus mauris, elementum placerat commodo sit amet, iaculis vitae justo. Fusce sed orci feugiat, cursus ante consectetur, vulputate urna. Duis pharetra magna sed semper auctor. Nullam et nunc nulla. Donec nibh nibh, ornare a ipsum quis, condimentum faucibus nibh. Etiam venenatis nec nibh vitae porta. Aliquam erat volutpat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed auctor semper dolor eu rhoncus. Mauris sit amet lacus pulvinar nunc vehicula vulputate. Ut quis nisl hendrerit, elementum ante quis, maximus mi. + +Sed fermentum finibus mauris. Nullam posuere ornare purus eu viverra. Fusce placerat erat ac dolor tincidunt, vitae elementum lorem luctus. Donec molestie et urna eu aliquam. In aliquam mauris in justo ullamcorper commodo. Donec sodales viverra quam vitae interdum. Maecenas volutpat congue massa. Suspendisse congue massa lorem, vitae luctus mi semper quis. + +Etiam sodales felis at magna condimentum, eu placerat arcu pulvinar. Nulla facilisi. Mauris bibendum felis in ex sagittis ornare. Mauris varius luctus magna, sed mattis lorem blandit id. Fusce condimentum odio non dui semper, quis finibus diam vehicula. Pellentesque ac aliquam lorem. Aliquam ac neque augue. Phasellus tempus faucibus blandit. Nulla iaculis tortor felis, et luctus libero rhoncus blandit. Donec mollis tortor nec tellus imperdiet, in porttitor sem molestie. Duis ac arcu rutrum urna auctor mollis. Phasellus ante dolor, congue ac lacinia id, ultrices et urna. + +Nullam id mollis justo. Sed malesuada interdum purus id commodo. Fusce finibus porttitor dolor, in pretium nulla. Praesent eleifend ornare nibh, id dictum sapien blandit sodales. Vestibulum scelerisque dolor sit amet tincidunt tincidunt. Integer at auctor odio. Maecenas at mattis nisi. Proin lobortis, ex sed tincidunt imperdiet, lorem odio porta felis, ac consectetur orci metus vel nisi. Donec quis libero dapibus, pulvinar est vitae, ullamcorper neque. Sed vestibulum nulla sed turpis congue elementum. Sed nunc nibh, lacinia non venenatis eget, mattis at libero. Praesent venenatis nulla vitae magna ullamcorper fringilla. Mauris vestibulum nec nunc at elementum. + +Praesent neque lorem, auctor ut commodo ut, condimentum in justo. Nam metus odio, bibendum dignissim neque vel, finibus dapibus nisi. In hac habitasse platea dictumst. Ut viverra magna rhoncus neque placerat finibus. Aenean vestibulum, quam non congue vulputate, risus felis convallis magna, sit amet ullamcorper odio arcu in tellus. Vivamus aliquam metus vel ante venenatis, vitae efficitur tellus facilisis. Ut tempus cursus mi, ultricies fringilla nunc. Mauris ac aliquet tortor. Cras ut ornare justo, dignissim vestibulum est. Donec pulvinar nibh nec venenatis viverra. Sed consectetur volutpat metus in volutpat. Suspendisse vulputate placerat tortor nec ultricies. Integer sed felis euismod lectus lobortis molestie. Donec finibus velit ullamcorper, feugiat dolor eu, maximus sem. Aenean congue vulputate massa quis placerat. diff --git a/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/index.js b/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/index.js new file mode 100644 index 0000000000000..371fe6fdfa91a --- /dev/null +++ b/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/index.js @@ -0,0 +1,209 @@ +import React from 'react'; +import { findDOMNode } from 'react-dom'; +var d3 = require('d3'); +import PostPublished from '../../components/PostPublished'; +import HelmetBlock from '../../components/HelmetBlock'; +// We have to include these components on every javascript page +// as we cannot use templates and layouts cannot query for data. +// Logically it would make sense to put these on the parents, but +// for now they have to be children of EVERY post. + + +// this is one method to export data and make it usable elsewhere +exports.data = { + title: 'Choropleth on d3v4', + written: '2017-03-09', + updated: '2017-04-28', + layoutType: 'post', + path: 'choropleth-on-d3v4', + category: 'data science', + description: 'Things about the choropleth.' +} + +class choroplethBase extends React.Component { + constructor(props) { + super(props); + } + + + componentDidMount() { + this.d3Node = d3.select('div#states'); + let measurements = { + width: this.d3Node._groups[0][0].clientWidth, + height: this.d3Node._groups[0][0].clientHeight + } + let space = graph.setup(this.d3Node, measurements); + + /* + we begin drawing here, grab the data and use it to draw + */ + + d3.queue() + .defer(d3.json, stateDataURL) + .defer(d3.csv,statisticsDataURL) + .awaitAll(function(error, results) { + let states = results[0].states; + let stats = results[1]; + let mergedData = mergeData(states, 'abbrev', stats, 'Abbreviation') + graph.draw(space, mergedData, measurements); + }); + } + + componentWillUnmount () { + d3.select('svg').remove(); + } + + render() { + let data = this.props.data.markdownRemark; + let html = data.html; + let frontmatter = this.props.data.jsFrontmatter.data; + + return ( +
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+ ); + } +} + +export default choroplethBase; + +var graph = {}; // we namespace our d3 graph into setup and draw + +var stateDataURL = 'https://gist.githubusercontent.com/jbolda/52cd5926e9241d26489ec82fa2bddf37/raw/f409b82e51072ea23746325eff7aa85b7ef4ebbd/states.json'; +var statisticsDataURL = 'https://gist.githubusercontent.com/jbolda/52cd5926e9241d26489ec82fa2bddf37/raw/f409b82e51072ea23746325eff7aa85b7ef4ebbd/stats.csv'; + +graph.setup = (selection, measurements) => { +// the path string is drawn expecting: + // a width of 950px + // a height of 600px + // which gives an aspect ratio of 1.6 + + let svg = selection.append('svg') + .attr('width', measurements.width) + .attr('height', measurements.width / 1.6); + + return svg; +} + +graph.draw = (svg, data, measurements) => { +/* +our data expects an array of objects +each object is expected to have: +name: tooltip - the full name of the state +abbrev: mergeData - used as the key to merge the json and csv +low: tooltip, color domain +high: tooltip, color domain +average: tooltip, path fill +*/ + let color = d3.scaleQuantize() + .range(["rgb(237,248,233)", + "rgb(186,228,179)", + "rgb(116,196,118)", + "rgb(49,163,84)", + "rgb(0,109,44)"]); + + color.domain([ + d3.min(data, function(d) { return d.low; }), + d3.max(data, function(d) { return d.high; }) + ]); + + let scaleFactor = measurements.width / 950; + + let states = svg.selectAll('path.states') + .data(data); + + let drawStates = states.enter().append('path') + .attr('class', 'state') + .attr('id', d => d.abbrev) + .attr('stroke', 'gray') + .attr('d', d => d.path) + .attr('transform', 'scale('+scaleFactor+')') + .style('fill', d => color(d.average)) + .on('mouseover', mouseOver) + .on('mouseout', mouseOut); +} + + +let tooltipHtml = (d) => { + return '

'+d.name+'

'+ + ''+ + ''+ + ''+ + '
Low'+(d.low)+'
High'+(d.high)+'
Avg'+(d.average)+'
'; +} + +let mouseOver = (d) => { + let tooltip = d3.select('#tooltip') + .html(tooltipHtml(d)) + .style('opacity', .9) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY - 28) + 'px'); + + tooltip.transition().duration(200) +} + +let mouseOut = () => { + d3.select('#tooltip') + .transition().duration(500) + .style('opacity', 0); +} + +function scale (scaleFactor,width,height) { + return d3.geoTransform({ + point: function(x, y) { + this.stream.point( (x - width/2) * scaleFactor + width/2 , (y - height/2) * scaleFactor + height/2); + } + }); + } + +let mergeData = (d1, d1key, d2, d2key) => { + let data = []; + d1.forEach((s1) => { + d2.forEach((s2) => { + if (s1[d1key] === s2[d2key]) { + data.push(Object.assign({}, s1, s2)) + } + }) + }) + + return data; +}; + +// We want to keep this component mostly about the code +// so we write our explanation with markdown and manually pull it in here. +// Within the config, we loop all of the markdown and createPages. However, +// it will ignore any files appended with an _underscore. We can still manually +// query for it here, and get the transformed html though because remark transforms +// any markdown based node. +export const pageQuery = graphql` +query choroplethOnD3v4($slug: String!) { + markdownRemark(fields: { slug: { eq: "/2017-03-09-choropleth-on-d3v4/_choropleth/" }}) { + html + } + jsFrontmatter(fields: {slug: {eq: $slug}}) { + data { + error + layoutType + path + title + written + category + description + updated + } + } +} +` diff --git a/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/style.scss b/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/style.scss new file mode 100644 index 0000000000000..cc0fb0c5877ad --- /dev/null +++ b/examples/using-javascript-transforms/src/articles/2017-03-09-choropleth-on-d3v4/style.scss @@ -0,0 +1,46 @@ + .state{ + fill: none; + stroke: #a9a9a9; + stroke-width: 1; + } + .state:hover{ + fill-opacity:0.5; + } + #tooltip { + position: absolute; + text-align: center; + padding: 20px; + margin: 10px; + font: 12px sans-serif; + background: lightsteelblue; + border: 1px; + border-radius: 2px; + pointer-events: none; + } + #tooltip h4{ + margin:0; + font-size:14px; + } + #tooltip{ + background:rgba(0,0,0,0.9); + border:1px solid grey; + border-radius:5px; + font-size:12px; + width:auto; + padding:4px; + color:white; + opacity:0; + } + #tooltip table{ + table-layout:fixed; + } + #tooltip tr td{ + padding:0; + margin:0; + } + #tooltip tr td:nth-child(1){ + width:50px; + } + #tooltip tr td:nth-child(2){ + text-align:center; + } \ No newline at end of file diff --git a/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/_choropleth.md b/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/_choropleth.md new file mode 100644 index 0000000000000..3bf16d3870bb6 --- /dev/null +++ b/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/_choropleth.md @@ -0,0 +1,13 @@ +--- +what: text for Choropleth on d3v4 +--- + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sed nisi eu quam ultrices malesuada. Vestibulum dictum aliquet turpis et lobortis. In a massa nec risus convallis accumsan sed a arcu. Sed lacus sapien, elementum et condimentum et, dictum eget sem. Suspendisse tellus mauris, elementum placerat commodo sit amet, iaculis vitae justo. Fusce sed orci feugiat, cursus ante consectetur, vulputate urna. Duis pharetra magna sed semper auctor. Nullam et nunc nulla. Donec nibh nibh, ornare a ipsum quis, condimentum faucibus nibh. Etiam venenatis nec nibh vitae porta. Aliquam erat volutpat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed auctor semper dolor eu rhoncus. Mauris sit amet lacus pulvinar nunc vehicula vulputate. Ut quis nisl hendrerit, elementum ante quis, maximus mi. + +Sed fermentum finibus mauris. Nullam posuere ornare purus eu viverra. Fusce placerat erat ac dolor tincidunt, vitae elementum lorem luctus. Donec molestie et urna eu aliquam. In aliquam mauris in justo ullamcorper commodo. Donec sodales viverra quam vitae interdum. Maecenas volutpat congue massa. Suspendisse congue massa lorem, vitae luctus mi semper quis. + +Etiam sodales felis at magna condimentum, eu placerat arcu pulvinar. Nulla facilisi. Mauris bibendum felis in ex sagittis ornare. Mauris varius luctus magna, sed mattis lorem blandit id. Fusce condimentum odio non dui semper, quis finibus diam vehicula. Pellentesque ac aliquam lorem. Aliquam ac neque augue. Phasellus tempus faucibus blandit. Nulla iaculis tortor felis, et luctus libero rhoncus blandit. Donec mollis tortor nec tellus imperdiet, in porttitor sem molestie. Duis ac arcu rutrum urna auctor mollis. Phasellus ante dolor, congue ac lacinia id, ultrices et urna. + +Nullam id mollis justo. Sed malesuada interdum purus id commodo. Fusce finibus porttitor dolor, in pretium nulla. Praesent eleifend ornare nibh, id dictum sapien blandit sodales. Vestibulum scelerisque dolor sit amet tincidunt tincidunt. Integer at auctor odio. Maecenas at mattis nisi. Proin lobortis, ex sed tincidunt imperdiet, lorem odio porta felis, ac consectetur orci metus vel nisi. Donec quis libero dapibus, pulvinar est vitae, ullamcorper neque. Sed vestibulum nulla sed turpis congue elementum. Sed nunc nibh, lacinia non venenatis eget, mattis at libero. Praesent venenatis nulla vitae magna ullamcorper fringilla. Mauris vestibulum nec nunc at elementum. + +Praesent neque lorem, auctor ut commodo ut, condimentum in justo. Nam metus odio, bibendum dignissim neque vel, finibus dapibus nisi. In hac habitasse platea dictumst. Ut viverra magna rhoncus neque placerat finibus. Aenean vestibulum, quam non congue vulputate, risus felis convallis magna, sit amet ullamcorper odio arcu in tellus. Vivamus aliquam metus vel ante venenatis, vitae efficitur tellus facilisis. Ut tempus cursus mi, ultricies fringilla nunc. Mauris ac aliquet tortor. Cras ut ornare justo, dignissim vestibulum est. Donec pulvinar nibh nec venenatis viverra. Sed consectetur volutpat metus in volutpat. Suspendisse vulputate placerat tortor nec ultricies. Integer sed felis euismod lectus lobortis molestie. Donec finibus velit ullamcorper, feugiat dolor eu, maximus sem. Aenean congue vulputate massa quis placerat. diff --git a/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/index.js b/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/index.js new file mode 100644 index 0000000000000..fa15bafad2988 --- /dev/null +++ b/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/index.js @@ -0,0 +1,208 @@ +import React from 'react'; +import { findDOMNode } from 'react-dom'; +var d3 = require('d3'); +import PostPublished from '../../components/PostPublished'; +import HelmetBlock from '../../components/HelmetBlock'; +// We have to include these components on every javascript page +// as we cannot use templates and layouts cannot query for data. +// Logically it would make sense to put these on the parents, but +// for now they have to be children of EVERY post. + + +// this is an additional method to export data and make it usable elsewhere +export const data = { + title: 'Alternate Choropleth on d3v4', + written: '2017-05-30', + layoutType: 'post', + path: 'choropleth-on-d3v4-alternate', + category: 'data science', + description: 'Even more things about the choropleth. No, seriously.' +} + +class choroplethAltBase extends React.Component { + constructor(props) { + super(props); + } + + + componentDidMount() { + this.d3Node = d3.select('div#states'); + let measurements = { + width: this.d3Node._groups[0][0].clientWidth, + height: this.d3Node._groups[0][0].clientHeight + } + let space = graph.setup(this.d3Node, measurements); + + /* + we begin drawing here, grab the data and use it to draw + */ + + d3.queue() + .defer(d3.json, stateDataURL) + .defer(d3.csv,statisticsDataURL) + .awaitAll(function(error, results) { + let states = results[0].states; + let stats = results[1]; + let mergedData = mergeData(states, 'abbrev', stats, 'Abbreviation') + graph.draw(space, mergedData, measurements); + }); + } + + componentWillUnmount () { + d3.select('svg').remove(); + } + + render() { + let data = this.props.data.markdownRemark; + let html = data.html; + let frontmatter = this.props.data.jsFrontmatter.data; + + return ( +
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+ ); + } +} + +export default choroplethAltBase; + +var graph = {}; // we namespace our d3 graph into setup and draw + +var stateDataURL = 'https://gist.githubusercontent.com/jbolda/52cd5926e9241d26489ec82fa2bddf37/raw/f409b82e51072ea23746325eff7aa85b7ef4ebbd/states.json'; +var statisticsDataURL = 'https://gist.githubusercontent.com/jbolda/52cd5926e9241d26489ec82fa2bddf37/raw/f409b82e51072ea23746325eff7aa85b7ef4ebbd/stats.csv'; + +graph.setup = (selection, measurements) => { +// the path string is drawn expecting: + // a width of 950px + // a height of 600px + // which gives an aspect ratio of 1.6 + + let svg = selection.append('svg') + .attr('width', measurements.width) + .attr('height', measurements.width / 1.6); + + return svg; +} + +graph.draw = (svg, data, measurements) => { +/* +our data expects an array of objects +each object is expected to have: +name: tooltip - the full name of the state +abbrev: mergeData - used as the key to merge the json and csv +low: tooltip, color domain +high: tooltip, color domain +average: tooltip, path fill +*/ + let color = d3.scaleQuantize() + .range(["rgb(237,248,233)", + "rgb(186,228,179)", + "rgb(116,196,118)", + "rgb(49,163,84)", + "rgb(0,109,44)"]); + + color.domain([ + d3.min(data, function(d) { return d.low; }), + d3.max(data, function(d) { return d.high; }) + ]); + + let scaleFactor = measurements.width / 950; + + let states = svg.selectAll('path.states') + .data(data); + + let drawStates = states.enter().append('path') + .attr('class', 'state') + .attr('id', d => d.abbrev) + .attr('stroke', 'gray') + .attr('d', d => d.path) + .attr('transform', 'scale('+scaleFactor+')') + .style('fill', d => color(d.average)) + .on('mouseover', mouseOver) + .on('mouseout', mouseOut); +} + + +let tooltipHtml = (d) => { + return '

'+d.name+'

'+ + ''+ + ''+ + ''+ + '
Low'+(d.low)+'
High'+(d.high)+'
Avg'+(d.average)+'
'; +} + +let mouseOver = (d) => { + let tooltip = d3.select('#tooltip') + .html(tooltipHtml(d)) + .style('opacity', .9) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY - 28) + 'px'); + + tooltip.transition().duration(200) +} + +let mouseOut = () => { + d3.select('#tooltip') + .transition().duration(500) + .style('opacity', 0); +} + +function scale (scaleFactor,width,height) { + return d3.geoTransform({ + point: function(x, y) { + this.stream.point( (x - width/2) * scaleFactor + width/2 , (y - height/2) * scaleFactor + height/2); + } + }); + } + +let mergeData = (d1, d1key, d2, d2key) => { + let data = []; + d1.forEach((s1) => { + d2.forEach((s2) => { + if (s1[d1key] === s2[d2key]) { + data.push(Object.assign({}, s1, s2)) + } + }) + }) + + return data; +}; + +// We want to keep this component mostly about the code +// so we write our explanation with markdown and manually pull it in here. +// Within the config, we loop all of the markdown and createPages. However, +// it will ignore any files appended with an _underscore. We can still manually +// query for it here, and get the transformed html though because remark transforms +// any markdown based node. +export const pageQuery = graphql` +query choroplethOnD3v4Alt($slug: String!) { + markdownRemark(fields: { slug: { eq: "/2017-05-30-choropleth-on-d3v4-alternate/_choropleth/" }}) { + html + } + jsFrontmatter(fields: {slug: {eq: $slug}}) { + data { + error + layoutType + path + title + written + category + description + updated + } + } +} +` diff --git a/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/style.scss b/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/style.scss new file mode 100644 index 0000000000000..cc0fb0c5877ad --- /dev/null +++ b/examples/using-javascript-transforms/src/articles/2017-05-30-choropleth-on-d3v4-alternate/style.scss @@ -0,0 +1,46 @@ + .state{ + fill: none; + stroke: #a9a9a9; + stroke-width: 1; + } + .state:hover{ + fill-opacity:0.5; + } + #tooltip { + position: absolute; + text-align: center; + padding: 20px; + margin: 10px; + font: 12px sans-serif; + background: lightsteelblue; + border: 1px; + border-radius: 2px; + pointer-events: none; + } + #tooltip h4{ + margin:0; + font-size:14px; + } + #tooltip{ + background:rgba(0,0,0,0.9); + border:1px solid grey; + border-radius:5px; + font-size:12px; + width:auto; + padding:4px; + color:white; + opacity:0; + } + #tooltip table{ + table-layout:fixed; + } + #tooltip tr td{ + padding:0; + margin:0; + } + #tooltip tr td:nth-child(1){ + width:50px; + } + #tooltip tr td:nth-child(2){ + text-align:center; + } \ No newline at end of file diff --git a/examples/using-javascript-transforms/src/components/HelmetBlock/index.js b/examples/using-javascript-transforms/src/components/HelmetBlock/index.js new file mode 100644 index 0000000000000..ce65a61647f36 --- /dev/null +++ b/examples/using-javascript-transforms/src/components/HelmetBlock/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import Helmet from 'react-helmet'; +import moment from 'moment'; + +class HelmetBlock extends React.Component { + render() { + const frontmatter = this.props; + return ( +
+ +
+ ); + } +} + +export default HelmetBlock; diff --git a/examples/using-javascript-transforms/src/components/PostPublished/index.js b/examples/using-javascript-transforms/src/components/PostPublished/index.js new file mode 100644 index 0000000000000..15f9750820186 --- /dev/null +++ b/examples/using-javascript-transforms/src/components/PostPublished/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import Helmet from 'react-helmet'; +import moment from 'moment'; + +class PostPublished extends React.Component { + render() { + const frontmatter = this; + + if (frontmatter.updated === null) { + var published = ( +
+

published { moment(frontmatter.written).format('D MMM YYYY') }

+
+ ); + } else { + var published = ( +
+

originally published { moment(frontmatter.written).format('D MMM YYYY') } and + updated { moment(frontmatter.updated).format('D MMM YYYY') }

+
+ ); + } + + return ( +
+ {published} +
+ ); + } +} + +export default PostPublished; diff --git a/examples/using-javascript-transforms/src/components/SiteLinks/index.js b/examples/using-javascript-transforms/src/components/SiteLinks/index.js new file mode 100644 index 0000000000000..124faab83b7f6 --- /dev/null +++ b/examples/using-javascript-transforms/src/components/SiteLinks/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import './style.css'; +import siteMetadata from '../metadata.yaml'; +import '../../static/fonts/fontawesome/style.css'; + +class SiteLinks extends React.Component { + static propTypes = { + data: PropTypes.shape({ + site: PropTypes.shape({ + siteMetadata: PropTypes.object.isRequired + }) + }) + } + + render() { + return ( +
+ +
+ ); + } +} + +export default SiteLinks; diff --git a/examples/using-javascript-transforms/src/components/SiteLinks/style.css b/examples/using-javascript-transforms/src/components/SiteLinks/style.css new file mode 100644 index 0000000000000..3edfa16065856 --- /dev/null +++ b/examples/using-javascript-transforms/src/components/SiteLinks/style.css @@ -0,0 +1,30 @@ + +.blog-social { + margin-top: 30px; +} +.blog-social ul { + list-style: none; + padding: 0; + margin: 10px 0; + clear: fix-legacy; +} + +.blog-social ul > li { + margin-right: 5px; + height: 24px; +} + +.blog-social ul > li > a { + border-bottom: 0; +} + +.blog-social ul > li > a > i { + color: #606060; + font-size: 14px; + line-height: 24px; +} + +.blog-social ul > li:hover a > i { + color: #444; + font-color: #444; +} diff --git a/examples/using-javascript-transforms/src/components/SiteNav/index.js b/examples/using-javascript-transforms/src/components/SiteNav/index.js new file mode 100644 index 0000000000000..3987c4e526b0f --- /dev/null +++ b/examples/using-javascript-transforms/src/components/SiteNav/index.js @@ -0,0 +1,25 @@ +import React from 'react' +import Link from 'gatsby-link' +import './style.css' + +class SiteNav extends React.Component { + render() { + const {location} = this.props + return ( + + ); + } +} + +export default SiteNav \ No newline at end of file diff --git a/examples/using-javascript-transforms/src/components/SiteNav/style.css b/examples/using-javascript-transforms/src/components/SiteNav/style.css new file mode 100644 index 0000000000000..a4f21298e4c85 --- /dev/null +++ b/examples/using-javascript-transforms/src/components/SiteNav/style.css @@ -0,0 +1,24 @@ +.blog-nav { + margin: 20px 0px 10px; +} +.blog-nav ul { + list-style: none; + padding-left: 0; +} +.blog-nav ul li { + margin: 10px 0 10px; +} +.blog-nav ul li a { + font-size: 16px; + line-heigh: 26px; + margin-bottom: 26px; + border-bottom: 0; + font-weight: 400; + color: #222; +} +.blog-nav ul li a.current { + border-bottom: 1px solid; +} +.blog-nav ul li a:hover { + border-bottom: 1px solid; +} \ No newline at end of file diff --git a/examples/using-javascript-transforms/src/components/SiteSidebar/index.js b/examples/using-javascript-transforms/src/components/SiteSidebar/index.js new file mode 100644 index 0000000000000..c7e539ce29c67 --- /dev/null +++ b/examples/using-javascript-transforms/src/components/SiteSidebar/index.js @@ -0,0 +1,90 @@ +import React from 'react'; +import Link from 'gatsby-link'; +import SiteNav from '../SiteNav'; +import SiteLinks from '../SiteLinks'; +import siteMetadata from '../metadata.yaml'; +// cheating with ^ because no graphql + +class SiteSidebar extends React.Component { + render() { + const isHome = location.pathname === ('/'); + // const siteMetadata = this.props.data.siteMetadata; + // placeholder ^ for graphql + // TODO, deal with image more nice like + + let header = ( +
+
+ +
+ +
+ +
+
+

+ + { siteMetadata.title } + +

+

+ { siteMetadata.siteDescr } +

+
+
+ ) + + return ( +
+ { header } +
+ +
+
+ +
+
+

+ © All rights reserved. +

+

+ Made with