├── .gitignore ├── Challenges.md ├── Challenges_solutions.md ├── LICENSE ├── README.md ├── pictures ├── 00A_libs.png ├── 00B_Series1.png ├── 00B_Series2.png ├── 00B_Series3.png ├── 00C_Main1.png ├── 00C_Main2.png ├── 00C_Main3.png ├── 02_Chart_Bar_Padding.png ├── 02_Chart_Original.png ├── 02_vertical.png ├── 03_twolines.png └── 06_pie.png └── samples ├── 00 nvd3 ├── 00 Lines │ ├── data.js │ ├── index.html │ ├── main.js │ └── readme.md └── 01 BubbleChart │ ├── data.js │ ├── index.html │ ├── main.js │ └── readme.md ├── 01 basics-web ├── 00 HTML │ ├── index.html │ └── readme.md ├── 01 CSS │ ├── index.html │ ├── readme.md │ └── styles.css ├── 02 DOM │ ├── index.html │ ├── main.js │ ├── readme.md │ └── styles.css ├── 03 DOM 2 │ ├── index.html │ ├── main.js │ └── readme.md ├── 04 SVG │ ├── index.html │ ├── readme.md │ └── styles.css └── 05 SVG 2 │ ├── index.html │ ├── readme.md │ └── styles.css ├── 02 Charts ├── 00 SVG │ ├── index.html │ ├── readme.md │ └── styles.css ├── 02 BarChart │ ├── index.html │ ├── main.js │ └── readme.md ├── 03 BarChartAxis │ ├── index.html │ ├── main.js │ ├── readme.md │ └── styles.css ├── 04 BarChartRefactor │ ├── data.js │ ├── index.html │ ├── main.js │ └── styles.css ├── 05 Lines │ ├── data.js │ ├── index.html │ ├── main.js │ └── styles.css ├── 06 Pie │ ├── data.js │ ├── index.html │ ├── main.js │ └── styles.css └── 07 Refresh │ ├── data.csv │ ├── index.html │ ├── main.js │ └── styles.css ├── 03 Sleek Charts └── 01 Bubble Chart │ ├── .babelrc │ ├── base.webpack.config.js │ ├── data │ └── StatsPerCountry.txt │ ├── dev.webpack.config.js │ ├── fonts │ └── rajdhani │ │ ├── Rajdhani-Bold.ttf │ │ ├── Rajdhani-Light.ttf │ │ ├── Rajdhani-Medium.ttf │ │ ├── Rajdhani-Regular.ttf │ │ └── Rajdhani-SemiBold.ttf │ ├── package.json │ ├── prod.webpack.config.js │ └── src │ ├── app.js │ ├── bubbleChart.js │ ├── bubbleChartStyles.scss │ ├── delimitedDataParser.js │ ├── index.html │ └── pageStyles.scss └── 04 maps ├── 00 world_basic ├── index.html ├── main.js └── readme.md ├── 01 world_interaction ├── d3-tip.js ├── index.html ├── main.js ├── package-lock.json ├── package.json ├── readme.md ├── world_countries.json └── world_population.tsv ├── 02 spain ├── ccaa.json ├── index.html ├── main.js ├── municipios.json ├── package.json └── readme.md └── 03 others └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist/ 4 | *.orig 5 | .idea/ -------------------------------------------------------------------------------- /Challenges.md: -------------------------------------------------------------------------------- 1 | # 02 Charts / 02 BarChart 2 | 3 | 1) Add a padding between each of the chart bars. 4 | 5 | ![No padding](./pictures/02_Chart_Original.png "Chart Original") 6 | ![Bar Padding](./pictures/02_Chart_Bar_Padding.png "Chart Padding") 7 | 8 | 2) What about adding some color to each bar, based on the product Id ? 9 | 10 | Some tips: 11 | 12 | - We can treat product category as an ordinal. 13 | 14 | - We can create a function that returns a color based on 15 | an existing d3 SchemeCategory 16 | 17 | ```javascript 18 | // helper that returns a color based on an ID 19 | var barColor = d3.scaleOrdinal(d3.schemeCategory10); 20 | ``` 21 | 22 | The React it self has an attribute called filled, we can 23 | attach to it a _function(d)_ extract from it the current product 24 | and invoke the color function we have previously created. 25 | 26 | 3) Let's rotate 90º the bar chart, we want it to show it like: 27 | 28 | ![Vertical](./pictures/02_vertical.png "Chart Vertical") 29 | 30 | If you need some help, a similar sample can be found: 31 | 32 | [Blocks Sample](http://bl.ocks.org/d3noob/8952219) 33 | 34 | # 02 Charts / 05 Lines 35 | 36 | 4) Add one more line to the chart, this line will represent 37 | a set of expenses. 38 | 39 | ![TwoLines](./pictures/03_twolines.png "Two lines") 40 | 41 | # 02 Charts / 06 Pie 42 | 43 | 4) Create a Doughnut like chart. 44 | 45 | ![Doughnut](./pictures/06_pie.png "Doughnut") 46 | 47 | 5) Make the pie chart scale and take all the available canvas space, plus include margin for legend. 48 | -------------------------------------------------------------------------------- /Challenges_solutions.md: -------------------------------------------------------------------------------- 1 | # 02 Charts / 02 BarChart 2 | 3 | 1) Add a padding between each of the chart bars. 4 | 5 | ![No padding](./pictures/02_Chart_Original.png "Chart Original") 6 | ![Bar Padding](./pictures/02_Chart_Bar_Padding.png "Chart Padding") 7 | 8 | **Solution** 9 | 10 | ```diff 11 | newRects.append('rect') 12 | .attr('x', x(0)) 13 | .attr('y', function(d, i) { 14 | return y(d.product); 15 | }) 16 | - .attr('height', y.bandwidth) 17 | + .attr('height', function(d, i) { 18 | + return y.bandwidth() - 5; 19 | + }) 20 | .attr('width', function(d, i) { 21 | return x(d.sales); 22 | }); 23 | 24 | ``` 25 | 26 | 2) What about adding some color to each bar, based on the product Id ? 27 | 28 | Some tips: 29 | 30 | - We can treat product category as an ordinal. 31 | 32 | - We can create a function that returns a color based on 33 | an existing d3 SchemeCategory 34 | 35 | ```javascript 36 | // helper that returns a color based on an ID 37 | var barColor = d3.scaleOrdinal(d3.schemeCategory10); 38 | ``` 39 | 40 | The React it self has an attribute called filled, we can 41 | attach to it a _function(d)_ extract from it the current product 42 | and invoke the color function we have previously created. 43 | 44 | **Solution** 45 | 46 | ```diff 47 | + // helper that returns a color based on an ID 48 | + var barColor = d3.scaleOrdinal(d3.schemeCategory10); 49 | 50 | 51 | newRects.append('rect') 52 | .attr('x', x(0)) 53 | .attr('y', function(d, i) { 54 | return y(d.product); 55 | }) 56 | .attr('height', y.bandwidth) 57 | .attr('width', function(d, i) { 58 | return x(d.sales); 59 | }) 60 | + .attr('fill', function(d) { 61 | + return barColor(d.product); 62 | + }); 63 | ``` 64 | 65 | 3) Let's rotate 90º the bar chart, we want it to show it like: 66 | 67 | ![Vertical](./pictures/02_vertical.png "Chart Vertical") 68 | 69 | First let's swap X Scale and Y Scale 70 | 71 | 72 | ```javascript 73 | 74 | let margin = null, 75 | width = null, 76 | height = null; 77 | 78 | let svg = null; 79 | let x, y = null; // scales 80 | 81 | setupCanvasSize(); 82 | appendSvg("body"); 83 | setupXScale(); 84 | setupYScale(); 85 | appendXAxis(); 86 | appendYAxis(); 87 | appendChartBars(); 88 | 89 | // 1. let's start by selecting the SVG Node 90 | function setupCanvasSize() { 91 | margin = {top: 100, left: 180, bottom: 120, right: 130}; 92 | width = 960 - margin.left - margin.right; 93 | height = 800 - margin.top - margin.bottom; 94 | } 95 | 96 | function appendSvg(domElement) { 97 | svg = d3.select(domElement).append("svg") 98 | .attr("width", width + margin.left + margin.right) 99 | .attr("height", height + margin.top + margin.bottom) 100 | .append("g") 101 | .attr("transform", 102 | "translate(" + margin.left + "," + margin.top + ")"); 103 | ; 104 | } 105 | 106 | 107 | function setupXScale() 108 | { 109 | x = d3.scaleBand() 110 | .rangeRound([0, width]) 111 | .domain(totalSales.map(function(d, i) { 112 | return d.product; 113 | })); 114 | 115 | } 116 | 117 | function setupYScale() 118 | { 119 | var maxSales = d3.max(totalSales, function(d, i) { 120 | return d.sales; 121 | }); 122 | 123 | y = d3.scaleLinear() 124 | .range([height,0]) 125 | .domain([0, maxSales]); 126 | } 127 | 128 | function appendXAxis() { 129 | // Add the X Axis 130 | svg.append("g") 131 | .attr("transform", "translate(0," + height + ")") 132 | .call(d3.axisBottom(x)); 133 | } 134 | 135 | function appendYAxis() { 136 | // 137 | // Add the Y Axis 138 | svg.append("g") 139 | 140 | .call(d3.axisLeft(y)); 141 | } 142 | 143 | function appendChartBars() 144 | { 145 | // 2. Now let's select all the rectangles inside that svg 146 | // (right now is empty) 147 | var rects = svg.selectAll('rect') 148 | .data(totalSales); 149 | 150 | // Now it's time to append to the list of Rectangles we already have 151 | var newRects = rects.enter(); 152 | 153 | 154 | newRects.append('rect') 155 | .attr('x', function(d, i) { 156 | return x(d.product); 157 | }) 158 | .attr('y', function(d) { 159 | return y(d.sales); 160 | }) 161 | .attr('height', function(d, i) { 162 | return height - y(d.sales); 163 | }) 164 | .attr('width', x.bandwidth) 165 | ; 166 | 167 | } 168 | ``` 169 | 170 | **There's a second solution that you can try... what about keeping the chart 171 | as it was original and just rotate it?** 172 | 173 | # 02 Charts / 05 Lines 174 | 175 | 4) Add one more line to the chart, this line will represent 176 | a set of expenses. 177 | 178 | ![TwoLines](./pictures/03_twolines.png "Two lines") 179 | 180 | Let's start by adding a new style for the new line (styles.css) 181 | 182 | ```css 183 | .lineB { 184 | fill: none; 185 | stroke: red; 186 | stroke-width: 2px; 187 | } 188 | ``` 189 | 190 | We will add a new line of data 191 | 192 | ```javascript 193 | var totalExpenses = [ 194 | { month: new Date(2016,10, 01), sales: 3500 }, 195 | { month: new Date(2016,11, 01), sales: 2400 }, 196 | { month: new Date(2016,12, 01), sales: 1500 }, 197 | { month: new Date(2017,1, 01), sales: 6000 }, 198 | { month: new Date(2017,2, 01), sales: 4500 }, 199 | ]; 200 | ``` 201 | 202 | > For the sake of simplicity we will keep the same dates (as an additional excercise, what should 203 | you take care if we have different date ranges? we should combine min and max for the X timeline). 204 | 205 | Next step let's add the new line 206 | 207 | ```javascript 208 | function appendLineCharts() 209 | { 210 | // (...) 211 | var expenseline = d3.line() 212 | .x(function(d) { return x(d.month); }) 213 | .y(function(d) { return y(d.expense); }); 214 | 215 | // Add the valueline path. 216 | svg.append("path") 217 | .data([totalExpenses]) 218 | .attr("class", "lineB") 219 | .attr("d", expenseline); 220 | } 221 | ``` 222 | 223 | # 02 Charts / 06 Pie 224 | 225 | 4) Create a Doughnut like chart. 226 | 227 | ![Doughnut](./pictures/06_pie.png "Doughnut") 228 | 229 | You only need to play with the innerRadius property: 230 | 231 | ```javascript 232 | // Pie chart size 233 | var arc = d3.arc() 234 | .innerRadius(20) 235 | .outerRadius(50); 236 | ``` 237 | 238 | 5) Make the pie chart scale and take all the available canvas space, plus include margin for legend. 239 | 240 | ```diff 241 | function setupCanvasSize() { 242 | margin = {top: 0, left: 80, bottom: 20, right: 30}; 243 | + width = 760 - margin.left - margin.right; 244 | + height = 660 - margin.top - margin.bottom; 245 | } 246 | 247 | function appendPieChart() 248 | { 249 | // Where to get the measure data 250 | var pie = d3.pie() 251 | .value(function(d) { return d.sales }) 252 | 253 | // Calculate Arcs 254 | var slices = pie(totalSales); 255 | 256 | // Pie chart size 257 | var arc = d3.arc() 258 | .innerRadius(0) 259 | + .outerRadius(height / 2); 260 | 261 | + var positionX = width / 2; 262 | + var positionY = height / 2; 263 | 264 | // Draw the pie 265 | svg.selectAll('path.slice') 266 | .data(slices) 267 | .enter() 268 | .append('path') 269 | .attr('class', 'slice') 270 | .attr('d', arc) 271 | .attr('fill', function(d) { 272 | return color(d.data.product); 273 | }) 274 | + .attr("transform", `translate(${positionX}, ${positionY})`) 275 | ; 276 | } 277 | 278 | ``` 279 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Braulio Diez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # d3js-samples 2 | 3 | Simple d3js (v. 4) samples: 4 | - Basic HTML / CSS sample (intro to developers that are NOOB to web development). 5 | - Basic d3js charts samples: 6 | - Consuming third partie libraries. 7 | - Creating barchart from scratch. 8 | - Creating a pie chart. 9 | - Working with maps (guide included). 10 | 11 | # About Basefactor + Lemoncode 12 | 13 | We are an innovating team of Javascript experts, passionate about turning your ideas into robust products. 14 | 15 | [Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services. 16 | 17 | [Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services. 18 | 19 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 20 | 21 | 22 | -------------------------------------------------------------------------------- /pictures/00A_libs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/pictures/00A_libs.png -------------------------------------------------------------------------------- /pictures/00B_Series1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/pictures/00B_Series1.png -------------------------------------------------------------------------------- /pictures/00B_Series2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/pictures/00B_Series2.png -------------------------------------------------------------------------------- /pictures/00B_Series3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/pictures/00B_Series3.png -------------------------------------------------------------------------------- /pictures/00C_Main1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/pictures/00C_Main1.png -------------------------------------------------------------------------------- /pictures/00C_Main2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/pictures/00C_Main2.png -------------------------------------------------------------------------------- /pictures/00C_Main3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/pictures/00C_Main3.png -------------------------------------------------------------------------------- /pictures/02_Chart_Bar_Padding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/pictures/02_Chart_Bar_Padding.png -------------------------------------------------------------------------------- /pictures/02_Chart_Original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/pictures/02_Chart_Original.png -------------------------------------------------------------------------------- /pictures/02_vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/pictures/02_vertical.png -------------------------------------------------------------------------------- /pictures/03_twolines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/pictures/03_twolines.png -------------------------------------------------------------------------------- /pictures/06_pie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/pictures/06_pie.png -------------------------------------------------------------------------------- /samples/00 nvd3/00 Lines/data.js: -------------------------------------------------------------------------------- 1 | function sinAndCos() { 2 | var sin = [], 3 | sin2 = [], 4 | cos = [], 5 | rand = [], 6 | rand2 = [] 7 | ; 8 | for (var i = 0; i < 100; i++) { 9 | sin.push({x: i, y: i % 10 == 5 ? null : Math.sin(i/10) }); //the nulls are to show how defined works 10 | sin2.push({x: i, y: Math.sin(i/5) * 0.4 - 0.25}); 11 | cos.push({x: i, y: .5 * Math.cos(i/10)}); 12 | rand.push({x:i, y: Math.random() / 10}); 13 | rand2.push({x: i, y: Math.cos(i/10) + Math.random() / 10 }) 14 | } 15 | return [ 16 | { 17 | area: true, 18 | values: sin, 19 | key: "Sine Wave", 20 | color: "#ff7f0e", 21 | strokeWidth: 4, 22 | classed: 'dashed' 23 | }, 24 | { 25 | values: cos, 26 | key: "Cosine Wave", 27 | color: "#2ca02c" 28 | }, 29 | { 30 | values: rand, 31 | key: "Random Points", 32 | color: "#2222ff" 33 | }, 34 | { 35 | values: rand2, 36 | key: "Random Cosine", 37 | color: "#667711", 38 | strokeWidth: 3.5 39 | }, 40 | { 41 | area: true, 42 | values: sin2, 43 | key: "Fill opacity", 44 | color: "#EF9CFB", 45 | fillOpacity: .1 46 | } 47 | ]; 48 | } 49 | -------------------------------------------------------------------------------- /samples/00 nvd3/00 Lines/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /samples/00 nvd3/00 Lines/main.js: -------------------------------------------------------------------------------- 1 | 2 | // Wrapping in nv.addGraph allows for '0 timeout render', stores rendered charts in nv.graphs, and may do more in the future... it's NOT required 3 | var chart; 4 | var data; 5 | var legendPosition = "top"; 6 | 7 | nv.addGraph(function() { 8 | chart = nv.models.lineChart() 9 | .options({ 10 | duration: 300, 11 | useInteractiveGuideline: true 12 | }) 13 | ; 14 | // chart sub-models (ie. xAxis, yAxis, etc) when accessed directly, return themselves, not the parent chart, so need to chain separately 15 | chart.xAxis 16 | .axisLabel("Time (s)") 17 | .tickFormat(d3.format(',.1f')) 18 | .staggerLabels(true) 19 | ; 20 | chart.yAxis 21 | .axisLabel('Voltage (v)') 22 | .tickFormat(function(d) { 23 | if (d == null) { 24 | return 'N/A'; 25 | } 26 | return d3.format(',.2f')(d); 27 | }) 28 | ; 29 | data = sinAndCos(); 30 | d3.select('#chart1').append('svg') 31 | .datum(data) 32 | .call(chart); 33 | nv.utils.windowResize(chart.update); 34 | return chart; 35 | }); 36 | -------------------------------------------------------------------------------- /samples/00 nvd3/00 Lines/readme.md: -------------------------------------------------------------------------------- 1 | # Consuming third partie libraries 2 | 3 | Let's get a quick start, sometimes we just need to deliver a chart and use it as a blackbox, we can just make use of an open source library, if 4 | we are lucky and get exactly what we need can just use the chart as a black box. 5 | 6 | In this case we will make use of _nvD3_ open source chart library: 7 | 8 | http://nvd3.org/ 9 | 10 | # Steps to reproduce the sample 11 | 12 | - Let's first create a simple index.html file: 13 | 14 | ```html 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |

Sample Chart

23 | 24 | 25 | ``` 26 | 27 | - So far so good, we got a very basic HTML file working (double click on it and you will get some text displayed in your browser), 28 | the next step is to reffer the nvd3 library, to make it simpler: 29 | - We are gong to link it via CDN (content delivery network). 30 | - We will import the library code + styles. 31 | 32 | - ¿ Where I can get the links to this styles + library files? Right in the nvd3.org site: 33 | 34 | ![Download Libraries and styles](../../../pictures/00A_libs.png "Download Libraries and styles") 35 | 36 | ```diff 37 | 38 |

Sample Chart

