├── src
├── assets
│ └── .placeholder
├── data
│ ├── .placeholder
│ └── graphic.csv
├── js
│ ├── config.js
│ ├── detectFeatures.js
│ ├── utils.js
│ ├── share.js
│ ├── fm.js
│ └── thing.js
├── favicon.ico
├── jade
│ ├── includes
│ │ ├── test-pattern.jade
│ │ └── share.jade
│ ├── thing.jade
│ └── index.jade
└── styl
│ ├── qz
│ ├── buttons.styl
│ ├── share.styl
│ ├── test-pattern.styl
│ ├── colors.styl
│ ├── dropdowns.styl
│ ├── icons.styl
│ ├── chart-elements.styl
│ └── type.styl
│ ├── main.styl
│ ├── thing.styl
│ ├── layout.styl
│ ├── responsive.styl
│ └── normalize.styl
├── .gitignore
├── README.md
├── gulp
├── cli.js
├── utils.js
└── config.js
├── content.json
├── __build.sh
├── package.json
└── gulpfile.js
/src/assets/.placeholder:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/data/.placeholder:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/js/config.js:
--------------------------------------------------------------------------------
1 | var ENV = '/* @echo ENV */';
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | build
3 | !node_modules/stylus-normalize
4 | .DS_Store
5 | .tmp
6 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quartz/inequality-and-unions/master/src/favicon.ico
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # inequality-and-unions
2 |
3 | The relationship between inequality and unions in the OECD.
4 |
--------------------------------------------------------------------------------
/src/jade/includes/test-pattern.jade:
--------------------------------------------------------------------------------
1 | #test-pattern
2 | .circ.corner.t.l
3 | .circ.corner.t.r
4 | .circ.corner.b.l
5 | .circ.corner.b.r
6 | .circ.cent
--------------------------------------------------------------------------------
/src/jade/thing.jade:
--------------------------------------------------------------------------------
1 | h2 The correlation between union membership and inequality
2 |
3 | div#graphic
4 |
5 | div.footnotes Data: OECD. (TKTK link)
6 |
--------------------------------------------------------------------------------
/gulp/cli.js:
--------------------------------------------------------------------------------
1 | var opts = require('nomnom')
2 | .option('build', {
3 | abbr: 'b',
4 | help: 'Build project. [local | move | commit | push]',
5 | choices: ['local', 'move', 'commit', 'push']
6 | })
7 | .option('dont-minify', {
8 | abbr: 'd',
9 | flag: true,
10 | help: 'Prevent build from minifying your js'
11 | });
12 |
13 | module.exports = opts;
14 |
--------------------------------------------------------------------------------
/src/jade/includes/share.jade:
--------------------------------------------------------------------------------
1 | ul.share-buttons
2 | li.share-icon.twitter
3 | a(title='Tweet' class='share-action icon icon-twitter' target='_blank')
4 | li.share-icon.facebook
5 | a(title='Share on Facebook' class='share-action icon icon-facebook' target='_blank')
6 | li.share-icon.linkedin
7 | a(title='Share on LinkedIn' class='share-action icon icon-linkedin' target='_blank')
8 | li.share-icon.email
9 | a(title='Email' class='share-action icon icon-email' target='_blank')
10 |
--------------------------------------------------------------------------------
/src/styl/qz/buttons.styl:
--------------------------------------------------------------------------------
1 | qz-button-group()
2 | text-align center
3 | button
4 | color white
5 | border none
6 | background-color $qz-gray-1
7 | font-family $qz-sans
8 | border-radius 3px
9 | margin 2px 4px 2px 0
10 | vertical-align middle
11 | padding 0px 8px
12 | line-height 2em
13 | display inline-block
14 | &.active
15 | //font-family $qz-sans-bold
16 | background-color $qz-purp-2
17 | transition background-color 0.2s linear
18 | &:focus
19 | outline 0
20 |
--------------------------------------------------------------------------------
/gulp/utils.js:
--------------------------------------------------------------------------------
1 | function generateShellCmd (buildArg, qzdataPath, thingName) {
2 | var baseCmd = "./__build.sh " + qzdataPath + " " + thingName + " "; // then commit? push?
3 |
4 | switch (buildArg) {
5 | case "move":
6 | return baseCmd + "false false";
7 | case "commit":
8 | return baseCmd + "true false";
9 | case "push":
10 | return baseCmd + "true true";
11 | default:
12 | return 'echo ""';
13 | }
14 | }
15 |
16 | module.exports = {
17 | generateShellCmd: generateShellCmd
18 | };
19 |
--------------------------------------------------------------------------------
/src/js/detectFeatures.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | var features = {};
3 |
4 | features.hasDeviceMotion = 'ondevicemotion' in window;
5 | features.isAndroid = (/android/gi).test(navigator.appVersion);
6 | features.isIDevice = (/iphone|ipad/gi).test(navigator.appVersion);
7 | features.isTouchPad = (/hp-tablet/gi).test(navigator.appVersion);
8 | features.isKindle = (/silk/gi).test(navigator.appVersion);
9 | features.hasTouchEvents = (
10 | features.isAndroid ||
11 | features.isIDevice ||
12 | features.isTouchPad ||
13 | features.isKindle
14 | );
15 |
16 | return features;
17 | };
18 |
--------------------------------------------------------------------------------
/src/styl/main.styl:
--------------------------------------------------------------------------------
1 | // Normalize -- https://github.com/bymathias/normalize.styl
2 | @import 'normalize'
3 |
4 | // nib -- http://visionmedia.github.io/nib/
5 | @import 'nib'
6 |
7 | // Qz modules. Comment out the things you don't need.
8 | @import 'qz/type'
9 | @import 'qz/colors'
10 |
11 | // Optional components. Uncomment if you need them. See wiki for usage info
12 | // note: `share` depends on `icons`
13 | //@import 'qz/icons'
14 | //@import 'qz/share'
15 | //@import 'qz/dropdowns'
16 | //@import 'qz/buttons'
17 | //@import 'qz/chart-elements'
18 |
19 | // Thing-specific css
20 | @import 'responsive'
21 | @import 'layout'
22 | @import 'thing'
23 |
--------------------------------------------------------------------------------
/content.json:
--------------------------------------------------------------------------------
1 | {
2 | "hed": "This Quartz Thing has not connected to a Google Doc",
3 | "dek": "Perhaps you forgot to start the server? Make viewable to anyone with the link?",
4 | "items": [{
5 | "copy": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
6 | }, {
7 | "copy": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
8 | }, {
9 | "copy": "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"
10 | }]
11 | }
12 |
--------------------------------------------------------------------------------
/src/styl/qz/share.styl:
--------------------------------------------------------------------------------
1 | // Icon-related colors
2 | $color-twitter = #00aced
3 | $color-facebook = #3b5998
4 | $color-linkedin = #007bb6
5 | $color-email = #0096b6
6 | $color-icon-hover = #c3c5c0
7 |
8 | ul.share-buttons
9 | list-style-type none
10 | margin 0
11 | padding 0
12 |
13 | li.share-icon
14 | display inline-block
15 | font-size 1.2em
16 | a
17 | text-decoration none
18 | cursor pointer
19 |
20 | .icon-email
21 | color $color-email
22 | &:hover
23 | color $color-icon-hover
24 |
25 | .icon-facebook
26 | color $color-facebook
27 | &:hover
28 | color $color-icon-hover
29 |
30 | .icon-linkedin
31 | color $color-linkedin
32 | &:hover
33 | color $color-icon-hover
34 |
35 | .icon-twitter
36 | color $color-twitter
37 | &:hover
38 | color $color-icon-hover
39 |
--------------------------------------------------------------------------------
/src/jade/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | meta(charset='utf-8')
5 | meta(http-equiv='X-UA-Compatible', content='IE=edge')
6 | title inequality-and-unions
7 | meta(name='description', content='The relationship between inequality and unions in the OECD.')
8 | meta(name='viewport', content='width=device-width, initial-scale=1')
9 | link(rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Serif:400,700,400italic")
10 | link(rel='stylesheet', href='css/main.css?t=' + build_info.timestamp)
11 | link(rel="icon" href="/favicon.ico")
12 | script(src='https://app.qz.com/js/frameMessager/min/frameMessager.min.js')
13 |
14 | body
15 | base(target="_blank")
16 | div.item-body
17 | div#interactive-content
18 | include thing.jade
19 | script(src='js/config.js?t=' + build_info.timestamp)
20 | script(src='js/thing.js?t=' + build_info.timestamp)
21 |
--------------------------------------------------------------------------------
/src/styl/thing.styl:
--------------------------------------------------------------------------------
1 | @import 'qz/chart-elements'
2 |
3 | #annotation-background
4 | fill: $qz-background-gray
5 |
6 | #annotation
7 | font-family: $qz-sans-light
8 | fill: $qz-gray-2
9 |
10 | .dot
11 | fill $qz-purp-3
12 |
13 | .fit-line
14 | fill none
15 | stroke-width 2px
16 | stroke $qz-gray-1
17 | stroke-linecap round
18 | stroke-linejoin round
19 |
20 | .arrows path
21 | stroke-width: 1.5px
22 | stroke: $qz-body-black
23 | fill: none
24 |
25 | .labels text
26 | fill: $qz-body-black
27 | font-family: $qz-sans-bold
28 | -webkit-font-smoothing: antialiased
29 | font-size: 14px
30 |
31 | #x-label
32 | fill: $qz-gray-2
33 | font-family $qz-sans-light
34 |
35 |
36 | .footnotes
37 | font-size: 0.8em
38 | font-family: $qz-sans
39 | color: $qz-gray-2
40 | padding-left: 20px
41 | padding-bottom: 20px
42 |
43 | .clear
44 | clear:both
45 |
46 | @media only screen and (max-width $width-mobile)
47 | .arrows path
48 | stroke-width 1px
49 |
50 | .labels text
51 | font-family $qz-sans
52 |
--------------------------------------------------------------------------------
/src/data/graphic.csv:
--------------------------------------------------------------------------------
1 | "country","gini","pct_unionized"
2 | "ICE","24.37306701","85.5 "
3 | "NO","25.2","52.1 "
4 | "DEN","25.39","66.8 "
5 | "SLV","25.5470453","21.2 "
6 | "FN","25.735","69.0 "
7 | "CZ","26.16899727","12.7 "
8 | "BEL","26.75199686","55.1 "
9 | "SVK","26.91359694","13.3 "
10 | "AUT","27.96277636","27.8 "
11 | "SWE","28.0804","67.7 "
12 | "LUX","28.14576261","32.8 "
13 | "NED","28.3","17.8 "
14 | "HU","28.767","10.5 "
15 | "GE","29.215","18.1 "
16 | "FR","29.4","7.7 "
17 | "SWZ","29.5402653","16.2 "
18 | "POL","29.97268518","12.7 "
19 | "SK","30.23756836","10.1 "
20 | "IR","30.90000212","29.6 "
21 | "CA","32.18378515","27.1 "
22 | "IT","32.54833407","37.3 "
23 | "JP","33","17.8 "
24 | "NZ","33.3","19.8 "
25 | "AUS","33.7","17.0 "
26 | "POR","34.15638896","18.9 "
27 | "GR","34.32115027","21.5 "
28 | "SP","34.59415976","16.9 "
29 | "UK","35.8","25.8 "
30 | "EST","36.10388952","5.7 "
31 | "IS","36.45","22.8 "
32 | "TU","39.3","6.3 "
33 | "US","39.381632","10.8 "
34 | "MX","45.93388","13.6 "
35 | "CHL","46.5","15.0 "
36 |
--------------------------------------------------------------------------------
/src/styl/layout.styl:
--------------------------------------------------------------------------------
1 | // Page layout setup
2 |
3 | html, body
4 | min-width 270px
5 | font-family $qz-font-body
6 | color $qz-body-black
7 | background-color $qz-background-gray
8 |
9 | h1, h2, h4, h5
10 | font-family $qz-sans-bold
11 | color $qz-header-black
12 |
13 | h1
14 | line-height 50px
15 | margin-bottom 10px
16 |
17 | h2
18 | font-size 24px
19 | line-height 24px
20 | font-style bold
21 | margin 40px 0px
22 |
23 | h3
24 | font-size 20px
25 | font-family $qz-serif
26 | color: $qz-header-black
27 | font-weight: bold;
28 |
29 | p
30 | position relative
31 | margin 0 0 0 0
32 | padding 16px 0 16px 0
33 | line-height 1.6
34 | color #4c4c4c
35 |
36 | .item-body
37 | margin 0 auto
38 | //max-width 940px
39 |
40 | .clearfix
41 | clear both
42 |
43 | #interactive-content
44 | width 100%
45 | margin-left auto
46 | margin-right auto
47 |
48 | .copy-wrap
49 | width 86.25%
50 | max-width 940px
51 | margin 0 auto
52 |
53 | .copy
54 | max-width 640px
55 | width 100%
56 | float right
57 |
--------------------------------------------------------------------------------
/src/styl/qz/test-pattern.styl:
--------------------------------------------------------------------------------
1 | #test-pattern
2 | .circ
3 | // border 2px solid $qz-body-black
4 | border-radius 1000vw
5 | box-sizing border-box
6 |
7 | &.cent
8 | width 100%
9 | height 100%
10 | margin 0 auto
11 | position absolute
12 | border 4px solid $qz-body-black
13 |
14 | &.corner
15 | width 25vw
16 | height 25vw
17 | position absolute
18 | background-color alpha($qz-purp-2, 0.8)
19 |
20 | &.t
21 | top 0
22 | &.b
23 | bottom 0
24 | &.r
25 | right 0
26 | &.l
27 | left 0
28 |
29 | #interactive-content
30 | &[data-frame="0"]
31 | #test-pattern .circ.corner
32 | background-color alpha($qz-gray-1, 0.8)
33 |
34 | &[data-frame="1"]
35 | #test-pattern .circ.corner
36 | background-color alpha($qz-purp-2, 0.8)
37 |
38 | &[data-frame="2"]
39 | #test-pattern .circ.corner
40 | background-color alpha($qz-blue-2, 0.8)
41 |
42 | &[data-frame="3"]
43 | #test-pattern .circ.corner
44 | background-color alpha($qz-green-3, 0.8)
45 |
46 |
47 |
--------------------------------------------------------------------------------
/__build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | CURR_COMMIT=$(git log --oneline -n 1)
3 |
4 | printf '%s\n' "Copying built files to master repo"
5 | printf '%s\n' -------------------------
6 | rsync -rtvu --delete ./build/* $1
7 |
8 | cd $1
9 | QZDATA_BRANCH=$(git rev-parse --abbrev-ref HEAD)
10 |
11 | if [[ "$3" = true ]]; then
12 | printf '%s\n' "Committing ${CURR_COMMIT} to origin:${QZDATA_BRANCH}"
13 | printf '%s\n' -------------------------
14 | git add .
15 | git commit -m "built: ${2} | ${CURR_COMMIT}"
16 | fi
17 |
18 | if [[ "$4" = true ]]; then
19 | printf '%s\n' "Executing dry run of git push to origin:${QZDATA_BRANCH}"
20 | printf '%s\n' -------------------------
21 | echo `git push --dry-run origin ${QZDATA_BRANCH}`
22 | printf '%s\n' "That was a dry run to origin:${QZDATA_BRANCH}. Actually push? (y/n)"
23 | read cont -1
23 | if (ENV == 'prod' || local_prod_test) {
24 | if(local_prod_test) {
25 | PARENT_DOMAIN = "localhost:3000"
26 | }
27 | FM = frameMessager(
28 | _.assign({},
29 | {parentDomain : PARENT_DOMAIN},
30 | options
31 | )
32 | );
33 |
34 | document.body.style.overflow = "hidden";
35 | // Test environment: no frame messenging
36 | } else {
37 | document.body.style.border = "#ff8080";
38 | }
39 | }
40 |
41 | /**
42 | * Compute the height of the interactive.
43 | */
44 | /**
45 | * Get height of window including margin
46 | */
47 | function _getDocumentHeight() {
48 | var height = interactiveEl.offsetHeight;
49 | var style = getComputedStyle(interactiveEl);
50 |
51 | return height + parseInt(style.marginTop) + parseInt(style.marginBottom);
52 | }
53 |
54 | /**
55 | * Update parent height.
56 | */
57 | function updateHeight (height) {
58 | if (!FM) {
59 | return;
60 | }
61 |
62 | height = height || _getDocumentHeight();
63 |
64 | FM.triggerMessage("QZParent", "child:updateHeight", {
65 | height : height
66 | });
67 |
68 | return;
69 | }
70 |
71 | /**
72 | * Update parent hash.
73 | */
74 | function updateHash (hash) {
75 | if (!FM) {
76 | window.location.hash = hash;
77 | } else {
78 | FM.triggerMessage("QZParent", "child:updateHash", {
79 | hash : hash
80 | });
81 | }
82 | return;
83 | }
84 |
85 | /**
86 | * Read parent hash.
87 | */
88 | function getWindowProps () {
89 | if (!FM) {
90 | var frame = document.getElementById("interactive-content")
91 | var frame_bb = frame.getBoundingClientRect()
92 |
93 | var clientWidth = window.innerWidth;
94 | var clientHeight = window.innerHeight;
95 |
96 | return propCallback({
97 | action: "parent:readWindowProps",
98 | fromId: "QZParent",
99 | toId: "interactive-local",
100 | data: {
101 | windowProps: {
102 | clientDimensions: {
103 | width: clientWidth,
104 | height: clientHeight,
105 | },
106 | pageOffset: {
107 | x: window.scrollX,
108 | y: window.scrollY
109 | },
110 | uri: {
111 | hash: window.location.hash,
112 | href: window.location.href,
113 | origin: window.location.origin,
114 | pathname: window.location.pathname
115 | }
116 | }
117 | }
118 | });
119 | }
120 |
121 | FM.triggerMessage("QZParent", "child:getWindowProps");
122 |
123 | return;
124 | }
125 |
126 | function setupItemScroll(callback) {
127 | if(!FM) {
128 | var frame = document.getElementById("interactive-content")
129 |
130 | window.onscroll = function(){
131 | var rect = frame.getBoundingClientRect();
132 | var windowHeight = window.innerHeight;
133 |
134 | callback({
135 | "action":"itemWell:scroll",
136 | "data":{
137 | "frameTop":{
138 | "nav": rect.top,
139 | "window": windowHeight - rect.top
140 | },
141 | "frameBottom":{
142 | "nav": rect.bottom,
143 | "window": windowHeight - rect.bottom
144 | },
145 | "scrollDepth": document.body.scrollTop,
146 | "viewable":true,
147 | "visible":true
148 | },
149 | "fromId":"QZParent",
150 | "toId":"interactive-local"
151 | })
152 | }
153 | } else {
154 | FM.onMessage("itemWell:scroll", callback)
155 | }
156 | }
157 |
158 | /**
159 | * Set up a callback that will handle incoming hash data
160 | */
161 | function setupReadWindow(callback) {
162 | if (!FM) {
163 | propCallback = callback;
164 | } else {
165 | FM.onMessage("parent:readWindowProps", callback);
166 | }
167 | }
168 |
169 | /**
170 | * Resize the parent to match the new child height.
171 | */
172 | function resize () {
173 | updateHeight(_getDocumentHeight());
174 | }
175 |
176 | /**
177 | * Get height of window including margin
178 | */
179 | function _getWindowHeight() {
180 | var height = interactiveEl.offsetHeight;
181 | var style = getComputedStyle(interactiveEl);
182 |
183 | return height + parseInt(style.marginTop) + parseInt(style.marginBottom);
184 | }
185 |
186 | /**
187 | * Scroll the parent window to a given location.
188 | *
189 | * Call like this:
190 | * fm.scrollToPosition($("#scrollToThisDiv").offset().top,500)
191 | *
192 | * Where 500 is the duration of the scroll animation
193 | */
194 | function scrollToPosition (position,duration) {
195 | if (!FM) {
196 | d3.transition()
197 | .delay(0)
198 | .duration(duration)
199 | .tween("scroll", scrollTween(position));
200 | } else {
201 | FM.triggerMessage("QZParent", "child:scrollToPosition", {
202 | position : position,
203 | duration : 500
204 | });
205 | }
206 | }
207 |
208 |
209 | function scrollTween(offset) {
210 | return function() {
211 | var i = d3.interpolateNumber(window.pageYOffset || document.documentElement.scrollTop, offset);
212 | return function(t) { scrollTo(0, i(t)); };
213 | };
214 | }
215 |
216 | /**
217 | * Get a reference to the parent window.
218 | */
219 | function getParentWindow () {
220 | return FM.triggerMessage("QZParent", "child:getWindow");
221 | }
222 |
223 | // setupFrameMessenger();
224 |
225 | module.exports = {
226 | setup: setupFrameMessenger,
227 | updateHeight: updateHeight,
228 | resize: resize,
229 | scrollToPosition: scrollToPosition,
230 | getParentWindow: getParentWindow,
231 | updateHash: updateHash,
232 | getWindowProps: getWindowProps,
233 | setupReadWindow: setupReadWindow,
234 | setupItemScroll: setupItemScroll
235 | };
236 |
--------------------------------------------------------------------------------
/src/js/thing.js:
--------------------------------------------------------------------------------
1 | // NPM modules
2 | var _ = {};
3 | _.assign = require('lodash.assign');
4 |
5 | var d3 = _.assign({},
6 | require("d3-selection"),
7 | require("d3-request"),
8 | require("d3-scale"),
9 | require("d3-axis"),
10 | require("d3-shape"),
11 | require("d3-array"),
12 | require("d3-format"),
13 | require("d3-time-format")
14 | );
15 |
16 | d3.getEvent = function(){ return require("d3-selection").event}.bind(this);
17 |
18 |
19 | // Local modules
20 | var features = require('./detectFeatures')();
21 | var fm = require('./fm');
22 | var utils = require('./utils');
23 |
24 | // Globals
25 | var DEFAULT_WIDTH = 940;
26 | var MOBILE_BREAKPOINT = 600;
27 |
28 | var LABEL_DEFAULTS = {
29 | 'text-anchor': 'middle',
30 | 'font-size': 0.8,
31 | 'rotate': 0
32 | };
33 |
34 | var LABELS = [
35 | {
36 | 'country': 'Iceland',
37 | 'gini': 32,
38 | 'pct_unionized': 77.5,
39 | 'text-anchor': 'end'
40 | },
41 | {
42 | 'country': 'Chile',
43 | 'gini': 46.5,
44 | 'pct_unionized': 16,
45 | 'text-anchor': 'start'
46 | },
47 | {
48 | 'country': 'USA',
49 | 'gini': 39.2,
50 | 'pct_unionized': 11.75,
51 | 'text-anchor': 'start'
52 | }
53 | ];
54 |
55 | var ARROWS = [
56 | // Iceland
57 | {
58 | 'path': [
59 | [78, 32],
60 | [84, 30],
61 | [85.5, 26]
62 | ]
63 | }
64 | ];
65 |
66 | var FIT_SLOPE = -0.136362972;
67 | var FIT_INTERCEPT = 35.20618078;
68 |
69 | var graphicData = null;
70 | var isMobile = false;
71 |
72 | /**
73 | * Initialize the graphic.
74 | *
75 | * Fetch data, format data, cache HTML references, etc.
76 | */
77 | function init() {
78 | d3.csv('data/graphic.csv', function(error, data) {
79 | graphicData = formatData(data);
80 |
81 | fm.setup()
82 |
83 | render();
84 | window.addEventListener("resize", utils.throttle(onResize, 250), true);
85 | });
86 | }
87 |
88 | /**
89 | * Format data or generate any derived variables.
90 | */
91 | function formatData(data) {
92 | data.forEach(function(d) {
93 | d['gini'] = +d['gini'];
94 | d['pct_unionized'] = +d['pct_unionized'];
95 | });
96 |
97 | return data;
98 | }
99 |
100 | /**
101 | * Invoke on resize. By default simply rerenders the graphic.
102 | */
103 | function onResize() {
104 | render();
105 | }
106 |
107 | /**
108 | * Figure out the current frame size and render the graphic.
109 | */
110 | function render() {
111 | var width = d3.select("#interactive-content").node().getBoundingClientRect().width;
112 |
113 | if (width <= MOBILE_BREAKPOINT) {
114 | isMobile = true;
115 | } else {
116 | isMobile = false;
117 | }
118 |
119 | renderGraphic({
120 | container: '#graphic',
121 | width: width,
122 | data: graphicData
123 | });
124 |
125 | // Inform parent frame of new height
126 | fm.resize()
127 | }
128 |
129 | /*
130 | * Render the graphic.
131 | */
132 | function renderGraphic(config) {
133 | // Configuration
134 | var aspectRatio = 16 / 9;
135 |
136 | var margins = {
137 | top: 10,
138 | right: 30,
139 | bottom: 50,
140 | left: 30
141 | };
142 |
143 | // Calculate actual chart dimensions
144 | var width = config['width'];
145 | var height = width / aspectRatio;
146 |
147 | var chartWidth = width - (margins['left'] + margins['right']);
148 | var chartHeight = height - (margins['top'] + margins['bottom']);
149 |
150 | // Clear existing graphic (for redraw)
151 | var containerElement = d3.select(config['container']);
152 | containerElement.html('');
153 |
154 | // Create the root SVG element
155 | var chartWrapper = containerElement.append('div')
156 | .attr('class', 'graphic-wrapper');
157 |
158 | var chartElement = chartWrapper.append('svg')
159 | .attr('width', chartWidth + margins['left'] + margins['right'])
160 | .attr('height', chartHeight + margins['top'] + margins['bottom'])
161 | .append('g')
162 | .attr('transform', 'translate(' + margins['left'] + ',' + margins['top'] + ')');
163 |
164 | // Create scales
165 | var xScale = d3.scaleLinear()
166 | .range([0, chartWidth])
167 | .domain([0, 100]);
168 |
169 | var yScale = d3.scaleLinear()
170 | .range([chartHeight, 0])
171 | .domain([0, 60]);
172 |
173 | // Create axes
174 | var xAxis = d3.axisBottom()
175 | .scale(xScale)
176 | .ticks(5)
177 | .tickFormat(function(d) {
178 | return d.toFixed(0) + '%';
179 | })
180 |
181 | var yAxis = d3.axisLeft()
182 | .scale(yScale)
183 | .ticks(5)
184 | .tickFormat(function(d) {
185 | return d.toFixed(0);
186 | })
187 |
188 | // Render axes
189 | var xAxisElement = chartElement.append('g')
190 | .attr('class', 'x axis')
191 | .attr('transform', "translate(" + [0, chartHeight] + ")")
192 | .call(xAxis);
193 |
194 | var yAxisElement = chartElement.append('g')
195 | .attr('class', 'y axis')
196 | .call(yAxis);
197 |
198 | // Render axes grids
199 | var xAxisGrid = function() {
200 | return xAxis;
201 | };
202 |
203 | xAxisElement.append('g')
204 | .attr('class', 'x grid')
205 | .call(xAxisGrid()
206 | .tickSize(-chartHeight, 0)
207 | .tickFormat('')
208 | );
209 |
210 | var yAxisGrid = function() {
211 | return yAxis;
212 | };
213 |
214 | yAxisElement.append('g')
215 | .attr('class', 'y grid')
216 | .call(yAxisGrid()
217 | .tickSize(-chartWidth, 0)
218 | .tickFormat('')
219 | );
220 |
221 | var fitLine = d3.line()
222 | .x(function(d) {
223 | return xScale(d['pct_unionized']);
224 | })
225 | .y(function(d) {
226 | return yScale(FIT_SLOPE * d['pct_unionized'] + FIT_INTERCEPT);
227 | })
228 |
229 | chartElement.append("path")
230 | .datum(config['data'])
231 | .attr("class", "fit-line")
232 | .attr("d", fitLine);
233 |
234 | chartElement.append('g')
235 | .attr('class', 'dots')
236 | .selectAll('circle')
237 | .data(config['data'])
238 | .enter()
239 | .append('circle')
240 | .attr('r', isMobile ? 3 : 5)
241 | .attr('cx', function(d) {
242 | return xScale(d['pct_unionized']);
243 | })
244 | .attr('cy', function(d) {
245 | return yScale(d['gini']);
246 | })
247 | .attr('class', 'dot')
248 | .attr('id', function(d) {
249 | return utils.classify(d['country']);
250 | })
251 |
252 | /*
253 | * Render labels and arrows.
254 | */
255 | chartElement.append('defs')
256 | .append('marker')
257 | .attr('id','arrowhead')
258 | .attr('orient','auto')
259 | .attr('viewBox','0 0 5.108 8.18')
260 | .attr('markerHeight','8.18')
261 | .attr('markerWidth','5.108')
262 | .attr('orient','auto')
263 | .attr('refY','4.09')
264 | .attr('refX','5')
265 | .append('polygon')
266 | .attr('points','0.745,8.05 0.07,7.312 3.71,3.986 0.127,0.599 0.815,-0.129 5.179,3.999')
267 | .attr('fill','#4C4C4C')
268 |
269 | var arrowLine = d3.line()
270 | .curve(d3.curveNatural)
271 | .x(function(d) {
272 | return xScale(d[0]);
273 | })
274 | .y(function(d) {
275 | return yScale(d[1]);
276 | });
277 |
278 | var arrows = chartElement.append('g')
279 | .attr('class', 'arrows');
280 |
281 | arrows.selectAll('path')
282 | .data(ARROWS)
283 | .enter().append('path')
284 | .attr('d', function(d) { return arrowLine(d['path']); })
285 | .style('marker-end', 'url(#arrowhead)');
286 |
287 | var labels = chartElement.append('g')
288 | .attr('class', 'labels');
289 |
290 | labels.selectAll('text')
291 | .data(LABELS)
292 | .enter().append('text')
293 | .attr('x', function(d) {
294 | return xScale(d['pct_unionized']);
295 | })
296 | .attr('y', function(d) {
297 | return yScale(d['gini'])
298 | })
299 | .attr('text-anchor', function(d) {
300 | return d['text-anchor'] || LABEL_DEFAULTS['text-anchor'];
301 | })
302 | .style('alignment-baseline', function(d) {
303 | return 'middle';
304 | })
305 | .html(function(d) {
306 | return d['country'];
307 | });
308 |
309 | var annotation = chartElement.append('text')
310 | .attr('id', 'annotation')
311 | .attr('x', function(d) {
312 | return xScale(0) + 5;
313 | })
314 | .attr('y', function(d) {
315 | return yScale(60) + 2
316 | })
317 | .attr('text-anchor', 'start')
318 | .style('alignment-baseline', 'middle')
319 | .style('font-size', '16px')
320 | .html('gini coefficient of inequality');
321 |
322 | var bbox = annotation.node().getBBox();
323 |
324 | chartElement.append('rect')
325 | .attr('id', 'annotation-background')
326 | .attr('x', bbox.x)
327 | .attr('y', bbox.y)
328 | .attr('width', bbox.width + 5)
329 | .attr('height', bbox.height + 5)
330 |
331 | annotation.moveToFront();
332 |
333 | chartElement.append('text')
334 | .attr('id', 'x-label')
335 | .attr('x', chartWidth / 2)
336 | .attr('y', chartHeight + 45)
337 | .attr('text-anchor', 'middle')
338 | .html('Share of workers who are unionized')
339 | }
340 |
341 | d3.selection.prototype.moveToFront = function() {
342 | return this.each(function(){
343 | this.parentNode.appendChild(this);
344 | });
345 | };
346 |
347 | // Bind on-load handler
348 | document.addEventListener("DOMContentLoaded", function() {
349 | init();
350 | });
351 |
--------------------------------------------------------------------------------
/src/styl/normalize.styl:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.0 | MIT License | git.io/normalize */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html
10 | font-family: sans-serif // 1
11 | -ms-text-size-adjust: 100% // 2
12 | -webkit-text-size-adjust: 100% // 2
13 |
14 | /**
15 | * Remove default margin.
16 | */
17 |
18 | body
19 | margin: 0
20 |
21 | /* HTML5 display definitions
22 | ========================================================================== */
23 |
24 | /**
25 | * Correct `block` display not defined in IE 8/9.
26 | */
27 |
28 | article,
29 | aside,
30 | details,
31 | figcaption,
32 | figure,
33 | footer,
34 | header,
35 | hgroup,
36 | main,
37 | nav,
38 | section,
39 | summary
40 | display: block
41 |
42 | /**
43 | * 1. Correct `inline-block` display not defined in IE 8/9.
44 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
45 | */
46 |
47 | audio,
48 | canvas,
49 | progress,
50 | video
51 | display: inline-block // 1
52 | vertical-align: baseline // 2
53 |
54 | /**
55 | * Prevent modern browsers from displaying `audio` without controls.
56 | * Remove excess height in iOS 5 devices.
57 | */
58 |
59 | audio:not([controls])
60 | display: none
61 | height: 0
62 |
63 | /**
64 | * Address `[hidden]` styling not present in IE 8/9.
65 | * Hide the `template` element in IE, Safari, and Firefox < 22.
66 | */
67 |
68 | [hidden],
69 | template
70 | display: none
71 |
72 | /* Links
73 | ========================================================================== */
74 |
75 | /**
76 | * 1. Remove the gray background color from active links in IE 10.
77 | * 2. Improve readability when focused and also mouse hovered in all browsers.
78 | */
79 |
80 | a
81 | background: transparent // 1
82 | &:active,
83 | &:hover
84 | outline: 0 // 2
85 |
86 | /* Text-level semantics
87 | ========================================================================== */
88 |
89 | /**
90 | * Address styling not present in IE 8/9, Safari 5, and Chrome.
91 | */
92 |
93 | abbr[title]
94 | border-bottom: 1px dotted
95 |
96 | /**
97 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
98 | */
99 |
100 | b,
101 | strong
102 | font-weight: bold
103 |
104 | /**
105 | * Address styling not present in Safari 5 and Chrome.
106 | */
107 |
108 | dfn
109 | font-style: italic
110 |
111 | /**
112 | * Address variable `h1` font-size and margin within `section` and `article`
113 | * contexts in Firefox 4+, Safari 5, and Chrome.
114 | */
115 |
116 | h1
117 | font-size: 2em
118 | margin: 0.67em 0
119 |
120 | /**
121 | * Address styling not present in IE 8/9.
122 | */
123 |
124 | mark
125 | background: #ff0
126 | color: #000
127 |
128 | /**
129 | * Address inconsistent and variable font size in all browsers.
130 | */
131 |
132 | small
133 | font-size: 80%
134 |
135 | /**
136 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
137 | */
138 |
139 | sub,
140 | sup
141 | font-size: 75%
142 | line-height: 0
143 | position: relative
144 | vertical-align: baseline
145 |
146 | sup
147 | top: -0.5em
148 |
149 | sub
150 | bottom: -0.25em
151 |
152 | /* Embedded content
153 | ========================================================================== */
154 |
155 | /**
156 | * Remove border when inside `a` element in IE 8/9.
157 | */
158 |
159 | img
160 | border: 0
161 |
162 | /**
163 | * Correct overflow displayed oddly in IE 9.
164 | */
165 |
166 | svg:not(:root)
167 | overflow: hidden
168 |
169 | /* Grouping content
170 | ========================================================================== */
171 |
172 | /**
173 | * Address margin not present in IE 8/9 and Safari 5.
174 | */
175 |
176 | figure
177 | margin: 1em 40px
178 |
179 | /**
180 | * Address differences between Firefox and other browsers.
181 | */
182 |
183 | hr
184 | -moz-box-sizing: content-box
185 | box-sizing: content-box
186 | height: 0
187 |
188 | /**
189 | * Contain overflow in all browsers.
190 | */
191 |
192 | pre
193 | overflow: auto
194 |
195 | /**
196 | * Address odd `em`-unit font size rendering in all browsers.
197 | */
198 |
199 | code,
200 | kbd,
201 | pre,
202 | samp
203 | font-family: monospace, monospace
204 | font-size: 1em
205 |
206 | /* Forms
207 | ========================================================================== */
208 |
209 | /**
210 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
211 | * styling of `select`, unless a `border` property is set.
212 | */
213 |
214 | /**
215 | * 1. Correct color not being inherited.
216 | * Known issue: affects color of disabled elements.
217 | * 2. Correct font properties not being inherited.
218 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
219 | */
220 |
221 | button,
222 | input,
223 | optgroup,
224 | select,
225 | textarea
226 | color: inherit // 1
227 | font: inherit // 2
228 | margin: 0 // 3
229 |
230 | /**
231 | * Address `overflow` set to `hidden` in IE 8/9/10.
232 | */
233 |
234 | button
235 | overflow: visible
236 |
237 | /**
238 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
239 | * All other form control elements do not inherit `text-transform` values.
240 | * Correct `button` style inheritance in Firefox, IE 8+, and Opera
241 | * Correct `select` style inheritance in Firefox.
242 | */
243 |
244 | button,
245 | select
246 | text-transform: none
247 |
248 | /**
249 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
250 | * and `video` controls.
251 | * 2. Correct inability to style clickable `input` types in iOS.
252 | * 3. Improve usability and consistency of cursor style between image-type
253 | * `input` and others.
254 | */
255 |
256 | button,
257 | html input[type="button"], // 1
258 | input[type="reset"],
259 | input[type="submit"]
260 | -webkit-appearance: button // 2
261 | cursor: pointer // 3
262 |
263 | /**
264 | * Re-set default cursor for disabled elements.
265 | */
266 |
267 | button[disabled],
268 | html input[disabled]
269 | cursor: default
270 |
271 | /**
272 | * Remove inner padding and border in Firefox 4+.
273 | */
274 |
275 | button::-moz-focus-inner,
276 | input::-moz-focus-inner
277 | border: 0
278 | padding: 0
279 |
280 | /**
281 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
282 | * the UA stylesheet.
283 | */
284 |
285 | input
286 | line-height: normal
287 |
288 | /**
289 | * It's recommended that you don't attempt to style these elements.
290 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
291 | *
292 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
293 | * 2. Remove excess padding in IE 8/9/10.
294 | */
295 |
296 | input[type="checkbox"],
297 | input[type="radio"]
298 | box-sizing: border-box // 1
299 | padding: 0 // 2
300 |
301 | /**
302 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
303 | * `font-size` values of the `input`, it causes the cursor style of the
304 | * decrement button to change from `default` to `text`.
305 | */
306 |
307 | input[type="number"]::-webkit-inner-spin-button,
308 | input[type="number"]::-webkit-outer-spin-button
309 | height: auto
310 |
311 | /**
312 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
313 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
314 | * (include `-moz` to future-proof).
315 | */
316 |
317 | input[type="search"]
318 | -webkit-appearance: textfield // 1
319 | -moz-box-sizing: content-box
320 | -webkit-box-sizing: content-box // 2
321 | box-sizing: content-box
322 |
323 | /**
324 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
325 | * Safari (but not Chrome) clips the cancel button when the search input has
326 | * padding (and `textfield` appearance).
327 | */
328 |
329 | input[type="search"]::-webkit-search-cancel-button,
330 | input[type="search"]::-webkit-search-decoration
331 | -webkit-appearance: none
332 |
333 | /**
334 | * Define consistent border, margin, and padding.
335 | */
336 |
337 | fieldset
338 | border: 1px solid #c0c0c0
339 | margin: 0 2px
340 | padding: 0.35em 0.625em 0.75em
341 |
342 | /**
343 | * 1. Correct `color` not being inherited in IE 8/9.
344 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
345 | */
346 |
347 | legend
348 | border: 0 // 1
349 | padding: 0 // 2
350 |
351 | /**
352 | * Remove default vertical scrollbar in IE 8/9.
353 | */
354 |
355 | textarea
356 | overflow: auto
357 |
358 | /**
359 | * Don't inherit the `font-weight` (applied by a rule above).
360 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
361 | */
362 |
363 | optgroup
364 | font-weight: bold
365 |
366 | /* Tables ================================================================== */
367 |
368 | /**
369 | * Remove most spacing between table cells.
370 | */
371 |
372 | table
373 | border-collapse: collapse
374 | border-spacing: 0
375 |
376 | td,
377 | th
378 | padding: 0
379 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | // Node modules
2 | var browserify = require("browserify");
3 | var source = require("vinyl-source-stream");
4 | var browserSync = require("browser-sync");
5 | var reload = browserSync.reload;
6 | var nib = require("nib");
7 | var del = require("del");
8 |
9 | // Gulp-related
10 | var gulp = require("gulp");
11 | var changed = require("gulp-changed");
12 | var jade = require("gulp-jade");
13 | var preprocess = require("gulp-preprocess");
14 | var shell = require("gulp-shell");
15 | var stylus = require("gulp-stylus");
16 |
17 | // Non-gulp NPM modules
18 | var fs = require("fs");
19 | var archieml = require("archieml");
20 | var request = require('request');
21 | var http = require('http');
22 |
23 | // Local modules
24 | var args = require("./gulp/cli").parse();
25 | var config = require("./gulp/config");
26 | var utils = require("./gulp/utils");
27 |
28 | // Configuration
29 | var gdoc_id = "";
30 | var gdoc_host = "127.0.0.1:6006";
31 | var gdoc_url = "http://"+gdoc_host+"/"+ gdoc_id;
32 | var content = {};
33 |
34 | var qzdataPath = process.env.QZDATA_PATH || "~/qzdata";
35 | var thingName = "inequality-and-unions";
36 | var thingPath = qzdataPath + "/2017/inequality-and-unions";
37 |
38 | var isProd = args.build ? true : false;
39 | var preprocessOpts = {
40 | context: {
41 | ENV: isProd ? "prod" : "dev"
42 | }
43 | };
44 |
45 | var allTasks = [
46 | "get-content",
47 | "jade",
48 | "stylus",
49 | "browserify",
50 | "copy-libs",
51 | "copy-assets",
52 | "copy-fonts",
53 | "copy-data",
54 | "copy-favicon"
55 | ];
56 |
57 | var shellCmd = utils.generateShellCmd(args.build, thingPath, thingName);
58 |
59 | /**
60 | * Reads compiled ArchieML content from the local JSON file.
61 | */
62 | function readContentFromFile(doneCallback) {
63 | fs.readFile("content.json", function(err, data){
64 | if (!err) {
65 | content = JSON.parse(data);
66 | doneCallback();
67 | }
68 | else {
69 | console.log("Cannot load content from file");
70 | doneCallback(err);
71 | }
72 | });
73 | }
74 |
75 | /**
76 | * Fetch ArchieML data from Google Docs.
77 | */
78 | function getContentTask(doneCallback) {
79 | if(gdoc_id !== "") {
80 | request.get({
81 | "url": gdoc_url
82 | },
83 | function(error, resp, body) {
84 | if(!error && resp.statusCode < 400) {
85 | content = JSON.parse(body);
86 | fs.writeFileSync("content.json", body, "utf-8");
87 | doneCallback();
88 | }
89 | else {
90 | // if the server isn't up load from file
91 | if(resp && resp.statusCode >= 400) {
92 | console.log(body);
93 | }
94 |
95 | console.log("Cannot load content from server, loading from file");
96 | readContentFromFile(doneCallback);
97 | }
98 | });
99 | }
100 | else {
101 | console.log("No google doc specified, loading from file");
102 | readContentFromFile(doneCallback);
103 | }
104 | }
105 |
106 | getContentTask.description = "Fetch ArchieML data from Google Docs";
107 | gulp.task("get-content", getContentTask);
108 |
109 | /**
110 | * Compile Jade HTML templates. (Also triggers a refetch of the ArchieML doc.)
111 | */
112 | function compileJadeTask() {
113 | var context = {
114 | "content": content,
115 | "build_info": {
116 | "timestamp": (new Date()).getTime().toString()
117 | }
118 | }
119 |
120 | return gulp.src(config.paths.src.jade + "/index.jade")
121 | .pipe(jade({ pretty: true, locals: context }))
122 | .pipe(gulp.dest(config.dirs.build))
123 | .pipe(reload({ stream: true }));
124 | }
125 |
126 | compileJadeTask.description = "Compile Jade HTML templates (also triggers 'get-content')";
127 | gulp.task("jade", ["get-content"], compileJadeTask);
128 |
129 | /**
130 | * Compile Stylus CSS meta-language.
131 | */
132 | function compileStylusTask() {
133 | return gulp.src(config.paths.src.styl + "/main.styl")
134 | .pipe(stylus({
135 | use: [nib()],
136 | "include css": true,
137 | errors: true
138 | }))
139 | .pipe(gulp.dest(config.paths.build.css))
140 | .pipe(reload({ stream: true }));
141 | }
142 |
143 | compileStylusTask.description = "Compile Stylus CSS meta-language";
144 | gulp.task("stylus", compileStylusTask);
145 |
146 | /**
147 | * Bundle Javascript with browserify.
148 | */
149 | function compileJavascriptTask(doneCallback) {
150 | var bundler = browserify({
151 | entries: [config.paths.src.js + "/thing.js"],
152 | debug: !isProd
153 | });
154 |
155 | if (isProd && !args["dont-minify"]) {
156 | bundler.transform({ global: true }, "uglifyify");
157 | }
158 |
159 | return bundler
160 | .bundle()
161 | .on('error', function(err) {
162 | console.error('ERROR IN JS');
163 | console.error(err.message);
164 | doneCallback();
165 | })
166 | .pipe(source("thing.js"))
167 | .pipe(gulp.dest(config.paths.build.js))
168 | .pipe(reload({ stream: true }));
169 | }
170 |
171 | compileJavascriptTask.description = "Bundle Javascript with browserify";
172 | gulp.task("browserify", ["preprocess"], compileJavascriptTask);
173 |
174 | /**
175 | * Clear files from the build directory.
176 | */
177 | function cleanTask() {
178 | return del([
179 | config.dirs.build + "/**"
180 | ]);
181 | }
182 |
183 | cleanTask.description = "Clear files from the build directory";
184 | gulp.task("clean", cleanTask);
185 |
186 | /**
187 | * Copy Javascript libraries to build path.
188 | */
189 | function copyLibrariesTask() {
190 | return gulp.src(config.paths.src.js + "/libs/*")
191 | .pipe(gulp.dest(config.paths.build.js + "/libs"))
192 | .pipe(reload({ stream: true }));
193 | }
194 |
195 | copyLibrariesTask.description = "Copy Javascript libraries to build path";
196 | gulp.task("copy-libs", copyLibrariesTask);
197 |
198 | function copyLibrariesTask() {
199 | return gulp.src(config.paths.src.js + "/libs/*")
200 | .pipe(gulp.dest(config.paths.build.js + "/libs"))
201 | .pipe(reload({ stream: true }));
202 | }
203 |
204 | copyLibrariesTask.description = "Copy Javascript libraries to build path";
205 | gulp.task("copy-libs", copyLibrariesTask);
206 |
207 | /**
208 | * Copy static assets to build path.
209 | */
210 | function copyAssetsTask() {
211 | return gulp.src(config.paths.src.assets + "/**")
212 | .pipe(gulp.dest(config.paths.build.assets))
213 | .pipe(reload({ stream: true }));
214 | }
215 |
216 | copyAssetsTask.description = "Copy static assets to build path";
217 | gulp.task("copy-assets", copyAssetsTask);
218 |
219 | function copyFaviconTask() {
220 | return gulp.src("./src/favicon.ico")
221 | .pipe(gulp.dest("./build"))
222 | .pipe(reload({ stream: true }));
223 | }
224 |
225 | copyAssetsTask.description = "Copy favicon to build path";
226 | gulp.task("copy-favicon", copyFaviconTask);
227 |
228 | /**
229 | * Copy font files to build path.
230 | */
231 | function copyFontsTask() {
232 | return gulp.src(config.paths.src.fonts + "/**")
233 | .pipe(gulp.dest(config.paths.build.fonts))
234 | .pipe(reload({ stream: true }));
235 | }
236 |
237 | copyFontsTask.description = "Copy font files to build path";
238 | gulp.task("copy-fonts", copyFontsTask);
239 |
240 | /**
241 | * Copy data files to build path.
242 | */
243 | function copyDataTask() {
244 | return gulp.src(config.paths.src.data + "/**")
245 | .pipe(gulp.dest(config.paths.build.data))
246 | .pipe(reload({ stream: true }));
247 | }
248 |
249 | copyDataTask.description = "Copy data files to build path"
250 | gulp.task("copy-data", copyDataTask);
251 |
252 | /**
253 | * Writes environment configuration variables to config.js and puts it in
254 | * the build directory.
255 | */
256 | function preprocessTask() {
257 | return gulp.src([config.paths.src.js + "/config.js"])
258 | .pipe(preprocess(preprocessOpts))
259 | .pipe(gulp.dest(config.paths.build.js));
260 | }
261 |
262 | preprocessTask.description = "Preprocess dev/prod conditional code";
263 | gulp.task("preprocess", preprocessTask);
264 |
265 | /**
266 | * Watches project files for changes and runs the appropriate copy/compile
267 | * tasks.
268 | */
269 | function watchTask(done) {
270 | gulp.watch(config.paths.src.libs + "/**", ["copy-libs"]);
271 | gulp.watch(config.paths.src.fonts + "/**", ["copy-fonts"]);
272 | gulp.watch(config.paths.src.data + "/**", ["copy-data"]);
273 | gulp.watch(config.paths.src.assets + "/**", ["copy-assets"]);
274 | gulp.watch(config.paths.src.js + "/**", ["browserify"]);
275 | gulp.watch(config.paths.src.styl + "/**", ["stylus"]);
276 | gulp.watch(config.paths.src.jade + "/**", ["jade"]);
277 | done();
278 | }
279 |
280 | watchTask.description = "Watch local files for changes and re-build as necessary."
281 | gulp.task("watch", allTasks, watchTask);
282 |
283 | /**
284 | * Starts the browsersync server.
285 | */
286 | function browserSyncTask() {
287 | browserSync({
288 | server: {
289 | baseDir: "build"
290 | },
291 | open: false
292 | });
293 | }
294 |
295 | browserSyncTask.description = "Serve the built project using BrowserSync";
296 | gulp.task("browser-sync", ["watch"], browserSyncTask);
297 |
298 | /**
299 | * Other tasks
300 | */
301 | gulp.task("shell", allTasks, shell.task(shellCmd));
302 | gulp.task("build", ["shell"]);
303 |
304 | if (args.build) {
305 | gulp.task("default", ["clean"], function () {
306 | gulp.start("build");
307 | });
308 | } else {
309 | gulp.task("default", ["clean"], function () {
310 | gulp.start("browser-sync");
311 | });
312 | }
313 |
--------------------------------------------------------------------------------