39 | 40 | + 41 | + 42 | + 43 | 44 | ``` 45 | 46 | - The next step is to go to one of the samples, let's click on the "View More Samples" option in the main page, 47 | and click on the _Simple Line Chart_ option. 48 | 49 | - First we need to add the data, in this sample we are creating three series (lines) of data, just to create some random test data we are just generating values based on sine and cosine math funcions. Later on we will simplifiy this. 50 | 51 | Let's first define this: 52 | 53 | _data.js_ 54 | 55 | ```javascript 56 | function sinAndCos() { 57 | var sin = [], 58 | sin2 = [], 59 | cos = [], 60 | rand = [], 61 | rand2 = [] 62 | ; 63 | for (var i = 0; i < 100; i++) { 64 | sin.push({x: i, y: i % 10 == 5 ? null : Math.sin(i/10) }); //the nulls are to show how defined works 65 | sin2.push({x: i, y: Math.sin(i/5) * 0.4 - 0.25}); 66 | cos.push({x: i, y: .5 * Math.cos(i/10)}); 67 | rand.push({x:i, y: Math.random() / 10}); 68 | rand2.push({x: i, y: Math.cos(i/10) + Math.random() / 10 }) 69 | } 70 | return [ 71 | { 72 | area: true, 73 | values: sin, 74 | key: "Sine Wave", 75 | color: "#ff7f0e", 76 | strokeWidth: 4, 77 | classed: 'dashed' 78 | }, 79 | { 80 | values: cos, 81 | key: "Cosine Wave", 82 | color: "#2ca02c" 83 | }, 84 | { 85 | values: rand, 86 | key: "Random Points", 87 | color: "#2222ff" 88 | }, 89 | { 90 | values: rand2, 91 | key: "Random Cosine", 92 | color: "#667711", 93 | strokeWidth: 3.5 94 | }, 95 | { 96 | area: true, 97 | values: sin2, 98 | key: "Fill opacity", 99 | color: "#EF9CFB", 100 | fillOpacity: .1 101 | } 102 | ]; 103 | } 104 | ``` 105 | 106 | - Let's spend some time analyzing this file content. 107 | 108 | Function: 109 | 110 | ![Function](../../../pictures/00B_Series1.png "Function") 111 | 112 | Data generation: 113 | 114 | ![Data generation](../../../pictures/00B_Series2.png "Data Generation") 115 | 116 | Series generation (data + style): 117 | 118 | ![Series (data + style)](../../../pictures/00B_Series3.png "Series (data + style)") 119 | 120 | 121 | And let's reference this new file into our index.html 122 | 123 | _index.html_ 124 | 125 | ```diff 126 | 127 |
128 | 129 | 130 | 131 | 132 | + 133 | 134 | 135 | ``` 136 | 137 | - Now it's time to instantiate the chart. 138 | 139 | - First we will create a place holder where the chart will be displayed: 140 | 141 | ```diff 142 | 143 |

Sample Chart

144 | +
145 | 146 | 147 | ``` 148 | 149 | - Now we will add the chart instantiation, code below (we will break it down into pieces as we made with the data file). 150 | 151 | _main.js_ 152 | 153 | ```javascript 154 | // Wrapping in nv.addGraph allows for '0 timeout render', stores rendered charts in nv.graphs, and may do more in the future... it's NOT required 155 | var chart; 156 | var data; 157 | var legendPosition = "top"; 158 | 159 | nv.addGraph(function() { 160 | chart = nv.models.lineChart() 161 | .options({ 162 | duration: 300, 163 | useInteractiveGuideline: true 164 | }) 165 | ; 166 | // chart sub-models (ie. xAxis, yAxis, etc) when accessed directly, return themselves, not the parent chart, so need to chain separately 167 | chart.xAxis 168 | .axisLabel("Time (s)") 169 | .tickFormat(d3.format(',.1f')) 170 | .staggerLabels(true) 171 | ; 172 | chart.yAxis 173 | .axisLabel('Voltage (v)') 174 | .tickFormat(function(d) { 175 | if (d == null) { 176 | return 'N/A'; 177 | } 178 | return d3.format(',.2f')(d); 179 | }) 180 | ; 181 | data = sinAndCos(); 182 | d3.select('#chart1').append('svg') 183 | .datum(data) 184 | .call(chart); 185 | nv.utils.windowResize(chart.update); 186 | return chart; 187 | }); 188 | ``` 189 | 190 | And let's reference this new file into our index.html 191 | 192 | _index.html_ 193 | 194 | ```diff 195 | 196 |

Sample Chart

197 |
198 | 199 | 200 | 201 | 202 | + 203 | 204 | 205 | ``` 206 | 207 | - Let's chop the content of this file and try to understand it bit by bit. 208 | 209 | Instantianting the chart 210 | 211 | ![Chart definition](../../../pictures/00C_Main1.png "Chart definition") 212 | 213 | Setting up xAxis and YAxis 214 | 215 | ![Setting up xAxis and YAxis](../../../pictures/00C_Main2.png "Setting up xAxis and YAxis") 216 | 217 | Instantiate the chart 218 | 219 | ![Instantiate the chart](../../../pictures/00C_Main2.png "Instantiate the chart") 220 | 221 | > Excercise now is time to play with real data 222 | 223 | #Excercise 224 | 225 | Let's check population data from this site: 226 | 227 | https://datahub.io/core/population#resource-population 228 | 229 | We can download the json source (Data Files >> download json (1MB)) 230 | 231 | ## Tips 232 | 233 | Let's substitute serie by serie, some tips to progress with this excercise (just only read them if you get stucked): 234 | 235 | ### Obtaining data 236 | 237 | - About obtaining the json source: 238 | - Download it form this url: https://pkgstore.datahub.io/core/population/population_json/data/43d34c2353cbd16a0aa8cadfb193af05/population_json.json 239 | - The easiest way to access it for this sampe: copy it and paste it in a file named _country-data.js_ and assign it to a variable 240 | 241 | 242 | _./country-data.js_ 243 | 244 | ```javascript 245 | var countryData = [{ "Country Code": "ARB", "Country Name": "Arab World", "Value": 92490932.0, "Year": 1960 }, { "Country C... 246 | ``` 247 | 248 | - Then do not forget to reference it in your HTML file: 249 | 250 | _./index.html_ 251 | 252 | ```diff 253 | 254 | 255 | + 256 | 257 | 258 | ``` 259 | 260 | ### Transforming data 261 | 262 | You will need to filter by country, e.g. to filter by spain 263 | 264 | ```javascript 265 | var spain = countryData 266 | .filter(data => data["Country Code"] == "ESP") 267 | ``` 268 | You will need to transform the data from the current structure to a pair [X,Y] 269 | 270 | ```javascript 271 | var spain = countryData 272 | .filter(data => data["Country Code"] == "ESP") 273 | .map(data => ({x: data.Year, y: data.Value})) 274 | ; 275 | ``` 276 | 277 | Now you are ready to start loading the rest of series and substitue what is in the return in the _data.js_ file. 278 | 279 | The next step is to customize the X axis and Y axis, you only need to update _main.js_ 280 | 281 | The final step, the Y chart looks a bit cluttered, let's divided the value by 1000000 and add a "M" to each label 282 | 283 | ```javascript 284 | chart.yAxis 285 | .axisLabel('Population') 286 | .tickFormat(function(d) { 287 | const formatted = Math.trunc(d/1000000) + "M"; 288 | return formatted; 289 | }) 290 | ``` 291 | 292 | -------------------------------------------------------------------------------- /samples/00 nvd3/01 BubbleChart/data.js: -------------------------------------------------------------------------------- 1 | function randomData(groups, points) { //# groups,# points per group 2 | // smiley and thin-x are our custom symbols! 3 | var data = [], 4 | shapes = ['thin-x', 'circle', 'cross', 'triangle-up', 'triangle-down', 'diamond', 'square'], 5 | random = d3.random.normal(); 6 | 7 | for (i = 0; i < groups; i++) { 8 | data.push({ 9 | key: 'Group ' + i, 10 | values: [] 11 | }); 12 | 13 | for (j = 0; j < points; j++) { 14 | data[i].values.push({ 15 | x: random(), 16 | y: random(), 17 | size: Math.round(Math.random() * 100) / 100, 18 | shape: shapes[j % shapes.length] 19 | }); 20 | } 21 | } 22 | 23 | return data; 24 | } 25 | -------------------------------------------------------------------------------- /samples/00 nvd3/01 BubbleChart/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 31 | 32 | 33 | 34 | 35 |
36 | 37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /samples/00 nvd3/01 BubbleChart/main.js: -------------------------------------------------------------------------------- 1 | // register our custom symbols to nvd3 2 | // make sure your path is valid given any size because size scales if the chart scales. 3 | nv.utils.symbolMap.set('thin-x', function(size) { 4 | size = Math.sqrt(size); 5 | return 'M' + (-size/2) + ',' + (-size/2) + 6 | 'l' + size + ',' + size + 7 | 'm0,' + -(size) + 8 | 'l' + (-size) + ',' + size; 9 | }); 10 | 11 | // create the chart 12 | var chart; 13 | nv.addGraph(function() { 14 | chart = nv.models.scatterChart() 15 | .showDistX(true) 16 | .showDistY(true) 17 | .useVoronoi(true) 18 | .color(d3.scale.category10().range()) 19 | .duration(300) 20 | ; 21 | 22 | chart.xAxis.tickFormat(d3.format('.02f')); 23 | chart.yAxis.tickFormat(d3.format('.02f')); 24 | 25 | d3.select('#test1 svg') 26 | .datum(randomData(4,40)) 27 | .call(chart); 28 | 29 | nv.utils.windowResize(chart.update); 30 | 31 | return chart; 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /samples/00 nvd3/01 BubbleChart/readme.md: -------------------------------------------------------------------------------- 1 | # nvd3 Bubble Chart 2 | 3 | Let's finish our third parties libraries startup by creating a simple bubble chart (nvd3). 4 | 5 | # Steps to reproduce the sample 6 | 7 | This time we will just create the HTML in one go (you can find a step by step detailed guide in the previous sample). 8 | 9 | > This time we will include directly all the libs, plus data and main js files, we are including as well an SVG shape in 10 | the div container 11 | 12 | _./index.html_ 13 | 14 | ```html 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 45 | 46 | 47 | 48 | 49 |
50 | 51 |
52 | 53 | 54 | 55 | ``` 56 | 57 | - Now it's time to go through defining the data: 58 | - We are going to define an array of shapes (one of the shapes will be custom). 59 | - The we are going to add random values + shapes, in a loop following the format: 60 | 61 | ```json 62 | { 63 | x: number, 64 | y: number, 65 | size: number, 66 | shape: string 67 | } 68 | ``` 69 | 70 | _./data.js_ 71 | 72 | ```javascript 73 | function randomData(groups, points) { //# groups,# points per group 74 | // smiley and thin-x are our custom symbols! 75 | var data = [], 76 | shapes = ['thin-x', 'circle', 'cross', 'triangle-up', 'triangle-down', 'diamond', 'square'], 77 | random = d3.random.normal(); 78 | 79 | for (i = 0; i < groups; i++) { 80 | data.push({ 81 | key: 'Group ' + i, 82 | values: [] 83 | }); 84 | 85 | for (j = 0; j < points; j++) { 86 | data[i].values.push({ 87 | x: random(), 88 | y: random(), 89 | size: Math.round(Math.random() * 100) / 100, 90 | shape: shapes[j % shapes.length] 91 | }); 92 | } 93 | } 94 | 95 | return data; 96 | } 97 | ``` 98 | 99 | - Moving into the defining chart section, first let's define a custom shape (svg). 100 | 101 | _./main.js_ 102 | 103 | ```javascript 104 | // register our custom symbols to nvd3 105 | // make sure your path is valid given any size because size scales if the chart scales. 106 | nv.utils.symbolMap.set('thin-x', function(size) { 107 | size = Math.sqrt(size); 108 | return 'M' + (-size/2) + ',' + (-size/2) + 109 | 'l' + size + ',' + size + 110 | 'm0,' + -(size) + 111 | 'l' + (-size) + ',' + size; 112 | }); 113 | ``` 114 | 115 | - Let's add our AddGraph funnction: 116 | 117 | _./main.js_ 118 | 119 | ```javascript 120 | // create the chart 121 | var chart; 122 | nv.addGraph(function() { 123 | }); 124 | ``` 125 | 126 | - Let's define the chart 127 | 128 | _./main.js_ 129 | 130 | ```diff 131 | // create the chart 132 | var chart; 133 | nv.addGraph(function() { 134 | + chart = nv.models.scatterChart() 135 | + .showDistX(true) 136 | + .showDistY(true) 137 | + .useVoronoi(true) 138 | + .color(d3.scale.category10().range()) 139 | + .duration(300) 140 | + ; 141 | }); 142 | ``` 143 | 144 | - Let's provide the tick format to X and Y axis. 145 | 146 | ```diff 147 | var chart; 148 | nv.addGraph(function () { 149 | chart = nv.models.scatterChart() 150 | .showDistX(true) 151 | .showDistY(true) 152 | .useVoronoi(true) 153 | .color(d3.scale.category10().range()) 154 | .duration(300) 155 | ; 156 | 157 | + chart.xAxis.tickFormat(d3.format('.02f')); 158 | + chart.yAxis.tickFormat(d3.format('.02f')); 159 | }); 160 | ``` 161 | 162 | - Now let's instantiate the chart and place it in the svg placeholder that we have previously defined in the html, 163 | we are hooking as well to windowsResize to update the chart layout whenever the browser window changes it's width or height. 164 | 165 | ```diff 166 | // create the chart 167 | var chart; 168 | nv.addGraph(function () { 169 | chart = nv.models.scatterChart() 170 | .showDistX(true) 171 | .showDistY(true) 172 | .useVoronoi(true) 173 | .color(d3.scale.category10().range()) 174 | .duration(300) 175 | ; 176 | 177 | chart.xAxis.tickFormat(d3.format('.02f')); 178 | chart.yAxis.tickFormat(d3.format('.02f')); 179 | 180 | + d3.select('#test1 svg') 181 | + .datum(randomData(4,40)) 182 | + .call(chart); 183 | 184 | + nv.utils.windowResize(chart.update); 185 | 186 | + return chart; 187 | }); 188 | ``` 189 | 190 | > Excercise: Let's customize our scattered chart, entry data (Ice Cream Sales vs Temperature): 191 | 192 | | Temperature | Sales | 193 | | ------------- |:-------:| 194 | | 14.2 C | $215 | 195 | | 16.4 C | $325 | 196 | | 11.9 C | $185 | 197 | | 15.2 C | $332 | 198 | | 18.5 C | $406 | 199 | | 22.1 C | $522 | 200 | | 19.4 C | $412 | 201 | | 25.1 C | $614 | 202 | | 23.4 C | $544 | 203 | | 18.1 C | $421 | 204 | | 22.6 C | $445 | 205 | | 17.2 C | $408 | 206 | 207 | Challenges: 208 | 209 | - Create the right JSON structure. 210 | - Enrich the chart using shapes (e.g. if a given entry is above 400$ use a shape if it's below 200$ another one). 211 | - Display the data 212 | - Adjust X and Y Axis 213 | 214 | 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /samples/01 basics-web/00 HTML/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TITLE GOES HERE 6 | 7 | 8 |

9 | MAIN CONTENT GOES HERE 10 |

11 | 12 |

13 | HTML Basics tutorial 14 |

15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/01 basics-web/00 HTML/readme.md: -------------------------------------------------------------------------------- 1 | # Basic HTML 2 | 3 | Let's start creating a very basic HTML file. 4 | 5 | # Steps 6 | 7 | - Let's create a very basic structure 8 | 9 | ```html 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ``` 18 | 19 | Let's check each tag: 20 | 21 | _DocType_ 22 | 23 | ```html 24 | 25 | ``` 26 | 27 | The declaration must be the very first thing in your HTML document, before the tag. 28 | 29 | The declaration is not an HTML tag; it is an instruction to the web browser about what version of HTML the page is written in. 30 | 31 | More info: https://www.w3schools.com/tags/tag_doctype.asp 32 | 33 | _html_ 34 | 35 | The tag tells the browser that this is an HTML document. 36 | 37 | The tag represents the root of an HTML document. 38 | 39 | The tag is the container for all other HTML elements (except for the tag). 40 | 41 | More info: https://www.w3schools.com/tags/tag_html.asp 42 | 43 | _head_ 44 | 45 | The element is a container for all the head elements. 46 | 47 | The element can include a title for the document, scripts, styles, meta information, and more. 48 | 49 | More info: https://www.w3schools.com/tags/tag_head.asp 50 | 51 | _meta_ 52 | 53 | Metadata is data (information) about data. 54 | 55 | ```html 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ``` 64 | 65 | The tag provides metadata about the HTML document. Metadata will not be displayed on the page, but will be machine parsable. 66 | 67 | Meta elements are typically used to specify page description, keywords, author of the document, last modified, and other metadata. 68 | 69 | More info: https://www.w3schools.com/tags/tag_meta.asp 70 | 71 | _body_ 72 | 73 | The tag defines the document's body. 74 | 75 | The element contains all the contents of an HTML document, such as text, hyperlinks, images, tables, lists, etc. 76 | 77 | ```html 78 | 79 |

80 | MAIN CONTENT GOES HERE 81 |

82 | 83 |

84 | HTML Basics tutorial 85 |

86 | 87 | ``` 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /samples/01 basics-web/01 CSS/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Normal paragraph

11 | 12 |

Red paragraph

13 |
14 | 15 |
    16 |
  1. Unique element
  2. 17 |
  3. Another list element
  4. 18 |
  5. 19 |

    Paragraph inside list element

    20 |

    Second paragraph

    21 |
  6. 22 |
23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /samples/01 basics-web/01 CSS/readme.md: -------------------------------------------------------------------------------- 1 | # CSS basics 2 | 3 | Let's create a very basic sample and play a bit with CSS 4 | 5 | # Sample 6 | 7 | - Let's start by creating our basic _index.html_ file 8 | 9 | _index.html_ 10 | 11 | ```html 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ``` 20 | 21 | - Let's create an empty filed called _styles.css_ 22 | 23 | - Now it's time to define the metadata content in the _head_ section. 24 | 25 | ```diff 26 | 27 | 28 | 29 | + 30 | + 31 | + 32 | 33 | 34 | 35 | 36 | ``` 37 | 38 | We have just set the chartset to UTF8 (to avoid issues with tilde and addition characters), and imported the 39 | _styles.css_ file that we have previously created. 40 | 41 | - Next step we are going to add normal paragraph to the body section. 42 | 43 | ```diff 44 | 45 | +
46 | +

Normal paragraph

47 | +
48 | 49 | ``` 50 | 51 | - Now let's add a new paragraph that will use a class that will place the background in red. 52 | 53 | _./styles.css_ 54 | 55 | ```css 56 | /* Applied to all tags with the class "red" */ 57 | .red { 58 | background: red; 59 | } 60 | ``` 61 | 62 | _./index.html_ 63 | 64 | ```diff 65 | 66 |
67 |

Normal paragraph

68 | 69 | +

Red paragraph

70 |
71 | ``` 72 | 73 | - Now let's cover a bit more complex scenario: 74 | 75 | ```html 76 |
    77 |
  1. Unique element
  2. 78 |
  3. Another list element
  4. 79 |
  5. 80 |

    Paragraph inside list element

    81 |

    Second paragraph

    82 |
  6. 83 |
84 | ``` 85 | 86 | Let's say we want to highlight in green all the paragrapsh that are inside an li element. 87 | 88 | _styles.css_ 89 | 90 | ```css 91 | /* Applied only to

tags that are inside

  • tags */ 92 | li p { 93 | color: #0C0; 94 | } 95 | ``` 96 | 97 | - And now we want to highlight an specific element (italic, by id). 98 | 99 | _styles.css_ 100 | 101 | ```css 102 | /* Applied to the tag with the id "some-id" */ 103 | #some-id { 104 | font-style: italic; 105 | } 106 | ``` 107 | 108 | _index.html_ 109 | 110 | ```html 111 |
      112 |
    1. Unique element
    2. 113 |
    3. Another list element
    4. 114 |
    5. 115 |

      Paragraph inside list element

      116 |

      Second paragraph

      117 |
    6. 118 |
    119 | ``` 120 | 121 | - Ok, we learnt something... but the result was quite ugly, let's add some nice design. 122 | Let's remove the current list, and create a new one 123 | 124 | ```diff 125 | -
      126 | -
    1. Unique element
    2. 127 | -
    3. Another list element
    4. 128 | -
    5. 129 | -

      Paragraph inside list element

      130 | -

      Second paragraph

      131 | -
    6. 132 | -
    133 | + 142 | ``` 143 | 144 | - From the CSS let's remove the styles we have created and add the following 145 | 146 | ```diff 147 | - /* Applied to all tags with the class "red" */ 148 | - .red { 149 | - background: red; 150 | - } 151 | 152 | - /* Applied to the tag with the id "some-id" */ 153 | - #some-id { 154 | - font-style: italic; 155 | - } 156 | 157 | - /* Applied only to

    tags that are inside

  • tags */ 158 | - li p { 159 | - color: #0C0; 160 | - } 161 | 162 | + h2 { 163 | + font: 400 40px/1.5 Helvetica, Verdana, sans-serif; 164 | + margin: 0; 165 | + padding: 0; 166 | + } 167 | + 168 | + ul { 169 | + list-style-type: none; 170 | + margin: 0; 171 | + padding: 0; 172 | + } 173 | + 174 | + li { 175 | + font: 200 20px/1.5 Helvetica, Verdana, sans-serif; 176 | + border-bottom: 1px solid #ccc; 177 | + } 178 | + 179 | + li:last-child { 180 | + border: none; 181 | + } 182 | + 183 | + li a { 184 | + text-decoration: none; 185 | + color: #000; 186 | + display: block; 187 | + width: 100%; 188 | + 189 | + -webkit-transition: font-size 0.3s ease, background-color 0.3s ease; 190 | + -moz-transition: font-size 0.3s ease, background-color 0.3s ease; 191 | + -o-transition: font-size 0.3s ease, background-color 0.3s ease; 192 | + -ms-transition: font-size 0.3s ease, background-color 0.3s ease; 193 | + transition: font-size 0.3s ease, background-color 0.3s ease; 194 | +} 195 | + 196 | +li a:hover { 197 | + font-size: 30px; 198 | + background: #f6f6f6; 199 | +} 200 | ``` 201 | 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /samples/01 basics-web/01 CSS/styles.css: -------------------------------------------------------------------------------- 1 | /* Applied to all

    tags */ 2 | p { 3 | color: blue; 4 | } 5 | 6 | /* Applied to all tags with the class "red" */ 7 | .red { 8 | background: red; 9 | } 10 | 11 | /* Applied to the tag with the id "some-id" */ 12 | #some-id { 13 | font-style: italic; 14 | } 15 | 16 | /* Applied only to

    tags that are inside

  • tags */ 17 | li p { 18 | color: #0C0; 19 | } 20 | -------------------------------------------------------------------------------- /samples/01 basics-web/02 DOM/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |

    Normal paragraph

    11 | 12 |

    Red paragraph

    13 |
    14 | 15 |
      16 |
    1. Unique element
    2. 17 |
    3. Another list element
    4. 18 |
    5. 19 |

      Paragraph inside list element

      20 |

      Second paragraph

      21 |
    6. 22 |
    23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /samples/01 basics-web/02 DOM/main.js: -------------------------------------------------------------------------------- 1 | // DOM API 2 | document.getElementById('some-id'); 3 | //
  • Unique element
  • 4 | document.getElementsByTagName('p').length; 5 | // 4 6 | var reds = document.getElementsByClassName('red'); 7 | // [

    Red paragraph

    ] 8 | reds[0].innerText 9 | // "Red paragraph" 10 | 11 | // D3 Selection API 12 | d3.select('p').size(); // select() only finds one 13 | // 1 14 | d3.selectAll('p').size(); // selectAll() finds all 15 | // 4 16 | var reds = d3.selectAll('.red'); 17 | // [ > Array[1] ] 18 | reds.text(); 19 | // "Red paragraph" 20 | -------------------------------------------------------------------------------- /samples/01 basics-web/02 DOM/readme.md: -------------------------------------------------------------------------------- 1 | # Playing with selectors 2 | 3 | d3js implements a powerful set of selectors, they allows us to manipulate the existing DOM. 4 | 5 | # Sample 6 | 7 | In this sample we are going to learn how to play with the chrome debugger and how selectors work. 8 | 9 | Starting from the previous sample (right before we added the cool styled li). 10 | 11 | ```html 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
    21 |

    Normal paragraph

    22 | 23 |

    Red paragraph

    24 |
    25 | 26 |
      27 |
    1. Unique element
    2. 28 |
    3. Another list element
    4. 29 |
    5. 30 |

      Paragraph inside list element

      31 |

      Second paragraph

      32 |
    6. 33 |
    34 | 35 | 36 | 37 | 38 | ``` 39 | 40 | ```css 41 | /* Applied to all

    tags */ 42 | p { 43 | color: blue; 44 | } 45 | 46 | /* Applied to all tags with the class "red" */ 47 | .red { 48 | background: red; 49 | } 50 | 51 | /* Applied to the tag with the id "some-id" */ 52 | #some-id { 53 | font-style: italic; 54 | } 55 | 56 | /* Applied only to

    tags that are inside

  • tags */ 57 | li p { 58 | color: #0C0; 59 | } 60 | ``` 61 | 62 | - Let's crate a _main.js_ file and add the follwing selectors. 63 | 64 | ```javascript 65 | // DOM API 66 | document.getElementById('some-id'); 67 | //
  • Unique element
  • 68 | document.getElementsByTagName('p').length; 69 | // 4 70 | var reds = document.getElementsByClassName('red'); 71 | // [

    Red paragraph

    ] 72 | reds[0].innerText 73 | // "Red paragraph" 74 | 75 | // D3 Selection API 76 | d3.select('p').size(); // select() only finds one 77 | // 1 78 | d3.selectAll('p').size(); // selectAll() finds all 79 | // 4 80 | var reds = d3.selectAll('.red'); 81 | // [ > Array[1] ] 82 | reds.text(); 83 | // "Red paragraph" 84 | ``` 85 | 86 | - Now let's click on the index file and press F12, we are going to start debugging the samples. 87 | 88 | -------------------------------------------------------------------------------- /samples/01 basics-web/02 DOM/styles.css: -------------------------------------------------------------------------------- 1 | /* Applied to all

    tags */ 2 | p { 3 | color: blue; 4 | } 5 | 6 | /* Applied to all tags with the class "red" */ 7 | .red { 8 | background: red; 9 | } 10 | 11 | /* Applied to the tag with the id "some-id" */ 12 | #some-id { 13 | font-style: italic; 14 | } 15 | 16 | /* Applied only to

    tags that are inside

  • tags */ 17 | li p { 18 | color: #0C0; 19 | } 20 | -------------------------------------------------------------------------------- /samples/01 basics-web/03 DOM 2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

    10 | Click on me! 11 |

    12 | 13 |

    14 | Hover over me! 15 |

    16 | 17 |

    18 | OK now hover over here! 19 |

    20 | 21 |

    22 | Hover here too! 23 |

    24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /samples/01 basics-web/03 DOM 2/main.js: -------------------------------------------------------------------------------- 1 | // DOM API 2 | var clickMe = document.getElementById('click-me'); 3 | clickMe.onclick = function() { 4 | if (this.style.backgroundColor) { 5 | this.style.backgroundColor = ''; 6 | } else { 7 | this.style.backgroundColor = 'red'; 8 | } 9 | } 10 | 11 | // D3 Selection API. Note: it attaches the 12 | // callbacks to each element in the selection 13 | d3.selectAll('.hover-me') 14 | .on('mouseover', function() { 15 | this.style.backgroundColor = 'yellow'; 16 | }) 17 | .on('mouseleave', function() { 18 | this.style.backgroundColor = ''; 19 | }); 20 | -------------------------------------------------------------------------------- /samples/01 basics-web/03 DOM 2/readme.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | 3 | Let's play now with events and javascript. 4 | 5 | # Sample 6 | 7 | Let's create the following HTML 8 | 9 | ```html 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

    19 | Click on me! 20 |

    21 | 22 | 23 | 24 | 25 | 26 | ``` 27 | 28 | - Now on the javascript side let's create a _main.js_ file and add the following hook to react 29 | when the user clicks on the h1 tag with the id "click-me". This time we will use standard DOM access. 30 | 31 | _main.js_ 32 | 33 | ```javascript 34 | var clickMe = document.getElementById('click-me'); 35 | clickMe.onclick = function() { 36 | if (this.style.backgroundColor) { 37 | this.style.backgroundColor = ''; 38 | } else { 39 | this.style.backgroundColor = 'red'; 40 | } 41 | } 42 | ``` 43 | 44 | - Now let's add to the HTML some paragraphs with a given class. 45 | 46 | _index.html_ 47 | 48 | ```javascript 49 |

    50 | Hover over me! 51 |

    52 | 53 |

    54 | OK now hover over here! 55 |

    56 | 57 |

    58 | Hover here too! 59 |

    60 | ``` 61 | 62 | - Let's jump to the HTML and using d3 selectores let's react mouse over on any of this elements. 63 | 64 | _main.js_ 65 | 66 | ```javascript 67 | // D3 Selection API. Note: it attaches the 68 | // callbacks to each element in the selection 69 | d3.selectAll('.hover-me') 70 | .on('mouseover', function() { 71 | this.style.backgroundColor = 'yellow'; 72 | }) 73 | .on('mouseleave', function() { 74 | this.style.backgroundColor = ''; 75 | }); 76 | ``` -------------------------------------------------------------------------------- /samples/01 basics-web/04 SVG/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 18 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /samples/01 basics-web/04 SVG/readme.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | 3 | Let's start playing with vector graphics, we will use the SVG HTML standard. 4 | 5 | # Steps 6 | 7 | - Let's start by creating an index.html 8 | 9 | _index.html_ 10 | 11 | ```html 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ``` 23 | 24 | - Now let's create an SVG container 25 | 26 | _index.html_ 27 | 28 | ```diff 29 | 30 | + 31 | + 32 | 33 | ``` 34 | 35 | - Let's draw a circle 36 | 37 | _index.html_ 38 | 39 | ```diff 40 | 41 | + 42 | 43 | ``` 44 | 45 | - SVG elements support CSS styling (altough some class names are different to what we are used): 46 | 47 | _styles.css_ 48 | 49 | ```css 50 | .red { 51 | fill: red; /* not background-color! */ 52 | } 53 | ``` 54 | 55 | _index.html_ 56 | 57 | ```diff 58 | 59 | 60 | + 61 | 62 | 63 | 64 | ``` 65 | 66 | - Let's paint some styles rectangles. 67 | 68 | ```diff 69 | .red { 70 | fill: red; /* not background-color! */ 71 | } 72 | 73 | + .fancy { 74 | + fill: none; 75 | + stroke: black; /* similar to border-color */ 76 | + stroke-width: 3pt; /* similar to border-width */ 77 | + stroke-dasharray: 3,5,10; 78 | + } 79 | ``` 80 | 81 | ```diff 82 | 83 | 84 | 85 | 86 | 87 | + 89 | + 91 | + 93 | 94 | ``` 95 | 96 | -------------------------------------------------------------------------------- /samples/01 basics-web/04 SVG/styles.css: -------------------------------------------------------------------------------- 1 | .red { 2 | fill: red; /* not background-color! */ 3 | } 4 | 5 | .fancy { 6 | fill: none; 7 | stroke: black; /* similar to border-color */ 8 | stroke-width: 3pt; /* similar to border-width */ 9 | stroke-dasharray: 3,5,10; 10 | } 11 | -------------------------------------------------------------------------------- /samples/01 basics-web/05 SVG 2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Howdy! 4 | 5 | 6 | 7 | 10 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/01 basics-web/05 SVG 2/readme.md: -------------------------------------------------------------------------------- 1 | # Path and transformations 2 | 3 | Let's play a bit with paths and transformation. 4 | 5 | # Steps 6 | 7 | - Let's define our initial HTML 8 | 9 | ```html 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ``` 23 | 24 | - In SVG we can create a group of element and apply transformations (translations, rotations...), let' draw some text. 25 | 26 | _index.html_ 27 | 28 | ```diff 29 | 30 | + 31 | + Howdy! 32 | + 33 | 34 | ``` 35 | 36 | - One interesting element is the polygon path: 37 | 38 | ```html 39 | 40 | 43 | 45 | 46 | ``` 47 | 48 | - You can as well close the curve: 49 | 50 | ```html 51 | 52 | 54 | 55 | 56 | ``` 57 | 58 | > Let's play with the transformations, one of the things we can do is to rotate the shape: 59 | 60 | https://developer.mozilla.org/es/docs/Web/SVG/Attribute/transform -------------------------------------------------------------------------------- /samples/01 basics-web/05 SVG 2/styles.css: -------------------------------------------------------------------------------- 1 | .red { 2 | fill: red; /* not background-color! */ 3 | } 4 | 5 | .fancy { 6 | fill: none; 7 | stroke: black; /* similar to border-color */ 8 | stroke-width: 3pt; /* similar to border-width */ 9 | stroke-dasharray: 3,5,10; 10 | } 11 | -------------------------------------------------------------------------------- /samples/02 Charts/00 SVG/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | $10 27 | $80 28 | 29 | 30 | 31 | January 2014 32 | April 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /samples/02 Charts/00 SVG/readme.md: -------------------------------------------------------------------------------- 1 | # Trying to create a chart with no helpers 2 | 3 | Now that we got some basic concepts on SVG, it's a good excercise to try to create 4 | a chart without using any helper, we will notice it's a cumbersome task, and that 5 | it would be greate to have a helper library that covers all the boiler plate (e.g. d3js). 6 | 7 | # Steps 8 | 9 | - Let's first create the HTML 10 | 11 | ```html 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ``` 25 | 26 | - Now let's move a bit the chart we want to display to add some margins. 27 | 28 | ```diff 29 | 30 | 31 | + 32 | + 33 | 34 | 35 | ``` 36 | 37 | - Now let's draw the dots with the data (we are manually calculating where it should be placed) 38 | 39 | ```diff 40 | 41 | 42 | 43 | + 50 | + 51 | + 52 | + 53 | + 54 | 55 | 56 | ``` 57 | 58 | - And finally let's add X and Y axis. 59 | 60 | ```diff 61 | 62 | 63 | 64 | 65 | 66 | + 67 | + 68 | + $10 69 | + $80 70 | + 71 | + 72 | + 73 | + January 2014 74 | + April 75 | + 76 | 77 | 78 | ``` 79 | 80 | This approach is not ideal, one of the main pain points is when you try to resize the chart, you will have to recalculate all entries. -------------------------------------------------------------------------------- /samples/02 Charts/00 SVG/styles.css: -------------------------------------------------------------------------------- 1 | .red { 2 | fill: red; /* not background-color! */ 3 | } 4 | 5 | .fancy { 6 | fill: none; 7 | stroke: black; /* similar to border-color */ 8 | stroke-width: 3pt; /* similar to border-width */ 9 | stroke-dasharray: 3,5,10; 10 | } 11 | -------------------------------------------------------------------------------- /samples/02 Charts/02 BarChart/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/02 Charts/02 BarChart/main.js: -------------------------------------------------------------------------------- 1 | var totalSales = [ 2 | { product: 'Hoodie', sales: 7 }, 3 | { product: 'Jacket', sales: 6 }, 4 | { product: 'Snuggie', sales: 9 }, 5 | ]; 6 | 7 | 8 | // 1. let's start by selecting the SVG Node 9 | var svg = d3.select('svg'); 10 | 11 | // 2. Now let's select all the rectangles inside that svg 12 | // (right now is empty) 13 | var rects = svg.selectAll('rect') 14 | .data(totalSales); 15 | 16 | 17 | // 3. In order to calculate the max width for the X axis 18 | // on the bar chart, we need to know the max sales value we are going 19 | // to show. 20 | 21 | var maxSales = d3.max(totalSales, function(d, i) { 22 | return d.sales; 23 | }); 24 | 25 | // Now on the X axis we want to map totalSales values to 26 | // pixels 27 | // in this case we map the canvas range 0..350, to 0...maxSales 28 | // domain == data (data from 0 to maxSales) boundaries 29 | // ** Tip: let's play with [0, 350] values 30 | var x = d3.scaleLinear() 31 | .range([0, 350]) 32 | .domain([0, maxSales]); 33 | 34 | // Now we don't have a linear range of values, we have a discrete 35 | // range of values (one per product) 36 | // Here we are generating an array of product names 37 | // ** Tip: let's play with [0, 75] values 38 | var y = d3.scaleBand() 39 | .rangeRound([0, 75]) 40 | .domain(totalSales.map(function(d, i) { 41 | return d.product; 42 | })); 43 | 44 | // Now it's time to append to the list of Rectangles we already have 45 | var newRects = rects.enter(); 46 | 47 | // Let's append a new Rectangles 48 | // UpperCorner: 49 | // Starting x position, the start from the axis 50 | // Starting y position, where the product starts on the y scale 51 | // React width and height: 52 | // height: the space assign for each entry (product) on the Y axis 53 | // width: Now that we have the mapping previously done (linear) 54 | // we just pass the sales and use the X axis conversion to 55 | // get the right value 56 | newRects.append('rect') 57 | .attr('x', x(0)) 58 | .attr('y', function(d, i) { 59 | return y(d.product); 60 | }) 61 | .attr('height', y.bandwidth) 62 | .attr('width', function(d, i) { 63 | return x(d.sales); 64 | }); 65 | -------------------------------------------------------------------------------- /samples/02 Charts/02 BarChart/readme.md: -------------------------------------------------------------------------------- 1 | # Barchart starting with d3js 2 | 3 | Creating a chart from scratch it's a painful process let's see what d3js offers to us to simplify 4 | this process. 5 | 6 | # Steps 7 | 8 | - First let's create the basic HTML, in this case we will create just the SVG container. 9 | 10 | _./index.html_ 11 | 12 | ```html 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ``` 27 | 28 | - Let's define the data we want to display (sales by item): 29 | 30 | _main.js_ 31 | 32 | ```javascript 33 | var totalSales = [ 34 | { product: 'Hoodie', sales: 7 }, 35 | { product: 'Jacket', sales: 6 }, 36 | { product: 'Snuggie', sales: 9 }, 37 | ]; 38 | ``` 39 | 40 | - It's time to use a selector and selct the _svg_ node that we have created in the HTML: 41 | 42 | _main.js_ 43 | 44 | ```javascript 45 | // 1. let's start by selecting the SVG Node 46 | var svg = d3.select('svg'); 47 | ``` 48 | 49 | - Now let's select all the rectangles inside that areas (right now this selection is empty) 50 | 51 | ```javascript 52 | // 2. Now let's select all the rectangles inside that svg 53 | // (right now is empty) 54 | var rects = svg.selectAll('rect') 55 | .data(totalSales); 56 | ``` 57 | 58 | - In order to calculate the max width for the X axis on the bar chart, we need to know the max sales value we are going 59 | to show. 60 | 61 | ```javascript 62 | // 3. In order to calculate the max width for the X axis 63 | // on the bar chart, we need to know the max sales value we are going 64 | // to show. 65 | 66 | var maxSales = d3.max(totalSales, function(d, i) { 67 | return d.sales; 68 | }); 69 | ``` 70 | 71 | - Now on the X axis we want to map totalSales values to pixels 72 | in this case we map the canvas range 0..350, to 0...maxSales 73 | domain == data (data from 0 to maxSales) boundaries 74 | ** Tip: let's play with [0, 350] values 75 | 76 | ```javascript 77 | // Now on the X axis we want to map totalSales values to 78 | // pixels 79 | // in this case we map the canvas range 0..350, to 0...maxSales 80 | // domain == data (data from 0 to maxSales) boundaries 81 | // ** Tip: let's play with [0, 350] values 82 | var x = d3.scaleLinear() 83 | .range([0, 350]) 84 | .domain([0, maxSales]); 85 | ``` 86 | 87 | - Now we don't have a linear range of values, we have a discrete 88 | range of values (one per product), Here we are generating an array of product names 89 | ** Tip: let's play with [0, 75] values 90 | 91 | ```javascript 92 | // Now we don't have a linear range of values, we have a discrete 93 | // range of values (one per product) 94 | // Here we are generating an array of product names 95 | // ** Tip: let's play with [0, 75] values 96 | var y = d3.scaleBand() 97 | .rangeRound([0, 75]) 98 | .domain(totalSales.map(function(d, i) { 99 | return d.product; 100 | })); 101 | ``` 102 | 103 | - Now it's time to append to the list of Rectangles we already have 104 | 105 | ```javascript 106 | // Now it's time to append to the list of Rectangles we already have 107 | var newRects = rects.enter(); 108 | ``` 109 | 110 | - Let's append a new Rectangles 111 | UpperCorner: 112 | Starting x position, the start from the axis 113 | Starting y position, where the product starts on the y scale 114 | React width and height: 115 | height: the space assign for each entry (product) on the Y axis 116 | width: Now that we have the mapping previously done (linear) 117 | we just pass the sales and use the X axis conversion to 118 | get the right value 119 | 120 | 121 | ```javascript 122 | newRects.append('rect') 123 | .attr('x', x(0)) 124 | .attr('y', function(d, i) { 125 | return y(d.product); 126 | }) 127 | .attr('height', y.bandwidth) 128 | .attr('width', function(d, i) { 129 | return x(d.sales); 130 | }); 131 | ``` 132 | 133 | > **Excercise**: Let's try to this dynamic, we dont' want to stick to a given resolution, steps: 134 | 135 | 1. Create a function where you determine the size of the canvas as parameters. 136 | 2. Get the SVG widht and height and maximize size. 137 | 138 | tips 139 | 140 | - Wrap the create chart in a function: 141 | 142 | ```javascript 143 | function drawBarchChart(width, height) { 144 | ``` 145 | 146 | - Get the svg width and height 147 | 148 | ```javascript 149 | var svg = d3.select('svg'); 150 | drawBarcChart(svg._groups[0][0].clientWidth, svg._groups[0][0].clientHeight); 151 | ``` 152 | 153 | 154 | -------------------------------------------------------------------------------- /samples/02 Charts/03 BarChartAxis/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/02 Charts/03 BarChartAxis/main.js: -------------------------------------------------------------------------------- 1 | var totalSales = [ 2 | { product: 'Hoodie', sales: 7 }, 3 | { product: 'Jacket', sales: 6 }, 4 | { product: 'Snuggie', sales: 9 }, 5 | ]; 6 | 7 | 8 | // 1. let's start by selecting the SVG Node 9 | var margin = {top: 0, left: 80, bottom: 20, right: 0}; 10 | var width = 960 - margin.left - margin.right; 11 | var height = 120 - margin.top - margin.bottom; 12 | 13 | var svg = d3.select("body").append("svg") 14 | .attr("width", width + margin.left + margin.right) 15 | .attr("height", height + margin.top + margin.bottom) 16 | .append("g") 17 | .attr("transform", 18 | "translate(" + margin.left + "," + margin.top + ")"); 19 | ; 20 | 21 | var barChartsGroup = svg.append("g"); 22 | 23 | //barChartsGroup.attr("transform", "translate(" + margin.left + ",0)"); 24 | 25 | // 2. Now let's select all the rectangles inside that svg 26 | // (right now is empty) 27 | var rects = barChartsGroup.selectAll('rect') 28 | .data(totalSales); 29 | 30 | 31 | // 3. In order to calculate the max width for the X axis 32 | // on the bar chart, we need to know the max sales value we are going 33 | // to show. 34 | 35 | var maxSales = d3.max(totalSales, function(d, i) { 36 | return d.sales; 37 | }); 38 | 39 | // Now on the X axis we want to map totalSales values to 40 | // pixels 41 | // in this case we map the canvas range 0..350, to 0...maxSales 42 | // domain == data (data from 0 to maxSales) boundaries 43 | // ** Tip: let's play with [0, 350] values 44 | var x = d3.scaleLinear() 45 | .range([0, 350]) 46 | .domain([0, maxSales]); 47 | 48 | // Now we don't have a linear range of values, we have a discrete 49 | // range of values (one per product) 50 | // Here we are generating an array of product names 51 | // ** Tip: let's play with [0, 75] values 52 | var y = d3.scaleBand() 53 | .rangeRound([0, height]) 54 | .domain(totalSales.map(function(d, i) { 55 | return d.product; 56 | })); 57 | 58 | 59 | 60 | // Now it's time to append to the list of Rectangles we already have 61 | var newRects = rects.enter(); 62 | 63 | // Let's append a new Rectangles 64 | // UpperCorner: 65 | // Starting x position, the start from the axis 66 | // Starting y position, where the product starts on the y scale 67 | // React width and height: 68 | // height: the space assign for each entry (product) on the Y axis 69 | // width: Now that we have the mapping previously done (linear) 70 | // we just pass the sales and use the X axis conversion to 71 | // get the right value 72 | newRects.append('rect') 73 | .attr('x', x(0)) 74 | .attr('y', function(d, i) { 75 | return y(d.product); 76 | }) 77 | .attr('height', y.bandwidth) 78 | .attr('width', function(d, i) { 79 | return x(d.sales); 80 | }); 81 | 82 | // Add the X Axis 83 | svg.append("g") 84 | .attr("transform", "translate(0,"+ height +")") 85 | .call(d3.axisBottom(x)); 86 | 87 | // Add the Y Axis 88 | svg.append("g") 89 | .call(d3.axisLeft(y)); 90 | -------------------------------------------------------------------------------- /samples/02 Charts/03 BarChartAxis/readme.md: -------------------------------------------------------------------------------- 1 | # Setting up chart axis 2 | 3 | In this sample we will set the X and Y axis. 4 | 5 | We will start from scratch adn later on we will refactor the bar chart. 6 | 7 | # Steps 8 | 9 | - This time we will create the HTML with no SVG tag (we will inject it via javascript). 10 | 11 | _./index.html_ 12 | 13 | ```html 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ``` 27 | 28 | - Let's add the sales data. 29 | 30 | _./main.js_ 31 | 32 | ```javascript 33 | var totalSales = [ 34 | { product: 'Hoodie', sales: 7 }, 35 | { product: 'Jacket', sales: 6 }, 36 | { product: 'Snuggie', sales: 9 }, 37 | ]; 38 | ``` 39 | 40 | - Let's calculate the size of the new chart and add some margins 41 | 42 | _./main.js_ 43 | 44 | ```javascript 45 | var margin = {top: 0, left: 80, bottom: 20, right: 0}; 46 | var width = 960 - margin.left - margin.right; 47 | var height = 120 - margin.top - margin.bottom; 48 | ``` 49 | 50 | - Let's add the SVG element with the given new width and height, and translate the origins applying the margins. 51 | 52 | _./main.js_ 53 | 54 | ```javascript 55 | var svg = d3.select("body").append("svg") 56 | .attr("width", width + margin.left + margin.right) 57 | .attr("height", height + margin.top + margin.bottom) 58 | .append("g") 59 | .attr("transform", 60 | "translate(" + margin.left + "," + margin.top + ")"); 61 | ; 62 | 63 | var barChartsGroup = svg.append("g"); 64 | ``` 65 | 66 | - Now let's select all the rectangles inside that svg 67 | 68 | _./main.js_ 69 | 70 | ```javascript 71 | // 2. Now let's select all the rectangles inside that svg 72 | // (right now is empty) 73 | var rects = barChartsGroup.selectAll('rect') 74 | .data(totalSales); 75 | ``` 76 | 77 | - In order to calculate the max width for the X axis 78 | on the bar chart, we need to know the max sales value we are going 79 | to show. 80 | 81 | _./main.js_ 82 | 83 | ```javascript 84 | var maxSales = d3.max(totalSales, function(d, i) { 85 | return d.sales; 86 | }); 87 | ``` 88 | 89 | - Now on the X axis we want to map totalSales values to pixels 90 | in this case we map the canvas range 0..width, to 0...maxSales 91 | domain == data (data from 0 to maxSales) boundaries 92 | 93 | ```javascript 94 | var x = d3.scaleLinear() 95 | .range([0, width]) 96 | .domain([0, maxSales]); 97 | ``` 98 | 99 | - Now we don't have a linear range of values, we have a discrete 100 | range of values (one per product) Here we are generating an array of product names 101 | 102 | ```javascript 103 | var y = d3.scaleBand() 104 | .rangeRound([0, height]) 105 | .domain(totalSales.map(function(d, i) { 106 | return d.product; 107 | })); 108 | ``` 109 | 110 | - Now it's time to append to the list of Rectangles we already have. 111 | 112 | ```javascript 113 | // Now it's time to append to the list of Rectangles we already have 114 | var newRects = rects.enter(); 115 | 116 | // Let's append a new Rectangles 117 | // UpperCorner: 118 | // Starting x position, the start from the axis 119 | // Starting y position, where the product starts on the y scale 120 | // React width and height: 121 | // height: the space assign for each entry (product) on the Y axis 122 | // width: Now that we have the mapping previously done (linear) 123 | // we just pass the sales and use the X axis conversion to 124 | // get the right value 125 | newRects.append('rect') 126 | .attr('x', x(0)) 127 | .attr('y', function(d, i) { 128 | return y(d.product); 129 | }) 130 | .attr('height', y.bandwidth) 131 | .attr('width', function(d, i) { 132 | return x(d.sales); 133 | }); 134 | ``` 135 | 136 | - Let's add the X and Y axis. 137 | 138 | ```javascript 139 | // Add the X Axis 140 | svg.append("g") 141 | .attr("transform", "translate(0,"+ height +")") 142 | .call(d3.axisBottom(x)); 143 | 144 | // Add the Y Axis 145 | svg.append("g") 146 | .call(d3.axisLeft(y)); 147 | ``` 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /samples/02 Charts/03 BarChartAxis/styles.css: -------------------------------------------------------------------------------- 1 | .axis text { 2 | font: 10px sans-serif; 3 | } 4 | 5 | .axis path, 6 | .axis line { 7 | fill: none; 8 | stroke: #000; 9 | shape-rendering: crispEdges; 10 | } 11 | -------------------------------------------------------------------------------- /samples/02 Charts/04 BarChartRefactor/data.js: -------------------------------------------------------------------------------- 1 | var totalSales = [ 2 | { product: 'Hoodie', sales: 7 }, 3 | { product: 'Jacket', sales: 6 }, 4 | { product: 'Snuggie', sales: 9 }, 5 | ]; 6 | -------------------------------------------------------------------------------- /samples/02 Charts/04 BarChartRefactor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/02 Charts/04 BarChartRefactor/main.js: -------------------------------------------------------------------------------- 1 | 2 | // Let's start using ES6 3 | // And let's organize the code following clean code concepts 4 | // Later one we will complete a version using imports + webpack 5 | 6 | // Isolated data array to a different file 7 | 8 | let margin = null, 9 | width = null, 10 | height = null; 11 | 12 | let svg = null; 13 | let x, y = null; // scales 14 | 15 | setupCanvasSize(); 16 | appendSvg("body"); 17 | setupXScale(); 18 | setupYScale(); 19 | appendXAxis(); 20 | appendYAxis(); 21 | appendChartBars(); 22 | 23 | // 1. let's start by selecting the SVG Node 24 | function setupCanvasSize() { 25 | margin = {top: 0, left: 80, bottom: 20, right: 30}; 26 | width = 960 - margin.left - margin.right; 27 | height = 120 - margin.top - margin.bottom; 28 | } 29 | 30 | function appendSvg(domElement) { 31 | svg = d3.select(domElement).append("svg") 32 | .attr("width", width + margin.left + margin.right) 33 | .attr("height", height + margin.top + margin.bottom) 34 | .append("g") 35 | .attr("transform",`translate(${margin.left}, ${margin.top})`); 36 | 37 | } 38 | 39 | // Now on the X axis we want to map totalSales values to 40 | // pixels 41 | // in this case we map the canvas range 0..350, to 0...maxSales 42 | // domain == data (data from 0 to maxSales) boundaries 43 | function setupXScale() 44 | { 45 | var maxSales = d3.max(totalSales, function(d, i) { 46 | return d.sales; 47 | }); 48 | 49 | x = d3.scaleLinear() 50 | .range([0, width]) 51 | .domain([0, maxSales]); 52 | 53 | } 54 | 55 | // Now we don't have a linear range of values, we have a discrete 56 | // range of values (one per product) 57 | // Here we are generating an array of product names 58 | function setupYScale() 59 | { 60 | y = d3.scaleBand() 61 | .rangeRound([0, height]) 62 | .domain(totalSales.map(function(d, i) { 63 | return d.product; 64 | })); 65 | } 66 | 67 | function appendXAxis() { 68 | // Add the X Axis 69 | svg.append("g") 70 | .attr("transform",`translate(0, ${height})`) 71 | .call(d3.axisBottom(x)); 72 | } 73 | 74 | function appendYAxis() { 75 | // Add the Y Axis 76 | svg.append("g") 77 | .call(d3.axisLeft(y)); 78 | } 79 | 80 | function appendChartBars() 81 | { 82 | // 2. Now let's select all the rectangles inside that svg 83 | // (right now is empty) 84 | var rects = svg.selectAll('rect') 85 | .data(totalSales); 86 | 87 | // Now it's time to append to the list of Rectangles we already have 88 | var newRects = rects.enter(); 89 | 90 | // Let's append a new Rectangles 91 | // UpperCorner: 92 | // Starting x position, the start from the axis 93 | // Starting y position, where the product starts on the y scale 94 | // React width and height: 95 | // height: the space assign for each entry (product) on the Y axis 96 | // width: Now that we have the mapping previously done (linear) 97 | // we just pass the sales and use the X axis conversion to 98 | // get the right value 99 | newRects.append('rect') 100 | .attr('x', x(0)) 101 | .attr('y', function(d, i) { 102 | return y(d.product); 103 | }) 104 | .attr('height', y.bandwidth) 105 | .attr('width', function(d, i) { 106 | return x(d.sales); 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /samples/02 Charts/04 BarChartRefactor/styles.css: -------------------------------------------------------------------------------- 1 | .axis text { 2 | font: 10px sans-serif; 3 | } 4 | 5 | .axis path, 6 | .axis line { 7 | fill: none; 8 | stroke: #000; 9 | shape-rendering: crispEdges; 10 | } 11 | -------------------------------------------------------------------------------- /samples/02 Charts/05 Lines/data.js: -------------------------------------------------------------------------------- 1 | 2 | var totalSales = [ 3 | { month: new Date(2016,10, 01), sales: 6500 }, 4 | { month: new Date(2016,11, 01), sales: 5400 }, 5 | { month: new Date(2016,12, 01), sales: 3500 }, 6 | { month: new Date(2017,1, 01), sales: 9000 }, 7 | { month: new Date(2017,2, 01), sales: 8500 }, 8 | ]; 9 | -------------------------------------------------------------------------------- /samples/02 Charts/05 Lines/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/02 Charts/05 Lines/main.js: -------------------------------------------------------------------------------- 1 | 2 | // Let's start using ES6 3 | // And let's organize the code following clean code concepts 4 | // Later one we will complete a version using imports + webpack 5 | 6 | // Isolated data array to a different file 7 | 8 | let margin = null, 9 | width = null, 10 | height = null; 11 | 12 | let svg = null; 13 | let x, y = null; // scales 14 | 15 | setupCanvasSize(); 16 | appendSvg("body"); 17 | setupXScale(); 18 | setupYScale(); 19 | appendXAxis(); 20 | appendYAxis(); 21 | appendLineCharts(); 22 | 23 | 24 | // 1. let's start by selecting the SVG Node 25 | function setupCanvasSize() { 26 | margin = {top: 20, left: 80, bottom: 20, right: 30}; 27 | width = 960 - margin.left - margin.right; 28 | height = 520 - margin.top - margin.bottom; 29 | } 30 | 31 | function appendSvg(domElement) { 32 | svg = d3.select(domElement).append("svg") 33 | .attr("width", width + margin.left + margin.right) 34 | .attr("height", height + margin.top + margin.bottom) 35 | .append("g") 36 | .attr("transform",`translate(${margin.left}, ${margin.top})`); 37 | 38 | } 39 | 40 | // Now on the X axis we want to map totalSales values to 41 | // pixels 42 | // in this case we map the canvas range 0..350, to 0...maxSales 43 | // domain == data (data from 0 to maxSales) boundaries 44 | function setupXScale() 45 | { 46 | 47 | x = d3.scaleTime() 48 | .range([0, width]) 49 | .domain(d3.extent(totalSales, function(d) { return d.month})); 50 | } 51 | 52 | // Now we don't have a linear range of values, we have a discrete 53 | // range of values (one per product) 54 | // Here we are generating an array of product names 55 | function setupYScale() 56 | { 57 | var maxSales = d3.max(totalSales, function(d, i) { 58 | return d.sales; 59 | }); 60 | 61 | y = d3.scaleLinear() 62 | .range([height, 0]) 63 | .domain([0, maxSales]); 64 | 65 | } 66 | 67 | function appendXAxis() { 68 | // Add the X Axis 69 | svg.append("g") 70 | .attr("transform",`translate(0, ${height})`) 71 | .call(d3.axisBottom(x)); 72 | } 73 | 74 | function appendYAxis() { 75 | // Add the Y Axis 76 | svg.append("g") 77 | .call(d3.axisLeft(y)); 78 | } 79 | 80 | function appendLineCharts() 81 | { 82 | // define the line 83 | var valueline = d3.line() 84 | .x(function(d) { return x(d.month); }) 85 | .y(function(d) { return y(d.sales); }); 86 | 87 | // Add the valueline path. 88 | svg.append("path") 89 | .data([totalSales]) 90 | .attr("class", "line") 91 | .attr("d", valueline); 92 | 93 | } 94 | -------------------------------------------------------------------------------- /samples/02 Charts/05 Lines/styles.css: -------------------------------------------------------------------------------- 1 | .axis text { 2 | font: 10px sans-serif; 3 | } 4 | 5 | .axis path, 6 | .axis line { 7 | fill: none; 8 | stroke: #000; 9 | shape-rendering: crispEdges; 10 | } 11 | 12 | .line { 13 | fill: none; 14 | stroke: steelblue; 15 | stroke-width: 2px; 16 | } 17 | -------------------------------------------------------------------------------- /samples/02 Charts/06 Pie/data.js: -------------------------------------------------------------------------------- 1 | var totalSales = [ 2 | { product: 'Hoodie', sales: 7 }, 3 | { product: 'Jacket', sales: 6 }, 4 | { product: 'Snuggie', sales: 9 }, 5 | ]; 6 | -------------------------------------------------------------------------------- /samples/02 Charts/06 Pie/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/02 Charts/06 Pie/main.js: -------------------------------------------------------------------------------- 1 | 2 | // Let's start using ES6 3 | // And let's organize the code following clean code concepts 4 | // Later one we will complete a version using imports + webpack 5 | 6 | // Isolated data array to a different file 7 | 8 | let margin = null, 9 | width = null, 10 | height = null; 11 | 12 | let svg = null; 13 | let x, y = null; // scales 14 | 15 | // helper that returns a color based on an ID 16 | const color = d3.scaleOrdinal(d3.schemeCategory10); 17 | 18 | 19 | setupCanvasSize(); 20 | appendSvg("body"); 21 | appendPieChart(); 22 | AppendLegend(); 23 | 24 | // 1. let's start by selecting the SVG Node 25 | function setupCanvasSize() { 26 | margin = {top: 0, left: 80, bottom: 20, right: 30}; 27 | width = 960 - margin.left - margin.right; 28 | height = 120 - margin.top - margin.bottom; 29 | } 30 | 31 | function appendSvg(domElement) { 32 | svg = d3.select(domElement).append("svg") 33 | .attr("width", width + margin.left + margin.right) 34 | .attr("height", height + margin.top + margin.bottom) 35 | .append("g") 36 | .attr("transform",`translate(${margin.left}, ${margin.top})`); 37 | 38 | } 39 | 40 | 41 | function appendPieChart() 42 | { 43 | // Where to get the measure data 44 | var pie = d3.pie() 45 | .value(function(d) { return d.sales }) 46 | 47 | // Calculate Arcs 48 | var slices = pie(totalSales); 49 | 50 | // Pie chart size 51 | var arc = d3.arc() 52 | .innerRadius(0) 53 | .outerRadius(50); 54 | 55 | // Draw the pie 56 | svg.selectAll('path.slice') 57 | .data(slices) 58 | .enter() 59 | .append('path') 60 | .attr('class', 'slice') 61 | .attr('d', arc) 62 | .attr('fill', function(d) { 63 | return color(d.data.product); 64 | }) 65 | .attr("transform", `translate(150, 50)`) 66 | ; 67 | } 68 | 69 | function AppendLegend() { 70 | // building a legend is as simple as binding 71 | // more elements to the same data. in this case, 72 | // tags 73 | svg.append('g') 74 | .attr('class', 'legend') 75 | .selectAll('text') 76 | .data(totalSales) 77 | .enter() 78 | .append('text') 79 | .text(function(d) { return '• ' + d.product; }) 80 | .attr('fill', function(d) { return color(d.product); }) 81 | .attr('y', function(d, i) { return 20 * (i + 1); }) 82 | } -------------------------------------------------------------------------------- /samples/02 Charts/06 Pie/styles.css: -------------------------------------------------------------------------------- 1 | .axis text { 2 | font: 10px sans-serif; 3 | } 4 | 5 | .axis path, 6 | .axis line { 7 | fill: none; 8 | stroke: #000; 9 | shape-rendering: crispEdges; 10 | } 11 | -------------------------------------------------------------------------------- /samples/02 Charts/07 Refresh/data.csv: -------------------------------------------------------------------------------- 1 | month,sales 2 | 10-Oct-16,6500 3 | 1-Nov-16,5400 4 | 1-Dec-16,3500 5 | 1-Jan-17,9000 6 | 1-Feb-17,8500 -------------------------------------------------------------------------------- /samples/02 Charts/07 Refresh/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/02 Charts/07 Refresh/main.js: -------------------------------------------------------------------------------- 1 | 2 | // Let's start using ES6 3 | // And let's organize the code following clean code concepts 4 | // Later one we will complete a version using imports + webpack 5 | 6 | // Isolated data array to a different file 7 | 8 | let margin = null, 9 | width = null, 10 | height = null; 11 | 12 | let svg = null; 13 | let x, y = null; // scales 14 | 15 | setupCanvasSize(); 16 | appendSvg("body"); 17 | refreshChart(); 18 | autoRefreshChart(5000); 19 | 20 | function autoRefreshChart(miliSeconds) { 21 | setInterval(function() { 22 | refreshChart(); 23 | }, miliSeconds); 24 | } 25 | 26 | 27 | function refreshChart() { 28 | d3.csv("data.csv", function(error, data) { 29 | if (error) throw error; 30 | 31 | // parse the date / time 32 | var parseTime = d3.timeParse("%d-%b-%y"); 33 | 34 | data.forEach(function(d) { 35 | d.month = parseTime(d.month); 36 | d.sales = +d.sales; 37 | }); 38 | 39 | clearCanvas(); 40 | drawChart(data); 41 | }); 42 | } 43 | 44 | 45 | function drawChart(totalSales) { 46 | setupXScale(totalSales); 47 | setupYScale(totalSales); 48 | appendXAxis(totalSales); 49 | appendYAxis(totalSales); 50 | appendLineCharts(totalSales); 51 | } 52 | 53 | function clearCanvas() { 54 | d3.selectAll("svg > g > *").remove(); 55 | } 56 | 57 | 58 | 59 | // 1. let's start by selecting the SVG Node 60 | function setupCanvasSize() { 61 | margin = {top: 20, left: 80, bottom: 20, right: 30}; 62 | width = 960 - margin.left - margin.right; 63 | height = 520 - margin.top - margin.bottom; 64 | } 65 | 66 | function appendSvg(domElement) { 67 | svg = d3.select(domElement).append("svg") 68 | .attr("width", width + margin.left + margin.right) 69 | .attr("height", height + margin.top + margin.bottom) 70 | .append("g") 71 | .attr("transform",`translate(${margin.left}, ${margin.top})`); 72 | 73 | } 74 | 75 | // Now on the X axis we want to map totalSales values to 76 | // pixels 77 | // in this case we map the canvas range 0..350, to 0...maxSales 78 | // domain == data (data from 0 to maxSales) boundaries 79 | function setupXScale(totalSales) 80 | { 81 | 82 | x = d3.scaleTime() 83 | .range([0, width]) 84 | .domain(d3.extent(totalSales, function(d) { return d.month})); 85 | } 86 | 87 | // Now we don't have a linear range of values, we have a discrete 88 | // range of values (one per product) 89 | // Here we are generating an array of product names 90 | function setupYScale(totalSales) 91 | { 92 | var maxSales = d3.max(totalSales, function(d, i) { 93 | return d.sales; 94 | }); 95 | 96 | y = d3.scaleLinear() 97 | .range([height, 0]) 98 | .domain([0, maxSales]); 99 | 100 | } 101 | 102 | function appendXAxis(totalSales) { 103 | // Add the X Axis 104 | svg.append("g") 105 | .attr("transform",`translate(0, ${height})`) 106 | .call(d3.axisBottom(x)); 107 | } 108 | 109 | function appendYAxis(totalSales) { 110 | // Add the Y Axis 111 | svg.append("g") 112 | .call(d3.axisLeft(y)); 113 | } 114 | 115 | function appendLineCharts(totalSales) 116 | { 117 | // define the line 118 | var valueline = d3.line() 119 | .x(function(d) { return x(d.month); }) 120 | .y(function(d) { return y(d.sales); }); 121 | 122 | // Add the valueline path. 123 | svg.append("path") 124 | .data([totalSales]) 125 | .attr("class", "line") 126 | .attr("d", valueline); 127 | } 128 | -------------------------------------------------------------------------------- /samples/02 Charts/07 Refresh/styles.css: -------------------------------------------------------------------------------- 1 | .axis text { 2 | font: 10px sans-serif; 3 | } 4 | 5 | .axis path, 6 | .axis line { 7 | fill: none; 8 | stroke: #000; 9 | shape-rendering: crispEdges; 10 | } 11 | 12 | .line { 13 | fill: none; 14 | stroke: steelblue; 15 | stroke-width: 2px; 16 | } 17 | -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/base.webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | var webpack = require('webpack'); 4 | var CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | 6 | var basePath = __dirname; 7 | var dataPath = basePath + '/data'; 8 | 9 | module.exports = { 10 | context: path.join(basePath, 'src'), 11 | resolve: { 12 | extensions: ['.js', '.scss', 'css'] 13 | }, 14 | entry: { 15 | app: './app.js', 16 | pageStyles: './pageStyles.scss', 17 | vendor: 'd3' 18 | }, 19 | module: { 20 | rules: [{ 21 | test: /\.js$/, 22 | exclude: /node_modules/, 23 | loader: 'babel-loader' 24 | }, 25 | { 26 | test: /\.svg$/, 27 | loader: 'url-loader?limit=65000&mimetype=image/svg+xml&name=public/fonts/[name].[ext]' 28 | }, 29 | { 30 | test: /\.woff$/, 31 | loader: 'url-loader?limit=65000&mimetype=application/font-woff&name=public/fonts/[name].[ext]' 32 | }, 33 | { 34 | test: /\.woff2$/, 35 | loader: 'url-loader?limit=65000&mimetype=application/font-woff2&name=public/fonts/[name].[ext]' 36 | }, 37 | { 38 | test: /\.[ot]tf$/, 39 | loader: 'url-loader?limit=65000&mimetype=application/octet-stream&name=public/fonts/[name].[ext]' 40 | }, 41 | { 42 | test: /\.eot$/, 43 | loader: 'url-loader?limit=65000&mimetype=application/vnd.ms-fontobject&name=public/fonts/[name].[ext]' 44 | } 45 | ] 46 | }, 47 | plugins: [ 48 | // Generate index.html in /dist => https://github.com/ampedandwired/html-webpack-plugin 49 | new HtmlWebpackPlugin({ 50 | filename: 'index.html', //Name of file in ./dist/ 51 | template: 'index.html', //Name of template in ./src 52 | hash: true 53 | }), 54 | new webpack.optimize.CommonsChunkPlugin({ 55 | names: ['vendor', 'manifest'] 56 | }), 57 | new CopyWebpackPlugin([{ 58 | from: '../data/StatsPerCountry.txt', 59 | to: 'data.txt' 60 | }]) 61 | ] 62 | }; -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/data/StatsPerCountry.txt: -------------------------------------------------------------------------------- 1 | Country Population Continent Life Expectancy Purchasing Power 2 | Afghanistan 31889923 Asia 43.8 974.58 3 | Albania 3600523 Europe 76.4 5937.03 4 | Algeria 33333216 Africa 72.3 6223.37 5 | Angola 12420476 Africa 42.7 4797.23 6 | Argentina 40301927 Americas 75.3 12779.38 7 | Australia 20434176 Oceania 81.2 34435.37 8 | Austria 8199783 Europe 79.8 36126.49 9 | Bahrain 708573 Asia 75.6 29796.05 10 | Bangladesh 150448339 Asia 64.1 1391.25 11 | Belgium 10392226 Europe 79.4 33692.61 12 | Benin 8078314 Africa 56.7 1441.28 13 | Bolivia 9119152 Americas 65.6 3822.14 14 | Bosnia and Herzegovina 4552198 Europe 74.9 7446.30 15 | Botswana 1639131 Africa 50.7 12569.85 16 | Brazil 190010647 Americas 72.4 9065.80 17 | Bulgaria 7322858 Europe 73.0 10680.79 18 | Burkina Faso 14326203 Africa 52.3 1217.03 19 | Burundi 8390505 Africa 49.6 430.07 20 | Cambodia 14131858 Asia 59.7 1713.78 21 | Cameroon 17696293 Africa 50.4 2042.10 22 | Canada 33390141 Americas 80.7 36319.24 23 | Central African Republic 4369038 Africa 44.7 706.02 24 | Chad 10238807 Africa 50.7 1704.06 25 | Chile 16284741 Americas 78.6 13171.64 26 | China 1318683096 Asia 73.0 4959.11 27 | Colombia 44227550 Americas 72.9 7006.58 28 | Comoros 710960 Africa 65.2 986.15 29 | Congo. Dem. Rep. 64606759 Africa 46.5 277.55 30 | Congo. Rep. 3800610 Africa 55.3 3632.56 31 | Costa Rica 4133884 Americas 78.8 9645.06 32 | Cote d'Ivoire 18013409 Africa 48.3 1544.75 33 | Croatia 4493312 Europe 75.7 14619.22 34 | Cuba 11416987 Americas 78.3 8948.10 35 | Czech Republic 10228744 Europe 76.5 22833.31 36 | Denmark 5468120 Europe 78.3 35278.42 37 | Djibouti 496374 Africa 54.8 2082.48 38 | Dominican Republic 9319622 Americas 72.2 6025.37 39 | Ecuador 13755680 Americas 75.0 6873.26 40 | Egypt 80264543 Africa 71.3 5581.18 41 | El Salvador 6939688 Americas 71.9 5728.35 42 | Equatorial Guinea 551201 Africa 51.6 12154.09 43 | Eritrea 4906585 Africa 58.0 641.37 44 | Ethiopia 76511887 Africa 52.9 690.81 45 | Finland 5238460 Europe 79.3 33207.08 46 | France 61083916 Europe 80.7 30470.02 47 | Gabon 1454867 Africa 56.7 13206.48 48 | Gambia 1688359 Africa 59.4 752.75 49 | Germany 82400996 Europe 79.4 32170.37 50 | Ghana 22873338 Africa 60.0 1327.61 51 | Greece 10706290 Europe 79.5 27538.41 52 | Guatemala 12572928 Americas 70.3 5186.05 53 | Guinea 9947814 Africa 56.0 942.65 54 | Guinea-Bissau 1472041 Africa 46.4 579.23 55 | Haiti 8502814 Americas 60.9 1201.64 56 | Honduras 7483763 Americas 70.2 3548.33 57 | Hong Kong. China 6980412 Asia 82.2 39724.98 58 | Hungary 9956108 Europe 73.3 18008.94 59 | Iceland 301931 Europe 81.8 36180.79 60 | India 1110396331 Asia 64.7 2452.21 61 | Indonesia 223547000 Asia 70.7 3540.65 62 | Iran 69453570 Asia 71.0 11605.71 63 | Iraq 27499638 Asia 59.5 4471.06 64 | Ireland 4109086 Europe 78.9 40676.00 65 | Israel 6426679 Asia 80.7 25523.28 66 | Italy 58147733 Europe 80.5 28569.72 67 | Jamaica 2780132 Americas 72.6 7320.88 68 | Japan 127467972 Asia 82.6 31656.07 69 | Jordan 6053193 Asia 72.5 4519.46 70 | Kenya 35610177 Africa 54.1 1463.25 71 | Korea. Dem. Rep. 23301725 Asia 67.3 1593.07 72 | Korea. Rep. 49044790 Asia 78.6 23348.14 73 | Kuwait 2505559 Asia 77.6 47306.99 74 | Lebanon 3921278 Asia 72.0 10461.06 75 | Lesotho 2012649 Africa 42.6 1569.33 76 | Liberia 3193942 Africa 45.7 414.51 77 | Libya 6036914 Africa 74.0 12057.50 78 | Madagascar 19167654 Africa 59.4 1044.77 79 | Malawi 13327079 Africa 48.3 759.35 80 | Malaysia 24821286 Asia 74.2 12451.66 81 | Mali 12031795 Africa 54.5 1042.58 82 | Mauritania 3270065 Africa 64.2 1803.15 83 | Mauritius 1250882 Africa 72.8 10956.99 84 | Mexico 108700891 Americas 76.2 11977.57 85 | Mongolia 2874127 Asia 66.8 3095.77 86 | Montenegro 684736 Europe 74.5 9253.90 87 | Morocco 33757175 Africa 71.2 3820.18 88 | Mozambique 19951656 Africa 42.1 823.69 89 | Myanmar 47761980 Asia 62.1 944.00 90 | Namibia 2055080 Africa 52.9 4811.06 91 | Nepal 28901790 Asia 63.8 1091.36 92 | Netherlands 16570613 Europe 79.8 36797.93 93 | New Zealand 4115771 Oceania 80.2 25185.01 94 | Nicaragua 5675356 Americas 72.9 2749.32 95 | Niger 12894865 Africa 56.9 619.68 96 | Nigeria 135031164 Africa 46.9 2013.98 97 | Norway 4627926 Europe 80.2 49357.19 98 | Oman 3204897 Asia 75.6 22316.19 99 | Pakistan 169270617 Asia 65.5 2605.95 100 | Panama 3242173 Americas 75.5 9809.19 101 | Paraguay 6667147 Americas 71.8 4172.84 102 | Peru 28674757 Americas 71.4 7408.91 103 | Philippines 91077287 Asia 71.7 3190.48 104 | Poland 38518241 Europe 75.6 15389.92 105 | Portugal 10642836 Europe 78.1 20509.65 106 | Puerto Rico 3942491 Americas 78.7 19328.71 107 | Reunion 798094 Africa 76.4 7670.12 108 | Romania 22276056 Europe 72.5 10808.48 109 | Rwanda 8860588 Africa 46.2 863.09 110 | Sao Tome and Principe 199579 Africa 65.5 1598.44 111 | Saudi Arabia 27601038 Asia 72.8 21654.83 112 | Senegal 12267493 Africa 63.1 1712.47 113 | Serbia 10150265 Europe 74.0 9786.53 114 | Sierra Leone 6144562 Africa 42.6 862.54 115 | Singapore 4553009 Asia 80.0 47143.18 116 | Slovak Republic 5447502 Europe 74.7 18678.31 117 | Slovenia 2009245 Europe 77.9 25768.26 118 | Somalia 9118773 Africa 48.2 926.14 119 | South Africa 43997828 Africa 49.3 9269.66 120 | Spain 40448191 Europe 80.9 28821.06 121 | Sri Lanka 20378239 Asia 72.4 3970.10 122 | Sudan 42292929 Africa 58.6 2602.39 123 | Swaziland 1133066 Africa 39.6 4513.48 124 | Sweden 9031088 Europe 80.9 33859.75 125 | Switzerland 7554661 Europe 81.7 37506.42 126 | Syria 19314747 Asia 74.1 4184.55 127 | Taiwan 23174294 Asia 78.4 28718.28 128 | Tanzania 38139640 Africa 52.5 1107.48 129 | Thailand 65068149 Asia 70.6 7458.40 130 | Togo 5701579 Africa 58.4 882.97 131 | Trinidad and Tobago 1056608 Americas 69.8 18008.51 132 | Tunisia 10276158 Africa 73.9 7092.92 133 | Turkey 71158647 Europe 71.8 8458.28 134 | Uganda 29170398 Africa 51.5 1056.38 135 | United Kingdom 60776238 Europe 79.4 33203.26 136 | United States 301139947 Americas 78.2 42951.65 137 | Uruguay 3447496 Americas 76.4 10611.46 138 | Venezuela 26084662 Americas 73.7 11415.81 139 | Vietnam 85262356 Asia 74.2 2441.58 140 | West Bank and Gaza 4018332 Asia 73.4 3025.35 141 | Yemen. Rep. 22211743 Asia 62.7 2280.77 142 | Zambia 11746035 Africa 42.4 1271.21 143 | Zimbabwe 12311143 Africa 43.5 469.71 -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/dev.webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var webpackMerge = require('webpack-merge'); 4 | var commonConfig = require('./base.webpack.config.js'); 5 | 6 | var basePath = __dirname; 7 | 8 | module.exports = function () { 9 | return webpackMerge(commonConfig, { 10 | // For development https://webpack.js.org/configuration/devtool/#for-development 11 | devtool: 'inline-source-map', 12 | 13 | output: { 14 | path: path.join(basePath, 'dist'), 15 | filename: '[name].js', 16 | }, 17 | 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.scss$/, 22 | exclude: [/node_modules/, /pageStyles.scss/], 23 | use: [ 24 | 'style-loader', 25 | { 26 | loader: 'css-loader', 27 | options: { 28 | modules: true, 29 | localIdentName: '[name]__[local]___[hash:base64:5]', 30 | camelCase: true 31 | } 32 | }, 33 | 'sass-loader' 34 | ] 35 | }, 36 | { 37 | test: /\.scss$/, 38 | include: /pageStyles.scss/, 39 | use: ['style-loader', 'css-loader', 'sass-loader'] 40 | }, 41 | { 42 | test: /\.css$/, 43 | include: /node_modules/, 44 | use: ['style-loader', 'css-loader'] 45 | } 46 | ] 47 | }, 48 | 49 | devServer: { 50 | port: 8080 51 | } 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/fonts/rajdhani/Rajdhani-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/samples/03 Sleek Charts/01 Bubble Chart/fonts/rajdhani/Rajdhani-Bold.ttf -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/fonts/rajdhani/Rajdhani-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/samples/03 Sleek Charts/01 Bubble Chart/fonts/rajdhani/Rajdhani-Light.ttf -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/fonts/rajdhani/Rajdhani-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/samples/03 Sleek Charts/01 Bubble Chart/fonts/rajdhani/Rajdhani-Medium.ttf -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/fonts/rajdhani/Rajdhani-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/samples/03 Sleek Charts/01 Bubble Chart/fonts/rajdhani/Rajdhani-Regular.ttf -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/fonts/rajdhani/Rajdhani-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/d3js-samples/52942f4da800cfadef0ab47e58726d8cde30ad29/samples/03 Sleek Charts/01 Bubble Chart/fonts/rajdhani/Rajdhani-SemiBold.ttf -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bubblechart", 3 | "version": "1.0.0", 4 | "description": "A Bubble Chart implemented with D3.js and making use of ES6 modules plus Webpack 2 bundling features", 5 | "main": "dist/app.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --config=dev.webpack.config.js", 8 | "build:dev": "rimraf dist && webpack --config=dev.webpack.config.js", 9 | "build:prod": "rimraf dist && webpack -p --config=prod.webpack.config.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/Lemoncode/d3js-samples.git" 14 | }, 15 | "keywords": [ 16 | "d3", 17 | "js", 18 | "graphs", 19 | "javascript", 20 | "modules" 21 | ], 22 | "author": "Javier Calzado", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/Lemoncode/d3js-samples/issues" 26 | }, 27 | "homepage": "https://github.com/Lemoncode/d3js-samples#readme", 28 | "devDependencies": { 29 | "babel-core": "^6.23.1", 30 | "babel-loader": "^6.3.2", 31 | "babel-preset-env": "^1.1.11", 32 | "babel-preset-react": "^6.23.0", 33 | "copy-webpack-plugin": "^4.0.1", 34 | "css-loader": "^0.26.2", 35 | "extract-text-webpack-plugin": "^2.0.0", 36 | "file-loader": "^0.10.1", 37 | "html-webpack-plugin": "^2.28.0", 38 | "node-sass": "^4.5.1", 39 | "rimraf": "^2.6.1", 40 | "sass-loader": "^6.0.3", 41 | "style-loader": "^0.13.2", 42 | "url-loader": "^0.5.8", 43 | "webpack": "^2.2.1", 44 | "webpack-dev-server": "^2.4.1", 45 | "webpack-merge": "^4.0.0" 46 | }, 47 | "dependencies": { 48 | "d3": "^4.7.4" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/prod.webpack.config.js: -------------------------------------------------------------------------------- 1 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 2 | var path = require('path'); 3 | var webpackMerge = require('webpack-merge'); 4 | var commonConfig = require('./base.webpack.config.js'); 5 | 6 | var basePath = __dirname; 7 | 8 | module.exports = function () { 9 | return webpackMerge(commonConfig, { 10 | // For production https://webpack.js.org/configuration/devtool/#for-development 11 | devtool: 'cheap-module-source-map', 12 | 13 | output: { 14 | path: path.join(basePath, 'dist'), 15 | filename: '[chunkhash].[name].js', 16 | }, 17 | 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.scss$/, 22 | exclude: [/node_modules/, /pageStyles.scss/], 23 | loader: ExtractTextPlugin.extract({ 24 | fallback: 'style-loader', 25 | use: [ 26 | { 27 | loader: 'css-loader', 28 | options: { 29 | modules: true, 30 | localIdentName: '[name]__[local]___[hash:base64:5]', 31 | camelCase: true 32 | } 33 | }, 34 | { loader: 'sass-loader' } 35 | ] 36 | }) 37 | }, 38 | { 39 | test: /\.scss$/, 40 | include: /pageStyles.scss/, 41 | loader: ExtractTextPlugin.extract({ 42 | fallback: 'style-loader', 43 | use: [ 44 | { loader: 'css-loader' }, 45 | { loader: 'sass-loader' } ] 46 | }) 47 | }, 48 | { 49 | test: /\.css$/, 50 | include: /node_modules/, 51 | loader: ExtractTextPlugin.extract({ 52 | fallback: 'style-loader', 53 | use: { 54 | loader: 'css-loader' 55 | } 56 | }) 57 | } 58 | ] 59 | }, 60 | 61 | plugins: [ 62 | new ExtractTextPlugin({ 63 | filename: '[chunkhash].[name].css', 64 | allChunks: true 65 | }) 66 | ] 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/src/app.js: -------------------------------------------------------------------------------- 1 | import * as Parser from "./delimitedDataParser"; 2 | import * as BubbleChart from "./bubbleChart"; 3 | 4 | function initializeChart() { 5 | Parser.setup("\t", "\n", true); 6 | Parser.parse("./data.txt", 7 | (parsedData, htmlTable) => { 8 | document.getElementById("id-data-table").innerHTML = 9 | `

    Data Source

    10 | ${htmlTable}`; 11 | BubbleChart.initialize(parsedData, "id-bubble-chart"); 12 | BubbleChart.draw(); 13 | window.addEventListener("resize", BubbleChart.draw); 14 | window.addEventListener("orientationchange", BubbleChart.draw); 15 | }, 16 | (message) => { 17 | document.getElementById("id-data-table").innerHTML = message; 18 | }); 19 | } 20 | 21 | initializeChart(); 22 | -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/src/bubbleChart.js: -------------------------------------------------------------------------------- 1 | const d3 = require("d3"); 2 | import cssNames from "./bubbleChartStyles.scss"; 3 | 4 | /** 5 | * Module local variables. 6 | * @private 7 | */ 8 | 9 | // Chart main elements. 10 | let data = null; 11 | let htmlId = null; 12 | let svg = null; 13 | let chart = null; 14 | let bubbleSelection = null; 15 | let infoPopup = null; 16 | 17 | // Chart scales. 18 | let xScale = null; 19 | let yScale = null; 20 | let bubbleAreaScale = null; 21 | let continentScale = null; 22 | 23 | // Width and Height in relative units. 24 | // Fit the container by default unless a specific relative 25 | // size is indicated from the caller. 26 | const widthRel = "100%"; 27 | const heightRel = "100%"; 28 | 29 | // Chart Padding in relative units (rem). 30 | // Given that padding is intended to allocate axis labels, it makes 31 | // sense that it is expressed in relative units refering to font size. 32 | const paddingRel = { 33 | top: 4, 34 | right: 1, 35 | bottom: 4, 36 | left: 4 37 | }; 38 | let fontSizeAbs = null; 39 | 40 | // Padding in absolute units. 41 | let paddingAbs = {}; 42 | 43 | // Width and Height minus padding in absolute units. 44 | let innerWidthAbs = null; 45 | let innerHeightAbs = null; 46 | 47 | /** 48 | * Helper Functions 49 | * @private 50 | */ 51 | 52 | function updateAbsoluteSize() { 53 | const widthAbs = parseInt(svg.style("width"), 10); 54 | const heightAbs = parseInt(svg.style("height"), 10); 55 | // const displayScalingFactor = window.devicePixelRatio; 56 | fontSizeAbs = parseInt(getComputedStyle(document.body).fontSize, 10); 57 | paddingAbs.top = Math.round(paddingRel.top * fontSizeAbs); 58 | paddingAbs.right = Math.round(paddingRel.right * fontSizeAbs); 59 | paddingAbs.bottom = Math.round(paddingRel.bottom * fontSizeAbs); 60 | paddingAbs.left = Math.round(paddingRel.left * fontSizeAbs); 61 | innerWidthAbs = widthAbs - paddingAbs.left - paddingAbs.right; 62 | innerHeightAbs = heightAbs - paddingAbs.top - paddingAbs.bottom; 63 | } 64 | 65 | function addPaddingToScale(scale, marginPercentage) { 66 | const span = scale.range()[1] - scale.range()[0]; 67 | const rangeMargin = span * marginPercentage / 100; 68 | const newDomainMin = scale.invert(scale.range()[0] - rangeMargin); 69 | const newDomainMax = scale.invert(scale.range()[1] + rangeMargin); 70 | scale.domain([newDomainMin, newDomainMax]); 71 | } 72 | 73 | /** 74 | * Initialization Functions 75 | * @private 76 | */ 77 | 78 | function initializeChart() { 79 | svg = d3.select(`#${htmlId}`) 80 | .append("svg") 81 | .attr("width", widthRel) 82 | .attr("height", heightRel); 83 | updateAbsoluteSize(); 84 | chart = svg.append("g") 85 | .attr("transform", `translate(${paddingAbs.left},${paddingAbs.top})`); 86 | } 87 | 88 | function initializeScales() { 89 | // x Scale - Represents purchasing power. 90 | // It will better fit in log base 10. Countries will be scattered 91 | // in a nicer way, given that rich vs poor countries may difference 92 | // in several orders of magnitude. 93 | xScale = d3.scaleLog().base(10).nice() 94 | .domain(d3.extent(data, (d) => d.purchasingPower)) 95 | .range([0, innerWidthAbs]); 96 | addPaddingToScale(xScale, 8); 97 | 98 | // y Scale - Represents life expectancy. Linear. 99 | yScale = d3.scaleLinear().nice() 100 | .domain(d3.extent(data, (d) => d.lifeExpectancy)) 101 | .range([innerHeightAbs, 0]); 102 | addPaddingToScale(yScale, 5); 103 | 104 | // Bubble Area Scale - Represents population. 105 | // We want area of the circle to be scaled, however, we have radius 106 | // as circle property to be setup. Given that a = pi*r^2 and pi is 107 | // a constant factor that will not have any effect in the proportionality 108 | // of the relationship, we can safely say that r = sqrt(a). Thus, a 109 | // sqrt scale will be used. 110 | bubbleAreaScale = d3.scaleSqrt() 111 | .domain(d3.extent(data, (d) => d.population)) 112 | .range([1, 100]); 113 | 114 | // Continent Scale - Discrete scale to categorize per continent. 115 | continentScale = d3.scaleOrdinal() 116 | .domain(["Africa", "Asia", "Americas", "Europe", "Oceania"]) 117 | .range([ 118 | `${cssNames.continentAfrica}`, 119 | `${cssNames.continentAsia}`, 120 | `${cssNames.continentAmerica}`, 121 | `${cssNames.continentEurope}`, 122 | `${cssNames.continentOceania}` 123 | ]); 124 | } 125 | 126 | function initializeAxis() { 127 | // X axis - Purchasing Power. 128 | chart.append("g") 129 | .attr("class", cssNames.axis) 130 | .attr("transform", `translate(0,${innerHeightAbs})`) 131 | .call(d3.axisBottom(xScale) 132 | .ticks(Math.max(innerWidthAbs / (fontSizeAbs * 5), 2), "$.2s") 133 | .tickSizeOuter(0)); 134 | 135 | // Y axis - Life Expectancy. 136 | chart.append("g") 137 | .attr("class", cssNames.axis) 138 | .call(d3.axisLeft(yScale) 139 | .tickSizeOuter(0)); 140 | } 141 | 142 | function initializeLabels() { 143 | // X axis - Label 144 | chart.append("text") 145 | .attr("class", cssNames.axisTitle) 146 | .attr("text-anchor", "middle") 147 | .attr("transform", `translate(${innerWidthAbs / 2},${innerHeightAbs + paddingAbs.bottom * 0.7})`) 148 | .text("Purchasing Power ($US)"); 149 | 150 | // Y axis - Label 151 | chart.append("text") 152 | .attr("class", cssNames.axisTitle) 153 | .attr("text-anchor", "middle") 154 | .attr("transform", `translate(${paddingAbs.left * -0.5},${innerHeightAbs / 2})rotate(-90)`) 155 | .text("Life Expectancy (years)"); 156 | 157 | // Title 158 | chart.append("text") 159 | .attr("class", cssNames.mainTitle) 160 | .attr("text-anchor", "middle") 161 | .attr("transform", `translate(${innerWidthAbs / 2},${paddingAbs.top * -0.3})`) 162 | .text("Wealth Distribution per Country"); 163 | } 164 | 165 | function updatePositionInfoPopup() { 166 | infoPopup 167 | .style("left", `${d3.event.pageX - (infoPopup.node().getBoundingClientRect().width / 2)}px`) 168 | .style("top", `${d3.event.pageY - parseInt(infoPopup.style("height"), 0) - 5}px`); 169 | } 170 | 171 | function showInfoPopup(itemData) { 172 | infoPopup = infoPopup.html( 173 | ` 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 |
    ${itemData.country}${itemData.continent}
    ${itemData.purchasingPower.toLocaleString()}$
    ${itemData.lifeExpectancy.toLocaleString()}Years
    ${itemData.population.toLocaleString()}Inhabitants
    192 | ` 193 | ); 194 | updatePositionInfoPopup(); 195 | infoPopup.style("opacity", 0.9); 196 | } 197 | 198 | function hideInfoPopup() { 199 | infoPopup.style("opacity", 0); 200 | } 201 | 202 | function initializeInfoPopup() { 203 | if (!infoPopup) { 204 | infoPopup = d3.select(`#${htmlId}`) 205 | .append("div") 206 | .attr("class", cssNames.infoPopup); 207 | } 208 | } 209 | 210 | // Very useful to render small bubbles on top of big ones. 211 | // Emulates z-index in chart canvas. 212 | function sortBubbleSelection() { 213 | return bubbleSelection.sort((a, b) => { 214 | return (b.population - a.population); 215 | }); 216 | } 217 | 218 | function initializeSelections() { 219 | bubbleSelection = chart.selectAll("circle") 220 | .data(data) 221 | .enter() 222 | .append("circle") 223 | .attr("cx", (d) => xScale(d.purchasingPower)) 224 | .attr("cy", (d) => yScale(d.lifeExpectancy)) 225 | .attr("r", (d) => bubbleAreaScale(d.population)) 226 | .attr("class", (d) => `${cssNames.bubble} ${continentScale(d.continent)}`) 227 | .on("mouseenter", showInfoPopup) 228 | .on("mousemove", updatePositionInfoPopup) 229 | .on("mouseleave", hideInfoPopup); 230 | bubbleSelection = sortBubbleSelection(); 231 | } 232 | 233 | function initializeChartElements() { 234 | initializeChart(); 235 | initializeScales(); 236 | initializeAxis(); 237 | initializeLabels(); 238 | initializeInfoPopup(); 239 | initializeSelections() 240 | } 241 | 242 | /** 243 | * This function launches the chart rendering. 244 | * @public 245 | * @return {void} 246 | */ 247 | function draw() { 248 | // TODO: Although effective, this is not very efficient. 249 | // It was done to support resizing the quickest way. 250 | // There is room for improvement here. 251 | if (htmlId) { 252 | if (svg) { 253 | svg.remove(); 254 | } 255 | initializeChartElements(); 256 | } 257 | } 258 | 259 | /** 260 | * Initialize a new bubble Chart inside the corresponding 261 | * html element for a given dataset. 262 | * @public 263 | * @param {array} dataset {Countries stats dataset.} 264 | * @param {type} htmlElementId {ID of the html element where chart will be drawn.} 265 | * @param {type} width = widthRel {OPTIONAL. Supports relative units. 100% by default.} 266 | * @param {type} height = heightRel {OPTIONAL. Supports relative units. 100% by default.} 267 | * @return {void} 268 | */ 269 | function initialize(dataset, htmlElementId, width = widthRel, height = heightRel) { 270 | data = dataset; 271 | htmlId = htmlElementId; 272 | widthRel = width; 273 | heightRel = height; 274 | } 275 | 276 | export { 277 | initialize, 278 | draw 279 | }; 280 | -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/src/bubbleChartStyles.scss: -------------------------------------------------------------------------------- 1 | $softGray: hsl(0, 0%, 20%); 2 | $softRadius: 3px; 3 | $africaColor: rgb(102, 230, 165); 4 | $asiaColor: rgb(128, 177, 235); 5 | $americaColor: rgb(251, 128, 114); 6 | $europeColor: rgb(231, 41, 138); 7 | $oceaniaColor: rgb(231, 138, 195); 8 | 9 | @mixin continentFiller($color) { 10 | fill: $color; 11 | background-color: $color; 12 | } 13 | 14 | .continent { 15 | &--africa { 16 | @include continentFiller($africaColor); 17 | } 18 | 19 | &--asia { 20 | @include continentFiller($asiaColor); 21 | } 22 | 23 | &--america { 24 | @include continentFiller($americaColor); 25 | } 26 | 27 | &--europe { 28 | @include continentFiller($europeColor); 29 | } 30 | 31 | &--oceania { 32 | @include continentFiller($oceaniaColor); 33 | } 34 | } 35 | 36 | svg { 37 | font-family: inherit; 38 | } 39 | 40 | .bubble { 41 | fill-opacity: 0.7; 42 | stroke-opacity: 0; 43 | transition-property: stroke-opacity; 44 | transition-duration: 0.2s; 45 | transition-timing-function: ease; 46 | 47 | &:hover { 48 | stroke: $softGray; 49 | stroke-opacity: 0.5; 50 | cursor: help; 51 | } 52 | } 53 | 54 | .axis { 55 | font-family: inherit; 56 | 57 | text { 58 | fill: $softGray; 59 | fill-opacity: 0.8; 60 | font-size: 0.8rem; 61 | } 62 | 63 | path, 64 | line { 65 | stroke: $softGray; 66 | stroke-opacity: 0.3; 67 | } 68 | 69 | &_title { 70 | font-size: 1.2rem; 71 | fill: $softGray; 72 | } 73 | } 74 | 75 | .main-title { 76 | font-size: 1.6rem; 77 | fill: $softGray; 78 | } 79 | 80 | .info-popup { 81 | position: absolute; 82 | pointer-events: none; 83 | color: white; 84 | background-color: $softGray; 85 | border-radius: $softRadius; 86 | transition-property: opacity, left, top; 87 | transition-duration: 0.3s; 88 | transition-timing-function: ease; 89 | font-size: 0.8rem; 90 | table-layout: auto; 91 | white-space: nowrap; 92 | 93 | &_data { 94 | text-align: right; 95 | font-weight: bold; 96 | } 97 | 98 | &_units { 99 | text-align: left; 100 | } 101 | 102 | &_caption { 103 | font-size: 1rem; 104 | } 105 | 106 | &_subcaption { 107 | border-radius: $softRadius; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/src/delimitedDataParser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DISCLAIMER: Yes, you may think, why did this guy implement a parser from 3 | * scratch when D3 ships wide variety of parsers? Well, the answer is very 4 | * straightforward: I did not realize until I had the parser finished. 5 | * TIP: Read documentation before start coding :) Check d3-dsv documentation 6 | * https://github.com/d3/d3-dsv 7 | * Anyway, this helped me to do some JS exercise. 8 | */ 9 | 10 | 11 | /** 12 | * Module setup variables. 13 | * @private 14 | */ 15 | let delim = ","; 16 | let lineDelim = "\r\n"; 17 | let returnHtmlTable = false; 18 | 19 | /** 20 | * Convert parsed data into a string representing an HTML Table. For testing purposes only. 21 | * @private 22 | * @param {array} data {Object array containing parsed data.} 23 | * @param {array} fieldNames {Name of the data fields (headers).} 24 | * @return {void} 25 | */ 26 | function parseDataToHtmlTable(data, fieldNames) { 27 | return "" + "" + fieldNames.reduce((headerAcc, currHeader) => { 28 | return headerAcc + ""; 29 | }, "") + "" + data.reduce((recordAcc, currentRecord) => { 30 | return recordAcc + "" + Object.values(currentRecord).reduce((itemAcc, value) => { 31 | return itemAcc + ""; 32 | }, "") + ""; 33 | }, "") + "
    " + currHeader + "
    " + value.toLocaleString() + "
    "; 34 | } 35 | 36 | /** 37 | * Parse delimited data from string. 38 | * @private 39 | * @param {string} data {Input data read from url.} 40 | * @param {function} successCallback {Callback in case of success.} 41 | * @param {function} failCallback {Callback in case of fail.} 42 | * @return {void} 43 | */ 44 | function parseData(data, successCallback, failCallback) { 45 | let lines = data.split(lineDelim); 46 | 47 | // Expect at least, one header and one record. 48 | if (lines.length >= 2) { 49 | 50 | // Get header items. Those will be our field names. 51 | const fieldNames = lines.shift().split(delim); 52 | const numFields = fieldNames.length; 53 | const propertyNames = fieldNames.map((fieldName) => { 54 | const propertyName = fieldName.replace(/\s+/g, ""); 55 | return propertyName.charAt(0).toLowerCase() + propertyName.slice(1); 56 | }); 57 | 58 | if (numFields > 0) { 59 | const parsedData = lines.map((line) => { 60 | return line.split(delim).reduce((record, currentItem, index) => { 61 | const number = Number.parseFloat(currentItem); 62 | record[propertyNames[index]] = number ? number : currentItem; 63 | return record; 64 | }, {}); 65 | }); 66 | 67 | if (returnHtmlTable) { 68 | successCallback(parsedData, parseDataToHtmlTable(parsedData, fieldNames)); 69 | } else { 70 | successCallback(parsedData); 71 | } 72 | return; 73 | } 74 | } 75 | 76 | failCallback("Unexpected format"); 77 | } 78 | 79 | /** 80 | * Parse delimited data from file using native AJAX request. 81 | * @public 82 | * @param {string} url {Data input URL to be parsed.} 83 | * @param {function} successCallback {Callback in case of success.} 84 | * @param {function} failCallback {Callback in case of fail.} 85 | * @return {void} 86 | */ 87 | function parse(url, successCallback, failCallback) { 88 | const xhr = new XMLHttpRequest(); 89 | xhr.onload = () => { 90 | if (xhr.status === 200) { 91 | parseData(xhr.responseText, successCallback, failCallback); 92 | } else if (failCallback) { 93 | failCallback(`Data request failed with reason: ${xhr.statusText}`); 94 | } 95 | }; 96 | xhr.open("GET", url); 97 | xhr.send(null); 98 | } 99 | 100 | /** 101 | * Configure data parser. 102 | * @public 103 | * @param {string} delimiter [",""] {OPTIONAL: data delimiter string. Default: ','.} 104 | * @param {string} lineDelimiter ["\r\n"] {OPTIONAL: line delimiter string. Default: '\r\n'.} 105 | * @param {boolean} printTable = [false] {OPTIONAL: True if you want to print data into HTML table 106 | and pass it through successCallback. For tests purposes only.} 107 | * @return {void} 108 | */ 109 | function setup(delimiter = delim, lineDelimiter = lineDelim, printTable = returnHtmlTable) { 110 | delim = delimiter; 111 | lineDelim = lineDelimiter; 112 | returnHtmlTable = printTable; 113 | } 114 | 115 | export { 116 | parse, 117 | setup 118 | }; 119 | -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bubble Chart 6 | 7 | 8 | 9 | 10 | 11 |
    12 |
    13 |
    14 |
    15 |
    16 |
    17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/03 Sleek Charts/01 Bubble Chart/src/pageStyles.scss: -------------------------------------------------------------------------------- 1 | $softShadow: hsla(0, 0%, 80%, 0.5); 2 | $softRadius: 3px; 3 | 4 | @font-face { 5 | font-family: "Rajdhani"; 6 | font-style: normal; 7 | font-weight: 400; 8 | src: url('../fonts/rajdhani/Rajdhani-Regular.ttf'); 9 | // I wish we could use online URLs for resources to be bundled. 10 | //src: url('https://fonts.googleapis.com/css?family=Rajdhani'); 11 | } 12 | 13 | body { 14 | min-width: 640px; 15 | font-family: "Rajdhani"; 16 | } 17 | 18 | .flex-container { 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | } 23 | 24 | .flex-item { 25 | //margin-top: 5vmin; 26 | } 27 | 28 | .bubble-chart { 29 | width: calc(100vw - 20vmin); 30 | height: calc(100vh - 28vmin); 31 | min-width: 600px; 32 | min-height: 480px; 33 | margin: 9vmin 10vmin; 34 | box-shadow: 0 0 5vmin $softShadow; 35 | border-radius: $softRadius * 3; 36 | } 37 | 38 | .data-table { 39 | table { 40 | table-layout: auto; 41 | border-collapse: collapse; 42 | text-align: right; 43 | white-space: nowrap; 44 | 45 | th { 46 | color: hsla(0, 0%, 90%, 1); 47 | background-color: hsla(0, 0%, 35%, 1); 48 | text-align: right; 49 | padding-right: $softRadius; 50 | padding-left: 3vw; 51 | 52 | &:first-child { 53 | border-top-left-radius: $softRadius; 54 | border-bottom-left-radius: $softRadius; 55 | } 56 | 57 | &:last-child { 58 | border-top-right-radius: $softRadius; 59 | border-bottom-right-radius: $softRadius; 60 | } 61 | } 62 | 63 | tr:nth-child(even) { 64 | background-color: $softShadow; 65 | } 66 | 67 | td { 68 | padding-right: $softRadius; 69 | 70 | &:first-child { 71 | border-top-left-radius: $softRadius; 72 | border-bottom-left-radius: $softRadius; 73 | } 74 | 75 | &:last-child { 76 | border-top-right-radius: $softRadius; 77 | border-bottom-right-radius: $softRadius; 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /samples/04 maps/00 world_basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /samples/04 maps/00 world_basic/main.js: -------------------------------------------------------------------------------- 1 | var width = 960; 2 | var height = 500; 3 | 4 | var svg = d3.select("body").append("svg") 5 | 6 | // Project choose how to project a point on a sphere (e.g. the earth) 7 | // to a point on a flat surface (e.g. a screen) 8 | var projection = d3.geoMercator() 9 | .scale(width / 2 / Math.PI) 10 | //.scale(100) 11 | .translate([width / 2, height / 2]) 12 | 13 | // Path generator, transform GeoJSON into an SVG path string 14 | var path = d3.geoPath() 15 | .projection(projection); 16 | 17 | // read geojson file from a remote url 18 | // if we use topojson file use to take 80% less space 19 | var url = "http://enjalot.github.io/wwsd/data/world/world-110m.geojson"; 20 | d3.json(url, function(err, geojson) { 21 | svg.append("path") 22 | .attr("d", path(geojson)) 23 | }) -------------------------------------------------------------------------------- /samples/04 maps/00 world_basic/readme.md: -------------------------------------------------------------------------------- 1 | # Getting Started with maps 2 | 3 | Let's start creating maps. 4 | 5 | A good reading to get started: 6 | 7 | http://d3indepth.com/geographic/ 8 | 9 | https://bost.ocks.org/mike/map/ 10 | 11 | https://www.toptal.com/javascript/a-map-to-perfection-using-d3-js-to-make-beautiful-web-maps 12 | 13 | In our samples we will just have the simplfied JSON geo coords entries already cooked and consume it via topojson, 14 | you can find many contributions in github including many different areas converted to json. 15 | 16 | This map is based on the following samples: 17 | http://bl.ocks.org/almccon/fe445f1d6b177fd0946800a48aa59c71 18 | 19 | 20 | # Steps 21 | 22 | - Let's create a basic index.html 23 | 24 | ```html 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ``` 41 | 42 | - Now let's append an svg to the body and define width and height. 43 | 44 | ```javascript 45 | var width = 960; 46 | var height = 500; 47 | 48 | var svg = d3.select("body").append("svg") 49 | ``` 50 | 51 | - Project choose how to project a point on a sphere (e.g. the earth) 52 | to a point on a flat surface (e.g. a screen): 53 | 54 | ```javascript 55 | var projection = d3.geoMercator() 56 | .scale(width / 2 / Math.PI) 57 | //.scale(100) 58 | .translate([width / 2, height / 2]) 59 | ``` 60 | 61 | - Path generator, transform GeoJSON into an SVG path string. 62 | 63 | ```javascript 64 | var path = d3.geoPath() 65 | .projection(projection); 66 | ``` 67 | 68 | - Read geojson file from a remote url, if we use topojson file use to take 80% less space. 69 | 70 | ```javascript 71 | var url = "http://enjalot.github.io/wwsd/data/world/world-110m.geojson"; 72 | d3.json(url, function(err, geojson) { 73 | svg.append("path") 74 | .attr("d", path(geojson)) 75 | }) 76 | ``` 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /samples/04 maps/01 world_interaction/d3-tip.js: -------------------------------------------------------------------------------- 1 | /** 2 | * d3.tip 3 | * Copyright (c) 2013 Justin Palmer 4 | * 5 | * Tooltips for d3.js SVG visualizations 6 | */ 7 | // eslint-disable-next-line no-extra-semi 8 | ;(function(root, factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | // AMD. Register as an anonymous module with d3 as a dependency. 11 | define([ 12 | 'd3-collection', 13 | 'd3-selection' 14 | ], factory) 15 | } else if (typeof module === 'object' && module.exports) { 16 | /* eslint-disable global-require */ 17 | // CommonJS 18 | var d3Collection = require('d3-collection'), 19 | d3Selection = require('d3-selection') 20 | module.exports = factory(d3Collection, d3Selection) 21 | /* eslint-enable global-require */ 22 | } else { 23 | // Browser global. 24 | var d3 = root.d3 25 | // eslint-disable-next-line no-param-reassign 26 | root.d3.tip = factory(d3, d3) 27 | } 28 | }(this, function(d3Collection, d3Selection) { 29 | // Public - contructs a new tooltip 30 | // 31 | // Returns a tip 32 | return function() { 33 | var direction = d3TipDirection, 34 | offset = d3TipOffset, 35 | html = d3TipHTML, 36 | node = initNode(), 37 | svg = null, 38 | point = null, 39 | target = null 40 | 41 | function tip(vis) { 42 | svg = getSVGNode(vis) 43 | if (!svg) return 44 | point = svg.createSVGPoint() 45 | document.body.appendChild(node) 46 | } 47 | 48 | // Public - show the tooltip on the screen 49 | // 50 | // Returns a tip 51 | tip.show = function() { 52 | var args = Array.prototype.slice.call(arguments) 53 | if (args[args.length - 1] instanceof SVGElement) target = args.pop() 54 | 55 | var content = html.apply(this, args), 56 | poffset = offset.apply(this, args), 57 | dir = direction.apply(this, args), 58 | nodel = getNodeEl(), 59 | i = directions.length, 60 | coords, 61 | scrollTop = document.documentElement.scrollTop || 62 | document.body.scrollTop, 63 | scrollLeft = document.documentElement.scrollLeft || 64 | document.body.scrollLeft 65 | 66 | nodel.html(content) 67 | .style('opacity', 1).style('pointer-events', 'all') 68 | 69 | while (i--) nodel.classed(directions[i], false) 70 | coords = directionCallbacks.get(dir).apply(this) 71 | nodel.classed(dir, true) 72 | .style('top', (coords.top + poffset[0]) + scrollTop + 'px') 73 | .style('left', (coords.left + poffset[1]) + scrollLeft + 'px') 74 | 75 | return tip 76 | } 77 | 78 | // Public - hide the tooltip 79 | // 80 | // Returns a tip 81 | tip.hide = function() { 82 | var nodel = getNodeEl() 83 | nodel.style('opacity', 0).style('pointer-events', 'none') 84 | return tip 85 | } 86 | 87 | // Public: Proxy attr calls to the d3 tip container. 88 | // Sets or gets attribute value. 89 | // 90 | // n - name of the attribute 91 | // v - value of the attribute 92 | // 93 | // Returns tip or attribute value 94 | // eslint-disable-next-line no-unused-vars 95 | tip.attr = function(n, v) { 96 | if (arguments.length < 2 && typeof n === 'string') { 97 | return getNodeEl().attr(n) 98 | } 99 | 100 | var args = Array.prototype.slice.call(arguments) 101 | d3Selection.selection.prototype.attr.apply(getNodeEl(), args) 102 | return tip 103 | } 104 | 105 | // Public: Proxy style calls to the d3 tip container. 106 | // Sets or gets a style value. 107 | // 108 | // n - name of the property 109 | // v - value of the property 110 | // 111 | // Returns tip or style property value 112 | // eslint-disable-next-line no-unused-vars 113 | tip.style = function(n, v) { 114 | if (arguments.length < 2 && typeof n === 'string') { 115 | return getNodeEl().style(n) 116 | } 117 | 118 | var args = Array.prototype.slice.call(arguments) 119 | d3Selection.selection.prototype.style.apply(getNodeEl(), args) 120 | return tip 121 | } 122 | 123 | // Public: Set or get the direction of the tooltip 124 | // 125 | // v - One of n(north), s(south), e(east), or w(west), nw(northwest), 126 | // sw(southwest), ne(northeast) or se(southeast) 127 | // 128 | // Returns tip or direction 129 | tip.direction = function(v) { 130 | if (!arguments.length) return direction 131 | direction = v == null ? v : functor(v) 132 | 133 | return tip 134 | } 135 | 136 | // Public: Sets or gets the offset of the tip 137 | // 138 | // v - Array of [x, y] offset 139 | // 140 | // Returns offset or 141 | tip.offset = function(v) { 142 | if (!arguments.length) return offset 143 | offset = v == null ? v : functor(v) 144 | 145 | return tip 146 | } 147 | 148 | // Public: sets or gets the html value of the tooltip 149 | // 150 | // v - String value of the tip 151 | // 152 | // Returns html value or tip 153 | tip.html = function(v) { 154 | if (!arguments.length) return html 155 | html = v == null ? v : functor(v) 156 | 157 | return tip 158 | } 159 | 160 | // Public: destroys the tooltip and removes it from the DOM 161 | // 162 | // Returns a tip 163 | tip.destroy = function() { 164 | if (node) { 165 | getNodeEl().remove() 166 | node = null 167 | } 168 | return tip 169 | } 170 | 171 | function d3TipDirection() { return 'n' } 172 | function d3TipOffset() { return [0, 0] } 173 | function d3TipHTML() { return ' ' } 174 | 175 | var directionCallbacks = d3Collection.map({ 176 | n: directionNorth, 177 | s: directionSouth, 178 | e: directionEast, 179 | w: directionWest, 180 | nw: directionNorthWest, 181 | ne: directionNorthEast, 182 | sw: directionSouthWest, 183 | se: directionSouthEast 184 | }), 185 | directions = directionCallbacks.keys() 186 | 187 | function directionNorth() { 188 | var bbox = getScreenBBox() 189 | return { 190 | top: bbox.n.y - node.offsetHeight, 191 | left: bbox.n.x - node.offsetWidth / 2 192 | } 193 | } 194 | 195 | function directionSouth() { 196 | var bbox = getScreenBBox() 197 | return { 198 | top: bbox.s.y, 199 | left: bbox.s.x - node.offsetWidth / 2 200 | } 201 | } 202 | 203 | function directionEast() { 204 | var bbox = getScreenBBox() 205 | return { 206 | top: bbox.e.y - node.offsetHeight / 2, 207 | left: bbox.e.x 208 | } 209 | } 210 | 211 | function directionWest() { 212 | var bbox = getScreenBBox() 213 | return { 214 | top: bbox.w.y - node.offsetHeight / 2, 215 | left: bbox.w.x - node.offsetWidth 216 | } 217 | } 218 | 219 | function directionNorthWest() { 220 | var bbox = getScreenBBox() 221 | return { 222 | top: bbox.nw.y - node.offsetHeight, 223 | left: bbox.nw.x - node.offsetWidth 224 | } 225 | } 226 | 227 | function directionNorthEast() { 228 | var bbox = getScreenBBox() 229 | return { 230 | top: bbox.ne.y - node.offsetHeight, 231 | left: bbox.ne.x 232 | } 233 | } 234 | 235 | function directionSouthWest() { 236 | var bbox = getScreenBBox() 237 | return { 238 | top: bbox.sw.y, 239 | left: bbox.sw.x - node.offsetWidth 240 | } 241 | } 242 | 243 | function directionSouthEast() { 244 | var bbox = getScreenBBox() 245 | return { 246 | top: bbox.se.y, 247 | left: bbox.se.x 248 | } 249 | } 250 | 251 | function initNode() { 252 | var div = d3Selection.select(document.createElement('div')) 253 | div 254 | .style('position', 'absolute') 255 | .style('top', 0) 256 | .style('opacity', 0) 257 | .style('pointer-events', 'none') 258 | .style('box-sizing', 'border-box') 259 | 260 | return div.node() 261 | } 262 | 263 | function getSVGNode(element) { 264 | var svgNode = element.node() 265 | if (!svgNode) return null 266 | if (svgNode.tagName.toLowerCase() === 'svg') return svgNode 267 | return svgNode.ownerSVGElement 268 | } 269 | 270 | function getNodeEl() { 271 | if (node == null) { 272 | node = initNode() 273 | // re-add node to DOM 274 | document.body.appendChild(node) 275 | } 276 | return d3Selection.select(node) 277 | } 278 | 279 | // Private - gets the screen coordinates of a shape 280 | // 281 | // Given a shape on the screen, will return an SVGPoint for the directions 282 | // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), 283 | // nw(northwest), sw(southwest). 284 | // 285 | // +-+-+ 286 | // | | 287 | // + + 288 | // | | 289 | // +-+-+ 290 | // 291 | // Returns an Object {n, s, e, w, nw, sw, ne, se} 292 | function getScreenBBox() { 293 | var targetel = target || d3Selection.event.target 294 | 295 | while (targetel.getScreenCTM == null && targetel.parentNode == null) { 296 | targetel = targetel.parentNode 297 | } 298 | 299 | var bbox = {}, 300 | matrix = targetel.getScreenCTM(), 301 | tbbox = targetel.getBBox(), 302 | width = tbbox.width, 303 | height = tbbox.height, 304 | x = tbbox.x, 305 | y = tbbox.y 306 | 307 | point.x = x 308 | point.y = y 309 | bbox.nw = point.matrixTransform(matrix) 310 | point.x += width 311 | bbox.ne = point.matrixTransform(matrix) 312 | point.y += height 313 | bbox.se = point.matrixTransform(matrix) 314 | point.x -= width 315 | bbox.sw = point.matrixTransform(matrix) 316 | point.y -= height / 2 317 | bbox.w = point.matrixTransform(matrix) 318 | point.x += width 319 | bbox.e = point.matrixTransform(matrix) 320 | point.x -= width / 2 321 | point.y -= height / 2 322 | bbox.n = point.matrixTransform(matrix) 323 | point.y += height 324 | bbox.s = point.matrixTransform(matrix) 325 | 326 | return bbox 327 | } 328 | 329 | // Private - replace D3JS 3.X d3.functor() function 330 | function functor(v) { 331 | return typeof v === 'function' ? v : function() { 332 | return v 333 | } 334 | } 335 | 336 | return tip 337 | } 338 | // eslint-disable-next-line semi 339 | })); -------------------------------------------------------------------------------- /samples/04 maps/01 world_interaction/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /samples/04 maps/01 world_interaction/main.js: -------------------------------------------------------------------------------- 1 | var format = d3.format(","); 2 | 3 | // Set tooltips 4 | var tip = d3.tip() 5 | .attr('class', 'd3-tip') 6 | .offset([-10, 0]) 7 | .html(function (d) { 8 | return "Country: " + d.properties.name + "
    " + "Population: " + format(d.population) + ""; 9 | }) 10 | 11 | var margin = { top: 0, right: 0, bottom: 0, left: 0 }, 12 | width = 960 - margin.left - margin.right, 13 | height = 500 - margin.top - margin.bottom; 14 | 15 | var color = d3.scaleThreshold() 16 | .domain([10000, 100000, 500000, 1000000, 5000000, 10000000, 50000000, 100000000, 500000000, 1500000000]) 17 | .range(["rgb(247,251,255)", "rgb(222,235,247)", "rgb(198,219,239)", "rgb(158,202,225)", "rgb(107,174,214)", "rgb(66,146,198)", "rgb(33,113,181)", "rgb(8,81,156)", "rgb(8,48,107)", "rgb(3,19,43)"]); 18 | 19 | var path = d3.geoPath(); 20 | 21 | var svg = d3.select("body") 22 | .append("svg") 23 | .attr("width", width) 24 | .attr("height", height) 25 | .append('g') 26 | .attr('class', 'map'); 27 | 28 | var projection = d3.geoMercator() 29 | .scale(130) 30 | .translate([width / 2, height / 1.5]); 31 | 32 | var path = d3.geoPath().projection(projection); 33 | 34 | svg.call(tip); 35 | 36 | // World_countries extracted from: https://raw.githubusercontent.com/jdamiani27/Data-Visualization-and-D3/master/lesson4/world_countries.json 37 | queue() 38 | .defer(d3.json, "world_countries.json") 39 | .defer(d3.tsv, "world_population.tsv") 40 | .await(ready); 41 | 42 | function ready(error, data, population) { 43 | var populationById = {}; 44 | 45 | population.forEach(function (d) { populationById[d.id] = +d.population; }); 46 | data.features.forEach(function (d) { d.population = populationById[d.id] }); 47 | 48 | svg.append("g") 49 | .attr("class", "countries") 50 | .selectAll("path") 51 | .data(data.features) 52 | .enter().append("path") 53 | .attr("d", path) 54 | .style("fill", function (d) { return color(populationById[d.id]); }) 55 | .style('stroke', 'white') 56 | .style('stroke-width', 1.5) 57 | .style("opacity", 0.8) 58 | // tooltips 59 | .style("stroke", "white") 60 | .style('stroke-width', 0.3) 61 | .on('mouseover', function (d) { 62 | tip.show(d); 63 | 64 | d3.select(this) 65 | .style("opacity", 1) 66 | .style("stroke", "white") 67 | .style("stroke-width", 3); 68 | }) 69 | .on('mouseout', function (d) { 70 | tip.hide(d); 71 | 72 | d3.select(this) 73 | .style("opacity", 0.8) 74 | .style("stroke", "white") 75 | .style("stroke-width", 0.3); 76 | }); 77 | 78 | /* 79 | svg.append("path") 80 | .datum(topojson.mesh(data.features, function (a, b) { return a.id !== b.id; })) 81 | 82 | .attr("class", "names") 83 | .attr("d", path); 84 | */ 85 | } 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /samples/04 maps/01 world_interaction/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wolrdint", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "lite-server" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "lite-server": "^2.3.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/04 maps/01 world_interaction/readme.md: -------------------------------------------------------------------------------- 1 | # World Interaction 2 | 3 | In this sample we will draw a world map divided by country and display a tip when the user 4 | hovers over a country. 5 | 6 | This map has been take from the folloing sample: 7 | 8 | http://bl.ocks.org/micahstubbs/8e15870eb432a21f0bc4d3d527b2d14f 9 | 10 | # Steps 11 | 12 | - This time we are going to make ajax requests, we need to setup a lite web server (if not, chrome will block the requests as a 13 | security request). 14 | 15 | - Let's first execute _npm init_ (you will need nodejs installed). 16 | 17 | ```bash 18 | npm init 19 | ``` 20 | 21 | - After filling the data requested in init (remember project name must be lowercase, contain no spaces, and do not collide with well known 22 | library names), we are going to install _lite-server_. 23 | 24 | ``` 25 | npm install lite-server --save-dev 26 | ``` 27 | 28 | - Now let's update our _package.json_ and add a _start_ command. 29 | 30 | _package.json_ 31 | 32 | ```diff 33 | "scripts": { 34 | + "start": "lite-server", 35 | "test": "echo \"Error: no test specified\" && exit 1" 36 | }, 37 | ``` 38 | 39 | - Let's add a basic HTML, this time we will include the 40 | 41 | ```html 42 | 43 | 44 | 45 | 46 | 47 | 48 | ``` 49 | 50 | - We will include the following js/css links: 51 | - [External] d3 v4 52 | - [External] queue v1 (700 bytes gzipped library to manage asynchronous calls): https://github.com/d3/d3-queue 53 | - [External] topojson: library to manage with TopoJson format (small footprint than geojson). 54 | - d3-tip.js: custom javascript file to define better looking tips 55 | - styles.css: styles, mainly to define the tooltip content. 56 | 57 | ```diff 58 | 59 | 60 | 61 | 62 | + 63 | + 64 | + 65 | + 66 | + 67 | + 68 | 69 | 70 | ``` 71 | 72 | - Let's add now the styles css files 73 | 74 | _./styles.css_ 75 | 76 | ```css 77 | .names { 78 | fill: none; 79 | stroke: #fff; 80 | stroke-linejoin: round; 81 | } 82 | 83 | /* Tooltip CSS */ 84 | 85 | .d3-tip { 86 | line-height: 1.5; 87 | font-weight: 400; 88 | font-family: "avenir next", Arial, sans-serif; 89 | padding: 6px; 90 | background: rgba(0, 0, 0, 0.6); 91 | color: #FFA500; 92 | border-radius: 1px; 93 | pointer-events: none; 94 | } 95 | 96 | /* Creates a small triangle extender for the tooltip */ 97 | 98 | .d3-tip:after { 99 | box-sizing: border-box; 100 | display: inline; 101 | font-size: 8px; 102 | width: 100%; 103 | line-height: 1.5; 104 | color: rgba(0, 0, 0, 0.6); 105 | position: absolute; 106 | pointer-events: none; 107 | 108 | } 109 | 110 | /* Northward tooltips */ 111 | 112 | .d3-tip.n:after { 113 | content: "\25BC"; 114 | margin: -1px 0 0 0; 115 | top: 100%; 116 | left: 0; 117 | text-align: center; 118 | } 119 | 120 | /* Eastward tooltips */ 121 | 122 | .d3-tip.e:after { 123 | content: "\25C0"; 124 | margin: -4px 0 0 0; 125 | top: 50%; 126 | left: -8px; 127 | } 128 | 129 | /* Southward tooltips */ 130 | 131 | .d3-tip.s:after { 132 | content: "\25B2"; 133 | margin: 0 0 1px 0; 134 | top: -8px; 135 | left: 0; 136 | text-align: center; 137 | } 138 | 139 | /* Westward tooltips */ 140 | 141 | .d3-tip.w:after { 142 | content: "\25B6"; 143 | margin: -4px 0 0 -1px; 144 | top: 50%; 145 | left: 100%; 146 | } 147 | 148 | /* text{ 149 | pointer-events:none; 150 | }*/ 151 | 152 | .details { 153 | color: white; 154 | } 155 | ``` 156 | 157 | - And the D3-tips file: 158 | 159 | _./d3-tip.js_ 160 | 161 | ```javascript 162 | /** 163 | * d3.tip 164 | * Copyright (c) 2013 Justin Palmer 165 | * 166 | * Tooltips for d3.js SVG visualizations 167 | */ 168 | // eslint-disable-next-line no-extra-semi 169 | ;(function(root, factory) { 170 | if (typeof define === 'function' && define.amd) { 171 | // AMD. Register as an anonymous module with d3 as a dependency. 172 | define([ 173 | 'd3-collection', 174 | 'd3-selection' 175 | ], factory) 176 | } else if (typeof module === 'object' && module.exports) { 177 | /* eslint-disable global-require */ 178 | // CommonJS 179 | var d3Collection = require('d3-collection'), 180 | d3Selection = require('d3-selection') 181 | module.exports = factory(d3Collection, d3Selection) 182 | /* eslint-enable global-require */ 183 | } else { 184 | // Browser global. 185 | var d3 = root.d3 186 | // eslint-disable-next-line no-param-reassign 187 | root.d3.tip = factory(d3, d3) 188 | } 189 | }(this, function(d3Collection, d3Selection) { 190 | // Public - contructs a new tooltip 191 | // 192 | // Returns a tip 193 | return function() { 194 | var direction = d3TipDirection, 195 | offset = d3TipOffset, 196 | html = d3TipHTML, 197 | node = initNode(), 198 | svg = null, 199 | point = null, 200 | target = null 201 | 202 | function tip(vis) { 203 | svg = getSVGNode(vis) 204 | if (!svg) return 205 | point = svg.createSVGPoint() 206 | document.body.appendChild(node) 207 | } 208 | 209 | // Public - show the tooltip on the screen 210 | // 211 | // Returns a tip 212 | tip.show = function() { 213 | var args = Array.prototype.slice.call(arguments) 214 | if (args[args.length - 1] instanceof SVGElement) target = args.pop() 215 | 216 | var content = html.apply(this, args), 217 | poffset = offset.apply(this, args), 218 | dir = direction.apply(this, args), 219 | nodel = getNodeEl(), 220 | i = directions.length, 221 | coords, 222 | scrollTop = document.documentElement.scrollTop || 223 | document.body.scrollTop, 224 | scrollLeft = document.documentElement.scrollLeft || 225 | document.body.scrollLeft 226 | 227 | nodel.html(content) 228 | .style('opacity', 1).style('pointer-events', 'all') 229 | 230 | while (i--) nodel.classed(directions[i], false) 231 | coords = directionCallbacks.get(dir).apply(this) 232 | nodel.classed(dir, true) 233 | .style('top', (coords.top + poffset[0]) + scrollTop + 'px') 234 | .style('left', (coords.left + poffset[1]) + scrollLeft + 'px') 235 | 236 | return tip 237 | } 238 | 239 | // Public - hide the tooltip 240 | // 241 | // Returns a tip 242 | tip.hide = function() { 243 | var nodel = getNodeEl() 244 | nodel.style('opacity', 0).style('pointer-events', 'none') 245 | return tip 246 | } 247 | 248 | // Public: Proxy attr calls to the d3 tip container. 249 | // Sets or gets attribute value. 250 | // 251 | // n - name of the attribute 252 | // v - value of the attribute 253 | // 254 | // Returns tip or attribute value 255 | // eslint-disable-next-line no-unused-vars 256 | tip.attr = function(n, v) { 257 | if (arguments.length < 2 && typeof n === 'string') { 258 | return getNodeEl().attr(n) 259 | } 260 | 261 | var args = Array.prototype.slice.call(arguments) 262 | d3Selection.selection.prototype.attr.apply(getNodeEl(), args) 263 | return tip 264 | } 265 | 266 | // Public: Proxy style calls to the d3 tip container. 267 | // Sets or gets a style value. 268 | // 269 | // n - name of the property 270 | // v - value of the property 271 | // 272 | // Returns tip or style property value 273 | // eslint-disable-next-line no-unused-vars 274 | tip.style = function(n, v) { 275 | if (arguments.length < 2 && typeof n === 'string') { 276 | return getNodeEl().style(n) 277 | } 278 | 279 | var args = Array.prototype.slice.call(arguments) 280 | d3Selection.selection.prototype.style.apply(getNodeEl(), args) 281 | return tip 282 | } 283 | 284 | // Public: Set or get the direction of the tooltip 285 | // 286 | // v - One of n(north), s(south), e(east), or w(west), nw(northwest), 287 | // sw(southwest), ne(northeast) or se(southeast) 288 | // 289 | // Returns tip or direction 290 | tip.direction = function(v) { 291 | if (!arguments.length) return direction 292 | direction = v == null ? v : functor(v) 293 | 294 | return tip 295 | } 296 | 297 | // Public: Sets or gets the offset of the tip 298 | // 299 | // v - Array of [x, y] offset 300 | // 301 | // Returns offset or 302 | tip.offset = function(v) { 303 | if (!arguments.length) return offset 304 | offset = v == null ? v : functor(v) 305 | 306 | return tip 307 | } 308 | 309 | // Public: sets or gets the html value of the tooltip 310 | // 311 | // v - String value of the tip 312 | // 313 | // Returns html value or tip 314 | tip.html = function(v) { 315 | if (!arguments.length) return html 316 | html = v == null ? v : functor(v) 317 | 318 | return tip 319 | } 320 | 321 | // Public: destroys the tooltip and removes it from the DOM 322 | // 323 | // Returns a tip 324 | tip.destroy = function() { 325 | if (node) { 326 | getNodeEl().remove() 327 | node = null 328 | } 329 | return tip 330 | } 331 | 332 | function d3TipDirection() { return 'n' } 333 | function d3TipOffset() { return [0, 0] } 334 | function d3TipHTML() { return ' ' } 335 | 336 | var directionCallbacks = d3Collection.map({ 337 | n: directionNorth, 338 | s: directionSouth, 339 | e: directionEast, 340 | w: directionWest, 341 | nw: directionNorthWest, 342 | ne: directionNorthEast, 343 | sw: directionSouthWest, 344 | se: directionSouthEast 345 | }), 346 | directions = directionCallbacks.keys() 347 | 348 | function directionNorth() { 349 | var bbox = getScreenBBox() 350 | return { 351 | top: bbox.n.y - node.offsetHeight, 352 | left: bbox.n.x - node.offsetWidth / 2 353 | } 354 | } 355 | 356 | function directionSouth() { 357 | var bbox = getScreenBBox() 358 | return { 359 | top: bbox.s.y, 360 | left: bbox.s.x - node.offsetWidth / 2 361 | } 362 | } 363 | 364 | function directionEast() { 365 | var bbox = getScreenBBox() 366 | return { 367 | top: bbox.e.y - node.offsetHeight / 2, 368 | left: bbox.e.x 369 | } 370 | } 371 | 372 | function directionWest() { 373 | var bbox = getScreenBBox() 374 | return { 375 | top: bbox.w.y - node.offsetHeight / 2, 376 | left: bbox.w.x - node.offsetWidth 377 | } 378 | } 379 | 380 | function directionNorthWest() { 381 | var bbox = getScreenBBox() 382 | return { 383 | top: bbox.nw.y - node.offsetHeight, 384 | left: bbox.nw.x - node.offsetWidth 385 | } 386 | } 387 | 388 | function directionNorthEast() { 389 | var bbox = getScreenBBox() 390 | return { 391 | top: bbox.ne.y - node.offsetHeight, 392 | left: bbox.ne.x 393 | } 394 | } 395 | 396 | function directionSouthWest() { 397 | var bbox = getScreenBBox() 398 | return { 399 | top: bbox.sw.y, 400 | left: bbox.sw.x - node.offsetWidth 401 | } 402 | } 403 | 404 | function directionSouthEast() { 405 | var bbox = getScreenBBox() 406 | return { 407 | top: bbox.se.y, 408 | left: bbox.se.x 409 | } 410 | } 411 | 412 | function initNode() { 413 | var div = d3Selection.select(document.createElement('div')) 414 | div 415 | .style('position', 'absolute') 416 | .style('top', 0) 417 | .style('opacity', 0) 418 | .style('pointer-events', 'none') 419 | .style('box-sizing', 'border-box') 420 | 421 | return div.node() 422 | } 423 | 424 | function getSVGNode(element) { 425 | var svgNode = element.node() 426 | if (!svgNode) return null 427 | if (svgNode.tagName.toLowerCase() === 'svg') return svgNode 428 | return svgNode.ownerSVGElement 429 | } 430 | 431 | function getNodeEl() { 432 | if (node == null) { 433 | node = initNode() 434 | // re-add node to DOM 435 | document.body.appendChild(node) 436 | } 437 | return d3Selection.select(node) 438 | } 439 | 440 | // Private - gets the screen coordinates of a shape 441 | // 442 | // Given a shape on the screen, will return an SVGPoint for the directions 443 | // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), 444 | // nw(northwest), sw(southwest). 445 | // 446 | // +-+-+ 447 | // | | 448 | // + + 449 | // | | 450 | // +-+-+ 451 | // 452 | // Returns an Object {n, s, e, w, nw, sw, ne, se} 453 | function getScreenBBox() { 454 | var targetel = target || d3Selection.event.target 455 | 456 | while (targetel.getScreenCTM == null && targetel.parentNode == null) { 457 | targetel = targetel.parentNode 458 | } 459 | 460 | var bbox = {}, 461 | matrix = targetel.getScreenCTM(), 462 | tbbox = targetel.getBBox(), 463 | width = tbbox.width, 464 | height = tbbox.height, 465 | x = tbbox.x, 466 | y = tbbox.y 467 | 468 | point.x = x 469 | point.y = y 470 | bbox.nw = point.matrixTransform(matrix) 471 | point.x += width 472 | bbox.ne = point.matrixTransform(matrix) 473 | point.y += height 474 | bbox.se = point.matrixTransform(matrix) 475 | point.x -= width 476 | bbox.sw = point.matrixTransform(matrix) 477 | point.y -= height / 2 478 | bbox.w = point.matrixTransform(matrix) 479 | point.x += width 480 | bbox.e = point.matrixTransform(matrix) 481 | point.x -= width / 2 482 | point.y -= height / 2 483 | bbox.n = point.matrixTransform(matrix) 484 | point.y += height 485 | bbox.s = point.matrixTransform(matrix) 486 | 487 | return bbox 488 | } 489 | 490 | // Private - replace D3JS 3.X d3.functor() function 491 | function functor(v) { 492 | return typeof v === 'function' ? v : function() { 493 | return v 494 | } 495 | } 496 | 497 | return tip 498 | } 499 | // eslint-disable-next-line semi 500 | })); 501 | ``` 502 | 503 | - Let's get started with the main.js 504 | 505 | - Let's define numeric formatting and the tip to be displayed when we hover over a country: 506 | 507 | _main.js_ 508 | 509 | ```javascript 510 | var format = d3.format(","); 511 | 512 | // Set tooltips 513 | var tip = d3.tip() 514 | .attr('class', 'd3-tip') 515 | .offset([-10, 0]) 516 | .html(function (d) { 517 | return "Country: " + d.properties.name + "
    " + "Population: " + format(d.population) + ""; 518 | }) 519 | ``` 520 | 521 | - Let's define chart margins: 522 | 523 | ```javascript 524 | var margin = { top: 0, right: 0, bottom: 0, left: 0 }, 525 | width = 960 - margin.left - margin.right, 526 | height = 500 - margin.top - margin.bottom; 527 | ``` 528 | 529 | - Let's define a color range scale to map a given country population range with a given background color. 530 | 531 | ```javascript 532 | var color = d3.scaleThreshold() 533 | .domain([10000, 100000, 500000, 1000000, 5000000, 10000000, 50000000, 100000000, 500000000, 1500000000]) 534 | .range(["rgb(247,251,255)", "rgb(222,235,247)", "rgb(198,219,239)", "rgb(158,202,225)", "rgb(107,174,214)", "rgb(66,146,198)", "rgb(33,113,181)", "rgb(8,81,156)", "rgb(8,48,107)", "rgb(3,19,43)"]); 535 | ``` 536 | 537 | - Let's choose the path generator, transform GeoJSON into an SVG path string. 538 | 539 | ```javascript 540 | var path = d3.geoPath(); 541 | ``` 542 | 543 | - Is time to add an SVG to the body,plus a group and add a class map to mark it. 544 | 545 | ```javascript 546 | var svg = d3.select("body") 547 | .append("svg") 548 | .attr("width", width) 549 | .attr("height", height) 550 | .append('g') 551 | .attr('class', 'map'); 552 | ``` 553 | 554 | - Project choose how to project a point on a sphere (e.g. the earth) 555 | to a point on a flat surface (e.g. a screen), plus provide scale + center the chart. 556 | 557 | ```javascript 558 | var projection = d3.geoMercator() 559 | .scale(130) 560 | .translate([width / 2, height / 1.5]); 561 | ``` 562 | 563 | - Let's apply the project and apply the tip component that we have previously created. 564 | 565 | ```javascript 566 | var path = d3.geoPath().projection(projection); 567 | 568 | svg.call(tip); 569 | ``` 570 | 571 | - Load the list of countries (including their geo position, path) and the population per country (json and tsv), 572 | once both calls has been completed it will called the "ready" function that we are about to create. 573 | 574 | Countries topo json: http://bl.ocks.org/micahstubbs/raw/8e15870eb432a21f0bc4d3d527b2d14f/world_countries.json 575 | Population tsv: http://bl.ocks.org/micahstubbs/raw/8e15870eb432a21f0bc4d3d527b2d14f/world_population.tsv 576 | 577 | ```javascript 578 | queue() 579 | .defer(d3.json, "world_countries.json") 580 | .defer(d3.tsv, "world_population.tsv") 581 | .await(ready); 582 | ``` 583 | 584 | - Let's create a function named _ready_ 585 | 586 | ```javascript 587 | function ready(error, data, population) { 588 | } 589 | ``` 590 | 591 | - inside that function let's start plotting the map. Let's first "cook" the data, we want to convert population from string to number (+ trick), 592 | then we want to get a plain _population_ field just to get an easy match when drawing the map. 593 | 594 | ```diff 595 | function ready(error, data, population) { 596 | + var populationById = {}; 597 | 598 | + population.forEach(function (d) { populationById[d.id] = +d.population; }); 599 | + data.features.forEach(function (d) { d.population = populationById[d.id] }); 600 | } 601 | ``` 602 | 603 | - Let's continue working inside the _ready_ function and adding more lines. Now we are going to 604 | append to the svg a group, plot the chart including the data, styling and hooking to on mouseover 605 | and out to highlight the map that is being selected. 606 | 607 | ``` 608 | svg.append("g") 609 | .attr("class", "countries") 610 | .selectAll("path") 611 | .data(data.features) 612 | .enter().append("path") 613 | .attr("d", path) 614 | .style("fill", function (d) { return color(populationById[d.id]); }) 615 | .style('stroke', 'white') 616 | .style('stroke-width', 1.5) 617 | .style("opacity", 0.8) 618 | // tooltips 619 | .style("stroke", "white") 620 | .style('stroke-width', 0.3) 621 | .on('mouseover', function (d) { 622 | tip.show(d); 623 | 624 | d3.select(this) 625 | .style("opacity", 1) 626 | .style("stroke", "white") 627 | .style("stroke-width", 3); 628 | }) 629 | .on('mouseout', function (d) { 630 | tip.hide(d); 631 | 632 | d3.select(this) 633 | .style("opacity", 0.8) 634 | .style("stroke", "white") 635 | .style("stroke-width", 0.3); 636 | }); 637 | ``` 638 | 639 | - Let's start the sample 640 | 641 | ```bash 642 | npm start 643 | ``` 644 | 645 | > As an excercise it would be great to apply clean code principles, this main.js should be something like (pseudocode): 646 | 647 | ```javascript 648 | configureMap(); 649 | loadData(ready); 650 | 651 | const ready = (error, geoData, populationData) => { 652 | // This could be enclosed in a method as well called 653 | // CookData or ConvertDataInfoChartFormat or MassageDataIntoChartFriendlyFormat 654 | populationData = convertPopulationIntoNumber(); 655 | geoData = addPopulationToGeoData(populationData); 656 | 657 | const svg = createMapSvgWithStyles(); 658 | hookeMouseEvents(svg); 659 | } 660 | ``` -------------------------------------------------------------------------------- /samples/04 maps/01 world_interaction/world_population.tsv: -------------------------------------------------------------------------------- 1 | id name population 2 | CHN China "1330141295" 3 | IND India "1173108018" 4 | USA United States "310232863" 5 | IDN Indonesia "242968342" 6 | BRA Brazil "201103330" 7 | PAK Pakistan "177276594" 8 | BGD Bangladesh "158065841" 9 | NGA Nigeria "152217341" 10 | RUS Russia "139390205" 11 | JPN Japan "126804433" 12 | MEX Mexico "112468855" 13 | PHL Philippines "99900177" 14 | VNM Vietnam "89571130" 15 | ETH Ethiopia "88013491" 16 | DEU Germany "82282988" 17 | EGY Egypt "80471869" 18 | TUR Turkey "77804122" 19 | COD "Congo, Democratic Republic of the" "70916439" 20 | IRN Iran "67037517" 21 | THA Thailand "66404688" 22 | FRA France "64057792" 23 | GBR United Kingdom "61284806" 24 | ITA Italy "58090681" 25 | MMR Burma "53414374" 26 | ZAF South Africa "49109107" 27 | KOR "Korea, South" "48636068" 28 | UKR Ukraine "45415596" 29 | COL Colombia "44205293" 30 | SDN Sudan "41980182" 31 | TZA Tanzania "41892895" 32 | ARG Argentina "41343201" 33 | ESP Spain "40548753" 34 | KEN Kenya "40046566" 35 | POL Poland "38463689" 36 | DZA Algeria "34586184" 37 | CAN Canada "33759742" 38 | UGA Uganda "33398682" 39 | MAR Morocco "31627428" 40 | PER Peru "29907003" 41 | IRQ Iraq "29671605" 42 | SAU Saudi Arabia "29207277" 43 | AFG Afghanistan "29121286" 44 | NPL Nepal "28951852" 45 | UZB Uzbekistan "27865738" 46 | VEN Venezuela "27223228" 47 | MYS Malaysia "26160256" 48 | GHA Ghana "24339838" 49 | YEM Yemen "23495361" 50 | TWN Taiwan "23024956" 51 | PRK "Korea, North" "22757275" 52 | SYR Syria "22198110" 53 | ROU Romania "22181287" 54 | MOZ Mozambique "22061451" 55 | AUS Australia "21515754" 56 | LKA Sri Lanka "21513990" 57 | MDG Madagascar "21281844" 58 | CIV Cote d'Ivoire "21058798" 59 | CMR Cameroon "19294149" 60 | NLD Netherlands "16783092" 61 | CHL Chile "16746491" 62 | BFA Burkina Faso "16241811" 63 | NER Niger "15878271" 64 | KAZ Kazakhstan "15460484" 65 | MWI Malawi "15447500" 66 | ECU Ecuador "14790608" 67 | KHM Cambodia "14753320" 68 | SEN Senegal "14086103" 69 | MLI Mali "13796354" 70 | GTM Guatemala "13550440" 71 | AGO Angola "13068161" 72 | ZMB Zambia "12056923" 73 | ZWE Zimbabwe "11651858" 74 | CUB Cuba "11477459" 75 | RWA Rwanda "11055976" 76 | GRC Greece "10749943" 77 | PRT Portugal "10735765" 78 | TUN Tunisia "10589025" 79 | TCD Chad "10543464" 80 | BEL Belgium "10423493" 81 | GIN Guinea "10324025" 82 | CZE Czech Republic "10201707" 83 | SOM Somalia "10112453" 84 | BOL Bolivia "9947418" 85 | HUN Hungary "9880059" 86 | BDI Burundi "9863117" 87 | DOM Dominican Republic "9794487" 88 | BLR Belarus "9612632" 89 | HTI Haiti "9203083" 90 | SWE Sweden "9074055" 91 | BEN Benin "9056010" 92 | AZE Azerbaijan "8303512" 93 | AUT Austria "8214160" 94 | HND Honduras "7989415" 95 | CHE Switzerland "7623438" 96 | TJK Tajikistan "7487489" 97 | ISR Israel "7353985" 98 | SRB Serbia "7344847" 99 | BGR Bulgaria "7148785" 100 | HKG Hong Kong "7089705" 101 | LAO Laos "6993767" 102 | LBY Libya "6461454" 103 | JOR Jordan "6407085" 104 | PRY Paraguay "6375830" 105 | TGO Togo "6199841" 106 | PNG Papua New Guinea "6064515" 107 | SLV El Salvador "6052064" 108 | NIC Nicaragua "5995928" 109 | ERI Eritrea "5792984" 110 | DNK Denmark "5515575" 111 | KGZ Kyrgyzstan "5508626" 112 | SVK Slovakia "5470306" 113 | FIN Finland "5255068" 114 | SLE Sierra Leone "5245695" 115 | ARE United Arab Emirates "4975593" 116 | TKM Turkmenistan "4940916" 117 | CAF Central African Republic "4844927" 118 | SGP Singapore "4701069" not listed 119 | NOR Norway "4676305" 120 | BIH Bosnia and Herzegovina "4621598" 121 | GEO Georgia "4600825" 122 | CRI Costa Rica "4516220" 123 | HRV Croatia "4486881" 124 | MDA Moldova "4317483" 125 | NZL New Zealand "4252277" 126 | IRL Ireland "4250163" 127 | COG "Congo, Republic of the" "4125916" 128 | LBN Lebanon "4125247" 129 | PRI Puerto Rico "3977663" 130 | LBR Liberia "3685076" 131 | ALB Albania "3659616" 132 | LTU Lithuania "3545319" 133 | URY Uruguay "3510386" 134 | PAN Panama "3410676" 135 | MRT Mauritania "3205060" 136 | MNG Mongolia "3086918" 137 | OMN Oman "2967717" 138 | ARM Armenia "2966802" 139 | JAM Jamaica "2847232" 140 | KWT Kuwait "2789132" 141 | PSE West Bank "2514845" 142 | LVA Latvia "2217969" 143 | NAM Namibia "2128471" 144 | MKD Macedonia "2072086" 145 | BWA Botswana "2029307" 146 | SVN Slovenia "2003136" 147 | LSO Lesotho "1919552" 148 | GMB "Gambia, The" "1824158" 149 | KWT Kosovo "1815048" 150 | 149 Gaza Strip "1604238" not listed 151 | GNB Guinea-Bissau "1565126" 152 | GAB Gabon "1545255" 153 | SWZ Swaziland "1354051" 154 | 153 Mauritius "1294104" not listed 155 | EST Estonia "1291170" 156 | TTO Trinidad and Tobago "1228691" 157 | TLS Timor-Leste "1154625" 158 | CYP Cyprus "1102677" 159 | FJI Fiji "957780" 160 | QAT Qatar "840926" 161 | 160 Comoros "773407" not listed 162 | GUY Guyana "748486" 163 | DJI Djibouti "740528" 164 | 163 Bahrain "738004" not listed 165 | BTN Bhutan "699847" 166 | MNE Montenegro "666730" 167 | GNQ Equatorial Guinea "650702" 168 | SLB Solomon Islands "609794" 169 | 168 Macau "567957" not listed 170 | 169 Cape Verde "508659" not listed 171 | LUX Luxembourg "497538" 172 | ESH Western Sahara "491519" 173 | SUR Suriname "486618" 174 | 173 Malta "406771" not listed 175 | 174 Maldives "395650" not listed 176 | BRN Brunei "395027" 177 | BLZ Belize "314522" 178 | BHS "Bahamas, The" "310426" 179 | ISL Iceland "308910" 180 | 179 French Polynesia "291000" not listed 181 | 180 Barbados "285653" not listed 182 | 181 Mayotte "231139" not listed 183 | NCL New Caledonia "229993" 184 | 183 Netherlands Antilles "228693" not listed 185 | VUT Vanuatu "221552" 186 | 185 Samoa "192001" not listed 187 | 186 Sao Tome and Principe "175808" not listed 188 | 187 Saint Lucia "160922" not listed 189 | 188 Tonga "122580" not listed 190 | 189 Virgin Islands "109775" not listed 191 | 190 Grenada "107818" not listed 192 | 191 "Micronesia, Federated States of" "107154" not listed 193 | 192 Aruba "104589" not listed 194 | 193 Saint Vincent and the Grenadines "104217" not listed 195 | 194 Kiribati "99482" not listed 196 | 195 Jersey "91812" not listed 197 | 196 Seychelles "88340" not listed 198 | 197 Antigua and Barbuda "86754" not listed 199 | 198 Andorra "84525" not listed 200 | 199 Isle of Man "76913" not listed 201 | DOM Dominica "72813" 202 | 201 Bermuda "68268" not listed 203 | 202 American Samoa "66432" not listed 204 | 203 Marshall Islands "65859" not listed 205 | 204 Guernsey "65632" not listed 206 | GRL Greenland "57637" 207 | 206 Cayman Islands "50209" not listed 208 | 207 Saint Kitts and Nevis "49898" not listed 209 | 208 Faroe Islands "49057" not listed 210 | 209 Northern Mariana Islands "48317" not listed 211 | 210 Liechtenstein "35002" not listed 212 | 211 San Marino "31477" not listed 213 | 212 Monaco "30586" not listed 214 | 213 Saint Martin "30235" not listed 215 | 214 Gibraltar "28877" not listed 216 | 215 British Virgin Islands "24939" not listed 217 | 216 Turks and Caicos Islands "23528" not listed 218 | 217 Palau "20879" not listed 219 | 218 Akrotiri "15700" not listed 220 | 219 Dhekelia "15700" not listed 221 | 220 Wallis and Futuna "15343" not listed 222 | 221 Anguilla "14764" not listed 223 | 222 Nauru "14264" not listed 224 | 223 Cook Islands "11488" not listed 225 | 224 Tuvalu "10472" not listed 226 | 225 "Saint Helena, Ascension, and Tristan da Cunha" "7670" not listed 227 | 226 Saint Barthelemy "7406" not listed 228 | 227 Saint Pierre and Miquelon "6010" not listed 229 | 228 Montserrat "5118" not listed 230 | FLK Falkland Islands (Islas Malvinas) "3140" 231 | 230 Norfolk Island "2155" not listed 232 | 231 Svalbard "2067" not listed 233 | 232 Christmas Island "1402" not listed 234 | 233 Tokelau "1400" not listed 235 | 234 Niue "1354" not listed 236 | 235 Holy See (Vatican City) 829 not listed 237 | 236 Cocos (Keeling) Islands 596 not listed 238 | 237 Pitcairn Islands 48 not listed 239 | ATA Antarctica 0 240 | ATF French Southern and Antarctic Lands 0 241 | SDS South Sudan "12152321" 242 | ABV Somaliland "3500000" 243 | OSA Kosovo "1824000" -------------------------------------------------------------------------------- /samples/04 maps/02 spain/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /samples/04 maps/02 spain/main.js: -------------------------------------------------------------------------------- 1 | var cities = [ 2 | { 3 | name: "Madrid", 4 | coordinates: [-3.723472, 40.429348] 5 | }, 6 | { name: "Barcelona", coordinates: [2.18559, 41.394579] }, 7 | { name: "Bilbao", coordinates: [-2.930737, 43.282435] }, 8 | { name: "Valencia", coordinates: [-0.33419, 39.494676] }, 9 | { name: "Seville", coordinates: [-5.990362, 37.389681] }, 10 | { name: "Santiago", coordinates: [-8.544953, 42.906538] }, 11 | { 12 | name: "Santa Cruz de Tenerife", 13 | coordinates: [-16.251692, 28.46326] 14 | } 15 | ]; 16 | 17 | var hover = null; 18 | var mouseX, mouseY; 19 | 20 | var width = 960; 21 | var height = 630; 22 | 23 | var color = d3.scale 24 | .threshold() 25 | .domain([5, 9, 19, 49, 74, 124, 249, 499, 1000]) 26 | .range([ 27 | "#FEFCED", 28 | "#FFF8D9", 29 | "#FEF5BD", 30 | "#FFEDB8", 31 | "#FDE3B3", 32 | "#F8CFAA", 33 | "#F1A893", 34 | "#E47479", 35 | "#C03F58", 36 | "#760420" 37 | ]); 38 | 39 | var format = d3.format(",4"); 40 | 41 | var tooltip = d3 42 | .select("body") 43 | .append("div") 44 | .attr("class", "g-tooltip") 45 | .style("opacity", 0); 46 | 47 | document.onmousemove = handleMouseMove; 48 | 49 | function handleMouseMove(event) { 50 | mouseX = event.pageX; 51 | mouseY = event.pageY; 52 | 53 | tooltip.style("left", mouseX - 100 + "px").style("top", mouseY + 25 + "px"); 54 | } 55 | 56 | var legend = d3 57 | .select("body") 58 | .append("div") 59 | .attr("class", "g-legend") 60 | .append("span") 61 | .text("People per km2") 62 | .attr("class", "g-legendText"); 63 | 64 | var legendList = d3 65 | .select(".g-legend") 66 | .append("ul") 67 | .attr("class", "list-inline"); 68 | 69 | var keys = legendList.selectAll("li.key").data(color.range()); 70 | 71 | keys 72 | .enter() 73 | .append("li") 74 | .attr("class", "key") 75 | .style("border-top-color", String) 76 | .text(function (d) { 77 | var r = color.invertExtent(d); 78 | return r[0]; 79 | }); 80 | 81 | d3.json("municipios.json", function (error, d) { 82 | d3.json("ccaa.json", function (error, ccaa) { 83 | topojson.presimplify(d); 84 | topojson.presimplify(ccaa); 85 | 86 | var map = new ZoomableCanvasMap({ 87 | element: "body", 88 | zoomScale: 0.8, 89 | width: width, 90 | height: height, 91 | projection: d3.geo 92 | .conicConformalSpain() 93 | .translate([width / 2 + 300, height / 2 + 100]) 94 | .scale(960 * 4.3), 95 | data: [ 96 | { 97 | features: topojson.feature(d, d.objects["municipios"]), 98 | static: { 99 | paintfeature: function (parameters, d) { 100 | parameters.context.fillStyle = color(d.properties.rate); 101 | parameters.context.fill(); 102 | }, 103 | postpaint: function (parameters) { 104 | var p = new Path2D(parameters.projection.getCompositionBorders()); 105 | 106 | parameters.context.lineWidth = 0.5; 107 | parameters.context.strokeStyle = "#555"; 108 | parameters.context.stroke(p); 109 | } 110 | }, 111 | dynamic: { 112 | postpaint: function (parameters) { 113 | if (!hover) { 114 | tooltip.style("opacity", 0); 115 | return; 116 | } 117 | 118 | parameters.context.beginPath(); 119 | parameters.context.lineWidth = 1.5 / parameters.scale; 120 | parameters.path(hover); 121 | parameters.context.stroke(); 122 | 123 | tooltip 124 | .style("opacity", 1) 125 | .html( 126 | "
    " + 127 | "" + 128 | hover.properties.name + 129 | "" + 130 | "
    " + 131 | "Density" + 132 | "" + 133 | format(hover.properties.rate) + 134 | " per km2" 135 | ); 136 | } 137 | }, 138 | events: { 139 | hover: function (parameters, d) { 140 | hover = d; 141 | 142 | parameters.map.paint(); 143 | } 144 | } 145 | }, 146 | { 147 | features: topojson.feature(ccaa, ccaa.objects["ccaa"]), 148 | static: { 149 | paintfeature: function (parameters, d) { 150 | parameters.context.lineWidth = 0.5 / parameters.scale; 151 | parameters.context.strokeStyle = "rgb(130,130,130)"; 152 | parameters.context.stroke(); 153 | }, 154 | postpaint: function (parameters) { 155 | for (var i in cities) { 156 | // Assign the cities to a variable for a cleaner access 157 | var city = cities[i]; 158 | 159 | // Project the coordinates into our Canvas map 160 | var projectedPoint = parameters.projection(city.coordinates); 161 | 162 | // Create the label dot 163 | parameters.context.beginPath(); 164 | 165 | parameters.context.arc( 166 | projectedPoint[0], 167 | projectedPoint[1], 168 | 2 / parameters.scale, 169 | 0, 170 | 2 * Math.PI, 171 | true 172 | ); 173 | 174 | // Font properties 175 | var fontSize = 11 / parameters.scale; 176 | parameters.context.textAlign = "center"; 177 | parameters.context.font = fontSize + "px sans-serif"; 178 | 179 | // Create the text shadow 180 | parameters.context.shadowColor = "black"; 181 | parameters.context.shadowBlur = 5; 182 | parameters.context.lineWidth = 1 / parameters.scale; 183 | parameters.context.strokeText( 184 | city.name, 185 | projectedPoint[0], 186 | projectedPoint[1] - 7 / parameters.scale 187 | ); 188 | 189 | // Paint the labels 190 | parameters.context.fillStyle = "white"; 191 | parameters.context.fillText( 192 | city.name, 193 | projectedPoint[0], 194 | projectedPoint[1] - 7 / parameters.scale 195 | ); 196 | 197 | parameters.context.fill(); 198 | } 199 | } 200 | }, 201 | events: { 202 | click: function (parameters, d) { 203 | parameters.map.zoom(d); 204 | } 205 | } 206 | } 207 | ] 208 | }); 209 | map.init(); 210 | }); 211 | }); -------------------------------------------------------------------------------- /samples/04 maps/02 spain/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wolrdint", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "lite-server" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "lite-server": "^2.3.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/04 maps/02 spain/readme.md: -------------------------------------------------------------------------------- 1 | # Population density 2 | 3 | Let's start with a local map (Spain) and display population per "municipality". 4 | 5 | In this sample we will learn: 6 | - Where to find and how to display a country map. 7 | - Use a color range (plus legend). 8 | - Zoom on areas. 9 | - Place elements in a map given it's coordinates. 10 | - Display countries distant lands together (canary islands). 11 | 12 | Based on the following sample: https://bl.ocks.org/martgnz/77d30f5adf890ef7465c 13 | 14 | # Steps 15 | 16 | - This time we are going to make ajax requests, we need to setup a lite web server (if not, chrome will block the requests as a 17 | security request). 18 | 19 | - Let's first execute _npm init_ (you will need nodejs installed). 20 | 21 | ```bash 22 | npm init 23 | ``` 24 | 25 | - After filling the data requested in init (remember project name must be lowercase, contain no spaces, and do not collide with well known 26 | library names), we are going to install _lite-server_. 27 | 28 | ``` 29 | npm install lite-server --save-dev 30 | ``` 31 | 32 | - First let's create a basic index page. 33 | 34 | ```html 35 | 36 | 37 | 38 | 39 | 40 | 41 | ``` 42 | 43 | - We will include the following js/css links: 44 | - [External] d3 v3 45 | - [External] topojson: library to manage with TopoJson format (small footprint than geojson). 46 | - [External] rbush: RBush is a high-performance JavaScript library for 2D spatial indexing of points and rectangles 47 | (https://github.com/mourner/rbush) 48 | - [External] d3-composite-projections: Set of d3 projectins for showing countries' distant lands togehter (e.g. canary islands) 49 | https://github.com/rveciana/d3-composite-projections 50 | - [External] spamjs: A library that allow us to create zoomable maps https://github.com/newsappsio/spam 51 | - styles.css: styles, mainly to define the tooltip content. 52 | 53 | > As an excercise try to migrate this project from d3 v3 to v4 (you will find that some third partie libraries 54 | could break, time to fork a project port it and make a pull request). 55 | 56 | ```diff 57 | 58 | 59 | 60 | 61 | + 62 | + 63 | + 64 | + 65 | + 66 | + 67 | + 68 | 69 | 70 | ``` 71 | 72 | - Let's add now the styles css files 73 | 74 | _./styles.css_ 75 | 76 | ```css 77 | body { 78 | max-width: 960px; 79 | position: relative; 80 | } 81 | 82 | .g-tooltip { 83 | font-family: "Helvetica Neue", "Helvetica", Arial sans-serif; 84 | position: absolute; 85 | width: 200px; 86 | min-height: 50px; 87 | padding: 8px; 88 | font-size: 12px; 89 | background: rgb(255, 255, 255); 90 | box-shadow: 0 3px 5px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(0, 0, 0, .08); 91 | border-radius: 2px; 92 | pointer-events: none; 93 | z-index: 1; 94 | } 95 | 96 | .g-place { 97 | border-bottom: 1px solid rgb(130, 130, 130); 98 | margin-bottom: 5px; 99 | padding-bottom: 3px; 100 | } 101 | 102 | .g-headline { 103 | font-size: 16px; 104 | } 105 | 106 | .g-sub { 107 | color: rgb(130, 130, 130); 108 | } 109 | 110 | .g-value { 111 | float: right; 112 | } 113 | 114 | .g-legend { 115 | font-family: "Helvetica Neue", "Helvetica", Arial sans-serif; 116 | width: 300px; 117 | position: absolute; 118 | padding: 10px; 119 | bottom: 0; 120 | right: 0; 121 | background: rgba(255, 255, 255, 0.8); 122 | border-radius: 2px; 123 | } 124 | 125 | .g-legendText { 126 | font-size: 12px; 127 | } 128 | 129 | .list-inline { 130 | padding-left: 0; 131 | list-style: none 132 | } 133 | 134 | ul { 135 | margin: 5px auto; 136 | } 137 | 138 | li { 139 | display: inline-block; 140 | } 141 | 142 | li.key { 143 | border-top-width: 10px; 144 | border-top-style: solid; 145 | font-size: 10px; 146 | width: 10%; 147 | height: 10px; 148 | padding-left: 0; 149 | padding-right: 0; 150 | } 151 | ``` 152 | 153 | - Now it's time to jump into the _main.js_ file 154 | 155 | - First we will create an array of additional points to display (some main cities locations): 156 | 157 | _main.js_ 158 | 159 | ```javascript 160 | var cities = [ 161 | { 162 | name: "Madrid", 163 | coordinates: [-3.723472, 40.429348] 164 | }, 165 | { name: "Barcelona", coordinates: [2.18559, 41.394579] }, 166 | { name: "Bilbao", coordinates: [-2.930737, 43.282435] }, 167 | { name: "Valencia", coordinates: [-0.33419, 39.494676] }, 168 | { name: "Seville", coordinates: [-5.990362, 37.389681] }, 169 | { name: "Santiago", coordinates: [-8.544953, 42.906538] }, 170 | { 171 | name: "Santa Cruz de Tenerife", 172 | coordinates: [-16.251692, 28.46326] 173 | } 174 | ]; 175 | ``` 176 | 177 | > As an excercise add Málaga and Alicante to the map. 178 | 179 | - Let's add some declarations (hover callback, mouse coords, widht and height of the canvas) 180 | 181 | _main.js_ 182 | 183 | ```javascript 184 | var hover = null; 185 | var mouseX, mouseY; 186 | 187 | var width = 960; 188 | var height = 630; 189 | ``` 190 | 191 | - Let's define a range of colors per range of population, and a numeric format. 192 | 193 | _main.js_ 194 | 195 | ```javascript 196 | var color = d3.scale 197 | .threshold() 198 | .domain([5, 9, 19, 49, 74, 124, 249, 499, 1000]) 199 | .range([ 200 | "#FEFCED", 201 | "#FFF8D9", 202 | "#FEF5BD", 203 | "#FFEDB8", 204 | "#FDE3B3", 205 | "#F8CFAA", 206 | "#F1A893", 207 | "#E47479", 208 | "#C03F58", 209 | "#760420" 210 | ]); 211 | 212 | var format = d3.format(",4"); 213 | ``` 214 | 215 | - Let's add a tooltip (it will hook under the body dom item) 216 | 217 | ```javascript 218 | var tooltip = d3 219 | .select("body") 220 | .append("div") 221 | .attr("class", "g-tooltip") 222 | .style("opacity", 0); 223 | ``` 224 | 225 | - Let's hook on to the dom mouse move event and change the position of the tooltip when the mouse coords are changing. 226 | 227 | ```javascript 228 | document.onmousemove = handleMouseMove; 229 | 230 | function handleMouseMove(event) { 231 | mouseX = event.pageX; 232 | mouseY = event.pageY; 233 | 234 | tooltip.style("left", mouseX - 100 + "px").style("top", mouseY + 25 + "px"); 235 | } 236 | ``` 237 | 238 | - Lets define a chart legend (styled li including the range colors) 239 | 240 | ```javascript 241 | var legend = d3 242 | .select("body") 243 | .append("div") 244 | .attr("class", "g-legend") 245 | .append("span") 246 | .text("People per km2") 247 | .attr("class", "g-legendText"); 248 | 249 | var legendList = d3 250 | .select(".g-legend") 251 | .append("ul") 252 | .attr("class", "list-inline"); 253 | 254 | var keys = legendList.selectAll("li.key").data(color.range()); 255 | 256 | keys 257 | .enter() 258 | .append("li") 259 | .attr("class", "key") 260 | .style("border-top-color", String) 261 | .text(function (d) { 262 | var r = color.invertExtent(d); 263 | return r[0]; 264 | }); 265 | ``` 266 | 267 | - Let's load the geo info (name + path) of each municipality (_municipio_), plus the geo info (name + path) of each 268 | regin (_comunidad autonoma_), we can find this json files in the following urls: 269 | 270 | - Municipalities json: https://bl.ocks.org/martgnz/raw/77d30f5adf890ef7465c/municipios.json 271 | - Regions json: https://bl.ocks.org/martgnz/raw/77d30f5adf890ef7465c/ccaa.json 272 | 273 | 274 | ```javascript 275 | d3.json("municipios.json", function (error, d) { 276 | d3.json("ccaa.json", function (error, ccaa) { 277 | }) 278 | }); 279 | ``` 280 | 281 | > This could be chained and use await as we saw in the previous sample (world_interaction) as an excercise try to refactor this code. 282 | 283 | - Inside both functions calls, let's keep on implement it (plotting the chart), let's prepare TopoJSON for simplificatin 284 | 285 | ```diff 286 | d3.json("municipios.json", function (error, d) { 287 | d3.json("ccaa.json", function (error, ccaa) { 288 | + topojson.presimplify(d); 289 | + topojson.presimplify(ccaa); 290 | }) 291 | }); 292 | ``` 293 | 294 | - Now we will chop each call of the canvas (**do not paste it step by step just, at the end this readme we will provide the complete chunk, 295 | this is just for explanation purpose**) 296 | 297 | Creating a Zoomable canvas map, and adding conic conformal projection that show the canary Islands next to the iberian peninsula. 298 | 299 | _DO NOT COPY PASTE THIS CODE, YOU CAN FIND WHOLE PIECE OF CODE AT THE BOOTOM OF THIS README_ 300 | 301 | ```javascript 302 | var map = new ZoomableCanvasMap({ 303 | element: "body", 304 | zoomScale: 0.8, 305 | width: width, 306 | height: height, 307 | projection: d3.geo 308 | .conicConformalSpain() 309 | .translate([width / 2 + 300, height / 2 + 100]) 310 | .scale(960 * 4.3), 311 | ``` 312 | 313 | - Let's paint the municipalities shapes, we will provide a background based on rate, and after render we will paint the 314 | borders of each municipality (it will be displayed on hover) 315 | 316 | _DO NOT COPY PASTE THIS CODE, YOU CAN FIND WHOLE PIECE OF CODE AT THE BOOTOM OF THIS README_ 317 | ```javascript 318 | data: [ 319 | { 320 | features: topojson.feature(d, d.objects["municipios"]), 321 | static: { 322 | paintfeature: function (parameters, d) { 323 | parameters.context.fillStyle = color(d.properties.rate); 324 | parameters.context.fill(); 325 | }, 326 | postpaint: function (parameters) { 327 | var p = new Path2D(parameters.projection.getCompositionBorders()); 328 | 329 | parameters.context.lineWidth = 0.5; 330 | parameters.context.strokeStyle = "#555"; 331 | parameters.context.stroke(p); 332 | } 333 | }, 334 | ``` 335 | 336 | - Let's add tooltip support: 337 | 338 | _DO NOT COPY PASTE THIS CODE, YOU CAN FIND WHOLE PIECE OF CODE AT THE BOOTOM OF THIS README_ 339 | 340 | ```javascript 341 | dynamic: { 342 | postpaint: function (parameters) { 343 | if (!hover) { 344 | tooltip.style("opacity", 0); 345 | return; 346 | } 347 | 348 | parameters.context.beginPath(); 349 | parameters.context.lineWidth = 1.5 / parameters.scale; 350 | parameters.path(hover); 351 | parameters.context.stroke(); 352 | 353 | tooltip 354 | .style("opacity", 1) 355 | .html( 356 | "
    " + 357 | "" + 358 | hover.properties.name + 359 | "" + 360 | "
    " + 361 | "Density" + 362 | "" + 363 | format(hover.properties.rate) + 364 | " per km2" 365 | ); 366 | } 367 | }, 368 | 369 | ``` 370 | 371 | - On mouse hover let's repaint the map (paint broders plus updated tooltip): 372 | 373 | _DO NOT COPY PASTE THIS CODE, YOU CAN FIND WHOLE PIECE OF CODE AT THE BOOTOM OF THIS README_ 374 | ```javascript 375 | events: { 376 | hover: function (parameters, d) { 377 | hover = d; 378 | 379 | parameters.map.paint(); 380 | } 381 | } 382 | ``` 383 | 384 | - 385 | 386 | - Now let's paint the regions: 387 | 388 | _DO NOT COPY PASTE THIS CODE, YOU CAN FIND WHOLE PIECE OF CODE AT THE BOOTOM OF THIS README_ 389 | ```javascript 390 | { 391 | features: topojson.feature(ccaa, ccaa.objects["ccaa"]), 392 | static: { 393 | paintfeature: function (parameters, d) { 394 | parameters.context.lineWidth = 0.5 / parameters.scale; 395 | parameters.context.strokeStyle = "rgb(130,130,130)"; 396 | parameters.context.stroke(); 397 | }, 398 | ``` 399 | 400 | - And finally let's pain the list of main cities: 401 | 402 | _DO NOT COPY PASTE THIS CODE, YOU CAN FIND WHOLE PIECE OF CODE AT THE BOOTOM OF THIS README_ 403 | ```javascript 404 | postpaint: function (parameters) { 405 | for (var i in cities) { 406 | // Assign the cities to a variable for a cleaner access 407 | var city = cities[i]; 408 | 409 | // Project the coordinates into our Canvas map 410 | var projectedPoint = parameters.projection(city.coordinates); 411 | 412 | // Create the label dot 413 | parameters.context.beginPath(); 414 | 415 | parameters.context.arc( 416 | projectedPoint[0], 417 | projectedPoint[1], 418 | 2 / parameters.scale, 419 | 0, 420 | 2 * Math.PI, 421 | true 422 | ); 423 | 424 | // Font properties 425 | var fontSize = 11 / parameters.scale; 426 | parameters.context.textAlign = "center"; 427 | parameters.context.font = fontSize + "px sans-serif"; 428 | 429 | // Create the text shadow 430 | parameters.context.shadowColor = "black"; 431 | parameters.context.shadowBlur = 5; 432 | parameters.context.lineWidth = 1 / parameters.scale; 433 | parameters.context.strokeText( 434 | city.name, 435 | projectedPoint[0], 436 | projectedPoint[1] - 7 / parameters.scale 437 | ); 438 | 439 | // Paint the labels 440 | parameters.context.fillStyle = "white"; 441 | parameters.context.fillText( 442 | city.name, 443 | projectedPoint[0], 444 | projectedPoint[1] - 7 / parameters.scale 445 | ); 446 | 447 | parameters.context.fill(); 448 | } 449 | } 450 | }, 451 | ``` 452 | 453 | - It's time to define the zoom behavior when we click on the map, and initialize the map: 454 | 455 | _DO NOT COPY PASTE THIS CODE, YOU CAN FIND WHOLE PIECE OF CODE AT THE BOOTOM OF THIS README_ 456 | ``` 457 | events: { 458 | click: function (parameters, d) { 459 | parameters.map.zoom(d); 460 | } 461 | } 462 | } 463 | ] 464 | }); 465 | map.init(); 466 | ``` 467 | 468 | - Complete chunk 469 | 470 | 471 | _main.js_ 472 | _COMPLETE CHUNK INSERT RIGHT AFTER toposon.presimplify call_ 473 | 474 | ```javascript 475 | var map = new ZoomableCanvasMap({ 476 | element: "body", 477 | zoomScale: 0.8, 478 | width: width, 479 | height: height, 480 | projection: d3.geo 481 | .conicConformalSpain() 482 | .translate([width / 2 + 300, height / 2 + 100]) 483 | .scale(960 * 4.3), 484 | data: [ 485 | { 486 | features: topojson.feature(d, d.objects["municipios"]), 487 | static: { 488 | paintfeature: function (parameters, d) { 489 | parameters.context.fillStyle = color(d.properties.rate); 490 | parameters.context.fill(); 491 | }, 492 | postpaint: function (parameters) { 493 | var p = new Path2D(parameters.projection.getCompositionBorders()); 494 | 495 | parameters.context.lineWidth = 0.5; 496 | parameters.context.strokeStyle = "#555"; 497 | parameters.context.stroke(p); 498 | } 499 | }, 500 | dynamic: { 501 | postpaint: function (parameters) { 502 | if (!hover) { 503 | tooltip.style("opacity", 0); 504 | return; 505 | } 506 | 507 | parameters.context.beginPath(); 508 | parameters.context.lineWidth = 1.5 / parameters.scale; 509 | parameters.path(hover); 510 | parameters.context.stroke(); 511 | 512 | tooltip 513 | .style("opacity", 1) 514 | .html( 515 | "
    " + 516 | "" + 517 | hover.properties.name + 518 | "" + 519 | "
    " + 520 | "Density" + 521 | "" + 522 | format(hover.properties.rate) + 523 | " per km2" 524 | ); 525 | } 526 | }, 527 | events: { 528 | hover: function (parameters, d) { 529 | hover = d; 530 | 531 | parameters.map.paint(); 532 | } 533 | } 534 | }, 535 | { 536 | features: topojson.feature(ccaa, ccaa.objects["ccaa"]), 537 | static: { 538 | paintfeature: function (parameters, d) { 539 | parameters.context.lineWidth = 0.5 / parameters.scale; 540 | parameters.context.strokeStyle = "rgb(130,130,130)"; 541 | parameters.context.stroke(); 542 | }, 543 | postpaint: function (parameters) { 544 | for (var i in cities) { 545 | // Assign the cities to a variable for a cleaner access 546 | var city = cities[i]; 547 | 548 | // Project the coordinates into our Canvas map 549 | var projectedPoint = parameters.projection(city.coordinates); 550 | 551 | // Create the label dot 552 | parameters.context.beginPath(); 553 | 554 | parameters.context.arc( 555 | projectedPoint[0], 556 | projectedPoint[1], 557 | 2 / parameters.scale, 558 | 0, 559 | 2 * Math.PI, 560 | true 561 | ); 562 | 563 | // Font properties 564 | var fontSize = 11 / parameters.scale; 565 | parameters.context.textAlign = "center"; 566 | parameters.context.font = fontSize + "px sans-serif"; 567 | 568 | // Create the text shadow 569 | parameters.context.shadowColor = "black"; 570 | parameters.context.shadowBlur = 5; 571 | parameters.context.lineWidth = 1 / parameters.scale; 572 | parameters.context.strokeText( 573 | city.name, 574 | projectedPoint[0], 575 | projectedPoint[1] - 7 / parameters.scale 576 | ); 577 | 578 | // Paint the labels 579 | parameters.context.fillStyle = "white"; 580 | parameters.context.fillText( 581 | city.name, 582 | projectedPoint[0], 583 | projectedPoint[1] - 7 / parameters.scale 584 | ); 585 | 586 | parameters.context.fill(); 587 | } 588 | } 589 | }, 590 | events: { 591 | click: function (parameters, d) { 592 | parameters.map.zoom(d); 593 | } 594 | } 595 | } 596 | ] 597 | }); 598 | map.init(); 599 | ``` 600 | 601 | > This code could be enhanced and some parts could be even be reusable assets, as an excercise try to play with this 602 | and come with a cleaner implementation. -------------------------------------------------------------------------------- /samples/04 maps/03 others/readme.md: -------------------------------------------------------------------------------- 1 | 2 | Population Choropleth 3 | 4 | https://bl.ocks.org/mbostock/6320825 5 | 6 | 7 | Plan Hardiness Zones 8 | 9 | https://bl.ocks.org/mbostock/6264239 10 | 11 | Country Bubbles 12 | 13 | https://bl.ocks.org/mbostock/9943478 14 | 15 | Hierarchichal Edge Building 16 | 17 | https://bl.ocks.org/mbostock/7607999 18 | 19 | Pan and Zoom 20 | 21 | https://bl.ocks.org/mbostock/d1f7b58631e71fbf9c568345ee04a60e 22 | 23 | Dendogram 24 | 25 | https://bl.ocks.org/mbostock/ff91c1558bc570b08539547ccc90050b --------------------------------------------------------------------------------