21 |
22 |
23 |
--------------------------------------------------------------------------------
/preprocessing/preprocessing.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Created on Fri Oct 18 16:17:58 2019
4 |
5 | @author: caoa
6 | """
7 | import pandas as pd
8 |
9 | pd.options.display.max_rows =20
10 | pd.options.display.max_columns = 20
11 |
12 | df = pd.read_csv('GL2018.TXT', header=None, usecols=[0,3,6,9,10])
13 | df.columns = ['date','away','home','aRuns','hRuns']
14 |
15 | #%%
16 | df['team'] = df.apply(lambda x: x['away'] if x['aRuns'] > x['hRuns'] else x['home'], axis=1)
17 | data = df[['date','team']]
18 | data.to_csv('daily_snapshot.csv', index=False)
19 |
20 | #%% Find first day where all teams have won at least one game
21 | data['date'] = pd.to_datetime(data['date'], format='%Y%m%d')
22 | daterange = pd.date_range('2018-03-29','2018-10-01',freq='D')
23 | for day in daterange:
24 | abc = data[data['date'] <= day]
25 | xyz = abc.team.value_counts()
26 | if xyz.shape[0] >= 30:
27 | print(day)
28 | break
29 |
--------------------------------------------------------------------------------
/urls.js:
--------------------------------------------------------------------------------
1 | const urls = {
2 | 'ARI':'Arizona_Diamondbacks',
3 | 'ATL':'Atlanta_Braves',
4 | 'SFN':'SanFrancisco_Giants',
5 | 'CHN':'Chicago_Cubs',
6 | 'NYN':'NewYork_Mets',
7 | 'MIL':'Milwaukee_Brewers',
8 | 'BAL':'Baltimore_Orioles',
9 | 'CHA':'Chicago_White_Sox',
10 | 'OAK':'Oakland_Athletics',
11 | 'SEA':'Seattle_Mariners',
12 | 'TBA':'TampaBay_Rays',
13 | 'HOU':'Houston_Astros',
14 | 'NYA':'NewYork_Yankees',
15 | 'PHI':'Philadelphia_Phillies',
16 | 'WAS':'Washington_Nationals',
17 | 'MIA':'Miami_Marlins',
18 | 'PIT':'Pittsburgh_Pirates',
19 | 'ANA':'LosAngeles_Angels',
20 | 'BOS':'Boston_Redsox',
21 | 'TEX':'Texas_Rangers',
22 | 'COL':'Colorado_Rockies',
23 | 'LAN':'LosAngeles_Dodgers',
24 | 'MIN':'Minnesota_Twins',
25 | 'CLE':'Cleveland_Indians',
26 | 'TOR':'Toronto_Blue_Jays',
27 | 'SLN':'StLouis_Cardinals',
28 | 'CIN':'Cincinnati_Reds',
29 | 'DET':'Detroit_Tigers',
30 | 'SDN':'SanDiego_Padres',
31 | 'KCA':'KansasCity_Royals',
32 | }
--------------------------------------------------------------------------------
/bar.css:
--------------------------------------------------------------------------------
1 | /* .chart {
2 | clip-path: url(#clip);
3 | } */
4 |
5 | .bar {
6 | fill: orange;
7 | }
8 |
9 | .x.axis text {
10 | font: 15px sans-serif;
11 | }
12 |
13 | .axis path, .axis line {
14 | fill: none;
15 | stroke: '#000';
16 | shape-rendering: crispEdges;
17 | }
18 |
19 | .label {
20 | text-anchor: middle;
21 | font: 20px helvetica;
22 | }
23 |
24 | #date {
25 | text-anchor: start;
26 | font: 20px helvetica;
27 | }
28 |
29 | .grid line {
30 | stroke: lightgrey;
31 | stroke-opacity: 0.7;
32 | shape-rendering: crispEdges;
33 | }
34 |
35 | .grid path {
36 | stroke-width: 0;
37 | }
38 |
39 | .team {
40 | fill: black;
41 | font: 14px sans-serif;
42 | text-anchor: end;
43 | font-weight: 600;
44 | }
45 |
46 | .barlabel{
47 | fill: black;
48 | font: 14px sans-serif;
49 | text-anchor: left;
50 | font-weight: 600;
51 | }
52 |
53 | .logo {
54 | fill: black;
55 | font: 14px sans-serif;
56 | text-anchor: middle;
57 | }
58 |
59 | .divisions {
60 | stroke: black;
61 | stroke-width: 2;
62 | stroke-dasharray: 12;
63 | }
--------------------------------------------------------------------------------
/exercise_2/exercise_2.js:
--------------------------------------------------------------------------------
1 | // set the dimensions and margins of the graph
2 | var outerWidth = 960;
3 | var outerHeight = 500;
4 |
5 | var margin = {top: 50, right: 20, bottom: 80, left: 80},
6 | width = outerWidth - margin.left - margin.right,
7 | height = outerHeight - margin.top - margin.bottom;
8 |
9 | // set the ranges
10 | var x = d3.scaleBand()
11 | .range([0, width])
12 | .padding(0.33);
13 |
14 | var y= d3.scaleLinear()
15 | .range([height, 0]);
16 |
17 |
18 | var xAxis = d3.axisTop(x)
19 | .ticks(5)
20 |
21 | var yAxis = d3.axisLeft(y)
22 | .tickFormat('')
23 |
24 | // append the svg object to the body of the page
25 | // append a 'group' element to 'svg'
26 | // moves the 'group' element to the top left margin
27 | var svg = d3.select('body').append('svg')
28 | .attr("class", "chart")
29 | .attr("width", outerWidth)
30 | .attr("height", outerHeight)
31 | .append("g")
32 | .attr("transform", `translate(${margin.left},${margin.top})`);
33 |
34 | // data
35 | var data = [{'team':'Boston','value':100},
36 | {'team':'Detroit','value':85},
37 | {'team':'New York','value':80},
38 | {'team':'Atlanta','value':75},
39 | {'team':'Chicago','value':30}]
40 |
41 |
42 | // scale the range of the data in the domains
43 | x.domain(data.map(d => d.team));
44 | y.domain([0, d3.max(data, d => d.value)])
45 |
46 |
47 | // append the rectangles for the bar chart
48 | var bar = svg.selectAll(".bar")
49 | .data(data)
50 | .join("g")
51 | .attr("class","bar")
52 |
53 |
54 |
55 | var rect = bar.append('rect')
56 | .attr("height", d => height - y(d.value))
57 | .attr("x", d => x(d.team))
58 | .attr("width", x.bandwidth())
59 | .attr("y", d => y(d.value))
60 | .style('fill', d => d3.interpolatePurples(d.value/100));
61 |
62 | // add the x Axis
63 | svg.append("g")
64 | .attr("transform", `translate(0, ${height})`)
65 | .call(d3.axisBottom(x));
66 |
67 | // add the y Axis
68 | svg.append("g")
69 | .call(d3.axisLeft(y));
70 |
71 | // add chart labels
72 | labels = svg.append('g')
73 | .attr('class', 'label')
74 |
75 | // x label
76 | labels.append('text')
77 | .attr('transform', `translate(${width/2},450)`)
78 | .text('Teams')
79 |
80 | // y label
81 | ylabel = labels.append('text')
82 | .attr('transform', `translate(-45,${height/2}) rotate(-90)`)
83 | .text('Wins')
84 |
85 | barLabels = bar.append('text')
86 | .attr('class', 'barlabel')
87 | .attr('x', d => x(d.team) + (x.bandwidth()/2))
88 | .attr('y', d => y(d.value) - 15)
89 | .text(d => d.value)
90 | .style('fill', 'black')
91 |
--------------------------------------------------------------------------------
/exercise_1/solution/solution_1.js:
--------------------------------------------------------------------------------
1 | // set the dimensions and margins of the graph
2 | var outerWidth = 960;
3 | var outerHeight = 500;
4 |
5 | var margin = {top: 50, right: 20, bottom: 80, left: 80},
6 | width = outerWidth - margin.left - margin.right,
7 | height = outerHeight - margin.top - margin.bottom;
8 |
9 | // set the ranges
10 | var x = d3.scaleBand()
11 | .range([0, width])
12 | .padding(0.33);
13 |
14 | var y= d3.scaleLinear()
15 | .range([height, 0]);
16 |
17 |
18 | var xAxis = d3.axisTop(x)
19 | .ticks(5)
20 |
21 | var yAxis = d3.axisLeft(y)
22 | .tickFormat('')
23 |
24 | // append the svg object to the body of the page
25 | // append a 'group' element to 'svg'
26 | // moves the 'group' element to the top left margin
27 | var svg = d3.select('body').append('svg')
28 | .attr("class", "chart")
29 | .attr("width", outerWidth)
30 | .attr("height", outerHeight)
31 | .append("g")
32 | .attr("transform", `translate(${margin.left},${margin.top})`);
33 |
34 | // data
35 | var data = [{'team':'Boston','value':100},
36 | {'team':'Detroit','value':85},
37 | {'team':'New York','value':80},
38 | {'team':'Atlanta','value':75},
39 | {'team':'Chicago','value':30}]
40 |
41 |
42 | // scale the range of the data in the domains
43 | x.domain(data.map(d => d.team));
44 | y.domain([0, d3.max(data, d => d.value)])
45 |
46 |
47 | // append the rectangles for the bar chart
48 | var bar = svg.selectAll(".bar")
49 | .data(data)
50 | .join("g")
51 | .attr("class","bar")
52 |
53 |
54 |
55 | var rect = bar.append('rect')
56 | .attr("height", d => height - y(d.value))
57 | .attr("x", d => x(d.team))
58 | .attr("width", x.bandwidth())
59 | .attr("y", d => y(d.value))
60 | .style('fill', d => d3.interpolatePurples(d.value/100));
61 |
62 | // add the x Axis
63 | svg.append("g")
64 | .attr("transform", `translate(0, ${height})`)
65 | .call(d3.axisBottom(x));
66 |
67 | // add the y Axis
68 | svg.append("g")
69 | .call(d3.axisLeft(y));
70 |
71 | // add chart labels
72 | labels = svg.append('g')
73 | .attr('class', 'label')
74 |
75 | // x label
76 | labels.append('text')
77 | .attr('transform', `translate(${width/2},450)`)
78 | .text('Teams')
79 |
80 | // y label
81 | ylabel = labels.append('text')
82 | .attr('transform', `translate(-45,${height/2}) rotate(-90)`)
83 | .text('Wins')
84 |
85 | barLabels = bar.append('text')
86 | .attr('class', 'barlabel')
87 | .attr('x', d => x(d.team) + (x.bandwidth()/2))
88 | .attr('y', d => y(d.value) - 15)
89 | .text(d => d.value)
90 | .style('fill', 'black')
91 |
--------------------------------------------------------------------------------
/exercise_1/exercise_1.js:
--------------------------------------------------------------------------------
1 | // set the dimensions and margins of the graph
2 | var outerWidth = 650;
3 | var outerHeight = 300;
4 |
5 | var margin = {top: 20, right: 20, bottom: 70, left: 100},
6 | width = outerWidth - margin.left - margin.right - 20,
7 | height = outerHeight - margin.top - margin.bottom;
8 |
9 | // set the ranges
10 | var x = d3.scaleLinear()
11 | .range([0, width]);
12 |
13 | var y = d3.scaleBand()
14 | .range([height, 0])
15 | .padding(0.33);
16 |
17 | var xAxis = d3.axisTop(x)
18 | .ticks(5)
19 |
20 | var yAxis = d3.axisLeft(y)
21 | .tickFormat('')
22 |
23 | // append the svg object to the body of the page
24 | // append a 'group' element to 'svg'
25 | // moves the 'group' element to the top left margin
26 | var svg = d3.select('body').append('svg')
27 | .attr("class", "chart")
28 | .attr("width", outerWidth)
29 | .attr("height", outerHeight)
30 | .append("g")
31 | .attr("transform", `translate(${margin.left},${margin.top})`);
32 |
33 | // data
34 | var data = [{'team':'Boston','value':100},
35 | {'team':'Detroit','value':85},
36 | {'team':'New York','value':80},
37 | {'team':'Atlanta','value':75},
38 | {'team':'Chicago','value':30}]
39 |
40 |
41 | // scale the range of the data in the domains
42 | x.domain([0, d3.max(data, d => d.value)])
43 | y.domain(data.map(d => d.team));
44 |
45 |
46 | // append the rectangles for the bar chart
47 | var bar = svg.selectAll(".bar")
48 | .data(data)
49 | .join("g")
50 | .attr("class","bar")
51 |
52 |
53 |
54 | var rect = bar.append('rect')
55 | .attr("width", d => x(d.value))
56 | .attr("y", d => y(d.team))
57 | .attr("height", y.bandwidth())
58 |
59 | .style('fill', d => d3.interpolatePurples(d.value/100))
60 |
61 | // add the x Axis
62 | svg.append("g")
63 | .attr("transform", `translate(0, ${height})`)
64 | .call(d3.axisBottom(x));
65 |
66 | // add the y Axis
67 | svg.append("g")
68 | .call(d3.axisLeft(y));
69 |
70 | // add chart labels
71 | labels = svg.append('g')
72 | .attr('class', 'label')
73 |
74 | // x label
75 | labels.append('text')
76 | .attr('transform', `translate(${width/2},250)`)
77 | .text('Wins')
78 |
79 | // y label
80 | ylabel = labels.append('text')
81 | .attr('transform', `translate(-65,${height/2}) rotate(-90)`)
82 | .text('Teams')
83 |
84 | barLabels = bar.append('text')
85 | .attr('class', 'barlabel')
86 | .attr('x', d => x(d.value) - 20)
87 | .attr('y', d => y(d.team) + (y.bandwidth()/2) + 4)
88 | .text(d => d.value)
89 | .style('fill', 'black')
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/exercise_3/exercise_3.js:
--------------------------------------------------------------------------------
1 | // set the dimensions and margins of the graph
2 | var outerWidth = 960;
3 | var outerHeight = 500;
4 |
5 | var margin = {top: 50, right: 20, bottom: 80, left: 80},
6 | width = outerWidth - margin.left - margin.right,
7 | height = outerHeight - margin.top - margin.bottom;
8 |
9 | // set the ranges
10 | var y= d3.scaleLinear()
11 | .range([height, 0]);
12 |
13 | var x = d3.scaleBand()
14 | .range([0, width])
15 | .padding(0.33);
16 |
17 | var xAxis = d3.axisTop(x)
18 | .ticks(5)
19 |
20 | var yAxis = d3.axisLeft(y)
21 | .tickFormat('')
22 |
23 | // append the svg object to the body of the page
24 | // append a 'group' element to 'svg'
25 | // moves the 'group' element to the top left margin
26 | var svg = d3.select('body').append('svg')
27 | .attr("class", "chart")
28 | .attr("width", outerWidth)
29 | .attr("height", outerHeight)
30 | .append("g")
31 | .attr("transform", `translate(${margin.left},${margin.top})`);
32 |
33 | // data
34 | var data = [{'team':'Boston','value':100},
35 | {'team':'Detroit','value':85},
36 | {'team':'New York','value':80},
37 | {'team':'Atlanta','value':75},
38 | {'team':'Chicago','value':30}]
39 |
40 |
41 | // scale the range of the data in the domains
42 | y.domain([0, d3.max(data, d => d.value)])
43 | x.domain(data.map(d => d.team));
44 |
45 |
46 |
47 | // append the rectangles for the bar chart
48 | var bar = svg.selectAll(".bar")
49 | .data(data)
50 | .join("g")
51 | .attr("class","bar")
52 |
53 |
54 | var rect = bar.append('rect')
55 | .attr("height", d => height - y(d.value))
56 | .attr("x", d => x(d.team))
57 | .attr("width", x.bandwidth())
58 | .attr("y", d => y(d.value))
59 | .style('fill', d => d3.interpolatePurples(d.value/100));
60 |
61 |
62 | // add the x Axis
63 | svg.append("g")
64 | .attr('class', 'xaxis')
65 | .attr("transform", `translate(0, ${height})`)
66 | .call(d3.axisBottom(x));
67 |
68 | // add the y Axis
69 | svg.append("g")
70 | .call(d3.axisLeft(y));
71 |
72 | // add chart labels
73 | labels = svg.append('g')
74 | .attr('class', 'label')
75 |
76 | // x label
77 | labels.append('text')
78 | .attr('transform', `translate(${width/2},450)`)
79 | .text('Teams')
80 |
81 | // y label
82 | ylabel = labels.append('text')
83 | .attr('transform', `translate(-45,${height/2}) rotate(-90)`)
84 | .text('Wins')
85 |
86 | barLabels = bar.append('text')
87 | .attr('class', 'barlabel')
88 | .attr('x', d => x(d.team) + (x.bandwidth()/2))
89 | .attr('y', d => y(d.value) - 15)
90 | .text(d => d.value)
91 | .style('fill', 'black')
92 |
93 |
94 | function updateAlpha() {
95 |
96 | x.domain((data.map(d => d.team)).sort());
97 |
98 | bar.selectAll('rect')
99 | .attr("x", d => x(d.team))
100 |
101 | svg.select(".xaxis")
102 | .call(d3.axisBottom(x));
103 |
104 |
105 | bar.selectAll('.barlabel')
106 | .attr('x', d => x(d.team) + (x.bandwidth()/2))
107 |
108 | }
109 |
110 |
111 | function updateNum() {
112 |
113 | data.sort((a,b) => d3.ascending(a.value, b.value))
114 |
115 | x.domain(data.map(d => d.team));
116 |
117 | bar.selectAll('rect')
118 | .attr("x", d => x(d.team))
119 |
120 | svg.select(".xaxis")
121 | .call(d3.axisBottom(x));
122 |
123 | bar.selectAll('.barlabel')
124 | .attr('x', d => x(d.team) + (x.bandwidth()/2))
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/exercise_2/solution/solution_2.js:
--------------------------------------------------------------------------------
1 | // set the dimensions and margins of the graph
2 | var outerWidth = 960;
3 | var outerHeight = 500;
4 |
5 | var margin = {top: 50, right: 20, bottom: 80, left: 80},
6 | width = outerWidth - margin.left - margin.right,
7 | height = outerHeight - margin.top - margin.bottom;
8 |
9 | // set the ranges
10 | var x = d3.scaleBand()
11 | .range([0, width])
12 | .padding(0.33);
13 |
14 | var y= d3.scaleLinear()
15 | .range([height, 0]);
16 |
17 |
18 | var xAxis = d3.axisTop(x)
19 | .ticks(5)
20 |
21 | var yAxis = d3.axisLeft(y)
22 | .tickFormat('')
23 |
24 | // append the svg object to the body of the page
25 | // append a 'group' element to 'svg'
26 | // moves the 'group' element to the top left margin
27 | var svg = d3.select('body').append('svg')
28 | .attr("class", "chart")
29 | .attr("width", outerWidth)
30 | .attr("height", outerHeight)
31 | .append("g")
32 | .attr("transform", `translate(${margin.left},${margin.top})`);
33 |
34 | // data
35 | var data = [{'team':'Boston','value':100},
36 | {'team':'Detroit','value':85},
37 | {'team':'New York','value':80},
38 | {'team':'Atlanta','value':75},
39 | {'team':'Chicago','value':30}]
40 |
41 |
42 | // scale the range of the data in the domains
43 | x.domain(data.map(d => d.team));
44 | y.domain([0, d3.max(data, d => d.value)])
45 |
46 |
47 | // append the rectangles for the bar chart
48 | var bar = svg.selectAll(".bar")
49 | .data(data)
50 | .join("g")
51 | .attr("class","bar")
52 |
53 |
54 | var rect = bar.append('rect')
55 | .attr("height", d => height - y(d.value))
56 | .attr("x", d => x(d.team))
57 | .attr("width", x.bandwidth())
58 | .attr("y", d => y(d.value))
59 | .style('fill', d => d3.interpolatePurples(d.value/100));
60 |
61 |
62 | // add the x Axis
63 | svg.append("g")
64 | .attr('class', 'xaxis')
65 | .attr("transform", `translate(0, ${height})`)
66 | .call(d3.axisBottom(x));
67 |
68 | // add the y Axis
69 | svg.append("g")
70 | .call(d3.axisLeft(y));
71 |
72 | // add chart labels
73 | labels = svg.append('g')
74 | .attr('class', 'label')
75 |
76 | // x label
77 | labels.append('text')
78 | .attr('transform', `translate(${width/2},450)`)
79 | .text('Teams')
80 |
81 | // y label
82 | ylabel = labels.append('text')
83 | .attr('transform', `translate(-45,${height/2}) rotate(-90)`)
84 | .text('Wins')
85 |
86 | barLabels = bar.append('text')
87 | .attr('class', 'barlabel')
88 | .attr('x', d => x(d.team) + (x.bandwidth()/2))
89 | .attr('y', d => y(d.value) - 15)
90 | .text(d => d.value)
91 | .style('fill', 'black')
92 |
93 |
94 | function updateAlpha() {
95 |
96 | x.domain((data.map(d => d.team)).sort());
97 |
98 | bar.selectAll('rect')
99 | .attr("x", d => x(d.team))
100 |
101 | svg.select(".xaxis")
102 | .call(d3.axisBottom(x));
103 |
104 |
105 | bar.selectAll('.barlabel')
106 | .attr('x', d => x(d.team) + (x.bandwidth()/2))
107 |
108 |
109 |
110 | }
111 |
112 | function updateNum() {
113 |
114 | data.sort((a,b) => d3.ascending(a.value, b.value))
115 |
116 | x.domain(data.map(d => d.team));
117 |
118 | bar.selectAll('rect')
119 | .attr("x", d => x(d.team))
120 |
121 | svg.select(".xaxis")
122 | .call(d3.axisBottom(x));
123 |
124 | bar.selectAll('.barlabel')
125 | .attr('x', d => x(d.team) + (x.bandwidth()/2))
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/exercise_3/solution/solution_3.js:
--------------------------------------------------------------------------------
1 | // set the dimensions and margins of the graph
2 | var outerWidth = 960;
3 | var outerHeight = 500;
4 |
5 | var margin = {top: 50, right: 20, bottom: 80, left: 80},
6 | width = outerWidth - margin.left - margin.right,
7 | height = outerHeight - margin.top - margin.bottom;
8 |
9 | // set the ranges
10 | var y= d3.scaleLinear()
11 | .range([height, 0]);
12 |
13 | var x = d3.scaleBand()
14 | .range([0, width])
15 | .padding(0.33);
16 |
17 | var xAxis = d3.axisTop(x)
18 | .ticks(5)
19 |
20 | var yAxis = d3.axisLeft(y)
21 | .tickFormat('')
22 |
23 | // append the svg object to the body of the page
24 | // append a 'group' element to 'svg'
25 | // moves the 'group' element to the top left margin
26 | var svg = d3.select('body').append('svg')
27 | .attr("class", "chart")
28 | .attr("width", outerWidth)
29 | .attr("height", outerHeight)
30 | .append("g")
31 | .attr("transform", `translate(${margin.left},${margin.top})`);
32 |
33 | // data
34 | var data = [{'team':'Boston','value':100},
35 | {'team':'Detroit','value':85},
36 | {'team':'New York','value':80},
37 | {'team':'Atlanta','value':75},
38 | {'team':'Chicago','value':30}]
39 |
40 |
41 | // scale the range of the data in the domains
42 | y.domain([0, d3.max(data, d => d.value)])
43 | x.domain(data.map(d => d.team));
44 |
45 |
46 | // append the rectangles for the bar chart
47 | var bar = svg.selectAll(".bar")
48 | .data(data)
49 | .join("g")
50 | .attr("class","bar")
51 |
52 |
53 | var rect = bar.append('rect')
54 | .attr("height", d => height - y(d.value))
55 | .attr("x", d => x(d.team))
56 | .attr("width", x.bandwidth())
57 | .attr("y", d => y(d.value))
58 | .style('fill', d => d3.interpolatePurples(d.value/100));
59 |
60 |
61 | // add the x Axis
62 | svg.append("g")
63 | .attr('class', 'xaxis')
64 | .attr("transform", `translate(0, ${height})`)
65 | .call(d3.axisBottom(x));
66 |
67 | // add the y Axis
68 | svg.append("g")
69 | .call(d3.axisLeft(y));
70 |
71 | // add chart labels
72 | labels = svg.append('g')
73 | .attr('class', 'label')
74 |
75 | // x label
76 | labels.append('text')
77 | .attr('transform', `translate(${width/2},450)`)
78 | .text('Teams')
79 |
80 | // y label
81 | ylabel = labels.append('text')
82 | .attr('transform', `translate(-45,${height/2}) rotate(-90)`)
83 | .text('Wins')
84 |
85 | barLabels = bar.append('text')
86 | .attr('class', 'barlabel')
87 | .attr('x', d => x(d.team) + (x.bandwidth()/2))
88 | .attr('y', d => y(d.value) - 15)
89 | .text(d => d.value)
90 | .style('fill', 'black')
91 |
92 |
93 | function updateAlpha() {
94 | const T = 500
95 |
96 | x.domain((data.map(d => d.team)).sort());
97 |
98 | bar.selectAll('rect')
99 | .transition().duration(T)
100 | .attr("x", d => x(d.team))
101 |
102 | svg.select(".xaxis")
103 | .transition().duration(T)
104 | .call(d3.axisBottom(x))
105 |
106 | bar.selectAll('.barlabel')
107 | .transition().duration(T)
108 | .attr('x', d => x(d.team) + (x.bandwidth()/2))
109 |
110 |
111 | }
112 |
113 | function updateNum() {
114 | const T = 500
115 |
116 | data.sort((a,b) => d3.ascending(a.value, b.value));
117 |
118 | x.domain(data.map(d => d.team));
119 |
120 | bar.selectAll('rect')
121 | .transition().duration(T)
122 | .attr("x", d => x(d.team))
123 |
124 | svg.select(".xaxis")
125 | .transition().duration(T)
126 | .call(d3.axisBottom(x))
127 |
128 | bar.selectAll('.barlabel')
129 | .transition().duration(T)
130 | .attr('x', d => x(d.team) + (x.bandwidth()/2))
131 |
132 | }
133 |
134 |
--------------------------------------------------------------------------------
/sortable.js:
--------------------------------------------------------------------------------
1 | async function createChart() {
2 |
3 | // read data
4 | const fileLocation = 'https://gist.githubusercontent.com/caocscar/8cdb75721ea4f6c8a032a00ebc73516c/raw/854bbee2faffb4f6947b6b6c2424b18ca5a8970e/mlb2018.csv'
5 | DATA = await d3.csv(fileLocation, type)
6 | let chartDate = new Date(2018,3,3)
7 | let data = filterData(chartDate)
8 |
9 | // margins
10 | let margin = {top: 80, right: 90, bottom: 30+50, left: 120},
11 | width = 900 - margin.left - margin.right,
12 | height = 1500 - margin.top - margin.bottom; // 760
13 |
14 | // svg setup
15 | let svg = d3.select('body').append('svg')
16 | .attr("class", "chart")
17 | .attr("width", width + margin.left + margin.right)
18 | .attr("height", height + margin.top + margin.bottom)
19 | .append("g")
20 | .attr("transform", `translate(${margin.left},${margin.top})`);
21 |
22 | // set up scales
23 | let y = d3.scaleBand()
24 | .domain(data.map(d => d.team).reverse())
25 | .range([height, 0])
26 | .padding(0.33)
27 |
28 | let x = d3.scaleLinear()
29 | .domain([0, Math.ceil(d3.max(data, d => d.value)/5)*5])
30 | .range([0, width]);
31 |
32 | // add axes
33 | let xAxis = d3.axisTop(x)
34 | .ticks(6)
35 |
36 | svg.append("g")
37 | .attr("class", "x axis")
38 | .call(xAxis);
39 |
40 | let yAxis = d3.axisLeft(y)
41 | .tickFormat('')
42 |
43 | svg.append("g")
44 | .attr("class", "y axis")
45 | .call(yAxis);
46 |
47 | // add the x-axis gridlines
48 | let gridlines = d3.axisTop(x)
49 | .ticks(6)
50 | .tickSize(-height)
51 | .tickFormat("")
52 |
53 | svg.append("g")
54 | .attr("class", "grid")
55 | .call(gridlines)
56 |
57 | // set up bar groups
58 | let bar = svg.selectAll(".bar")
59 | .data(data)
60 | .join("g")
61 | .attr("class", "bar")
62 | .attr("transform", d => `translate(0,${y(d.team)})`)
63 |
64 | // adding bars
65 | let rects = bar.append('rect')
66 | .attr("width", (d,i) => x(d.value))
67 | .attr("height", y.bandwidth())
68 | .style('fill', d => d3.interpolateRdYlBu(d.value/100))
69 |
70 | // team labels
71 | bar.append('text')
72 | .attr('class', 'team')
73 | .attr('x', -10)
74 | .attr('y', y.bandwidth()/2 + 5)
75 | .text(d => d.team)
76 |
77 | // team logos
78 | const imgsize = 40
79 | let imgs = bar.append("svg:image")
80 | .attr('class', 'logo')
81 | .attr('x', d => x(d.value) + 5)
82 | .attr('y', -5)
83 | .attr('width', imgsize)
84 | .attr('height', imgsize)
85 | .attr("xlink:href", d => `http://www.capsinfo.com/images/MLB_Team_Logos/${urls[d.team]}.png`)
86 |
87 | // bar labels
88 | let barLabels = bar.append('text')
89 | .attr('class', 'barlabel')
90 | .attr('x', d => x(d.value) + 10 + imgsize)
91 | .attr('y', y.bandwidth()/2 + 5)
92 | .text(d => d.value)
93 |
94 | // other chart labels
95 | labels = svg.append('g')
96 | .attr('class', 'label')
97 |
98 | // x label
99 | labels.append('text')
100 | .attr('transform', `translate(${width},-40)`)
101 | .text('Wins')
102 |
103 | // y label
104 | ylabel = labels.append('text')
105 | .attr('transform', `translate(-80,${height/2}) rotate(-90)`) // order matters
106 | .text('Teams')
107 |
108 | // date label
109 | const formatDate = d3.timeFormat('%b %-d')
110 | let dateLabel = labels.append('text')
111 | .attr('id', 'date')
112 | .attr('transform', 'translate(0,-40)')
113 | .text(formatDate(chartDate))
114 |
115 | labels.append('text')
116 | .attr('id', 'season')
117 | .attr('transform', `translate(${width/2},-40)`)
118 | .text('MLB 2018 Season')
119 |
120 | // clipping rectangle
121 | const z = 0.97*(height / data.length)
122 | d3.select('.chart').append("defs")
123 | .append("clipPath")
124 | .attr("id", "clip")
125 | .append("rect")
126 | .attr('x', 0)
127 | .attr('y', 0)
128 | .attr("width", width + margin.left + margin.right)
129 | .attr("height", 0.4*height)
130 |
131 | // sorting transition
132 | const T = 300
133 | let dailyUpdate = setInterval(function() {
134 |
135 | chartDate = d3.timeDay.offset(chartDate,1)
136 | dateLabel.text(formatDate(chartDate))
137 | data = filterData(chartDate)
138 |
139 | // update x-axis
140 | x.domain([0, Math.ceil(d3.max(data, d => d.value)/5)*5]);
141 | svg.select('.x.axis').transition().duration(T)
142 | .call(xAxis);
143 | svg.select('.grid').transition().duration(T)
144 | .call(gridlines);
145 |
146 | // update bar chart
147 | rects.data(data)
148 | .transition().duration(T)
149 | .attr("width", d => x(d.value))
150 | .style('fill', d => d3.interpolateRdYlBu(d.value/100))
151 | imgs.data(data)
152 | .transition().duration(T)
153 | .attr('x', d => x(d.value) + 5)
154 | barLabels.data(data)
155 | .transition().duration(T)
156 | .attr('x', d => x(d.value) + 10 + imgsize)
157 | .attr('y', y.bandwidth()/2 + 5)
158 | .text(d => d.value)
159 |
160 | // sort data
161 | data.sort((a,b) => d3.descending(a.value,b.value));
162 |
163 | // update y-axis
164 | y.domain(data.map(d => d.team).reverse());
165 | bar.transition().duration(T)
166 | .attr("transform", d => `translate(0,${y(d.team)})`)
167 |
168 | // exit function
169 | if (chartDate > new Date(2018,9,1)) {
170 | clearInterval(dailyUpdate)
171 | }
172 |
173 | }, T);
174 |
175 | }
176 |
177 | function type(d) {
178 | const formatDate = d3.timeParse('%Y%m%d')
179 | d.date = formatDate(d.date)
180 | return d
181 | }
182 |
183 | function filterData(chartDate) {
184 | const snapshot = DATA.filter(d => d.date <= chartDate)
185 | const wins = d3.rollup(snapshot, v => v.length, d => d.team) // returns Map object
186 | return Array.from(wins, ([key, value]) => ({'team':key, 'value':value}))
187 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # D3.js Workshop
2 | - [Introduction to D3](#introduction-to-d3)
3 | - [Key Components](#key-components)
4 | - [Getting Started](#getting-started)
5 | - [Running D3 Locally](#running-d3-locally)
6 | - [Getting D3](#getting-d3)
7 | - [D3 Version](#d3-version)
8 | - [HTML Setup](#html-setup)
9 | - [Graph Setup](#graph-setup)
10 | - [Reading in Data](#reading-in-data)
11 | - [Setting up D3 Scales](#setting-up-d3-scales)
12 | - [Adding Axes](#adding-axes)
13 | - [Individual Team Bar Charts](#individual-team-bar-charts)
14 | - [Exercise #1 (Vertical Bar Chart)](#exercise-1-vertical-bar-chart)
15 | - [Animating the Graph](#animating-the-graph)
16 | - [Exercise #2 (Sort the Chart)](#exercise-2-sort-the-chart)
17 | - [Exercise #3 (Animated Sort)](#exercise-3-animated-sort)
18 | - [Easing](#easing)
19 | - [Clipped Path](#clipped-path)
20 | - [References](#references)
21 | ## Introduction to D3
22 | D3 (Data-Driven Documents or D3.js) is a JavaScript library for manipulating documents based on data and is used in conjunction with tools like HTML, SVG, and CSS. It's used by the [New York Times](https://getdolphins.com/blog/interactive-data-visualizations-new-york-times/) to make interactive custom graphics for its [news](https://www.nytimes.com/interactive/2019/11/06/us/politics/elizabeth-warren-policies-taxes.html) [articles](https://www.nytimes.com/interactive/2018/03/27/upshot/make-your-own-mobility-animation.html).
23 |
24 | ### Key Components
25 | - HTML (HyperText Markup Language)
26 | - standard markup language for webpages
27 | - defines content
28 | - CSS (Cascading Style Sheets)
29 | - language to describe the style of a HTML document
30 | - describes how HTML elements should be displayed
31 | - JavaScript
32 | - scripting language for web pages
33 | - lightweight, interpreted, or just-in-time compiled programming language
34 | - SVG (Scalable Vector Graphics)
35 | - language for describing 2-D vector graphics in XML
36 | - Web Server
37 |
38 | ## Getting Started
39 | Download the files from the Github repo or the Box link provided in the workshop. The main files we will be working with are `index.html`, `sortable.js`, and `bar.css`.
40 |
41 | ## Running D3 Locally
42 | To run D3 locally on your PC, you need to set up a web server. Fortunately, Python makes this relatively easy. Open a terminal (i.e. Anaconda Prompt) that has access to Python and run a simple http server with the command `python -m http.server `. The port number is optional (default is 8000). Then open your browser and go to `localhost:8000` or `127.0.0.1:8000`.
43 |
44 | ## Getting D3
45 | We load the d3.js library from a CDN with the following snippet.
46 | ```html
47 |
48 | ```
49 |
50 | We'll also use the **d3-array** library to do some data wrangling to get the data in the right format for plotting.
51 | ```html
52 |
53 | ```
54 |
55 | ### D3 Version
56 | We're using D3 version 5. Most of the online v5 examples are written using something called [Observable notebooks](https://observablehq.com/) (think of it like Jupyter Notebooks for JavaScript). The syntax for Observable notebooks is slightly different than what you would use for HTML. Most of the other examples on the web are for v3 or v4. Ideally, you'll want to look for examples using v4 or v5 on the web. There were some breaking changes made in the conversion from v3 to v4. Its not impossible to figure out from v3 examples but its just more errors to debug.
57 |
58 | ## HTML Setup
59 | Let's take a quick peek at our HTML file. Its pretty bare bones. The things to note are:
60 | - loading two JavaScript files `urls.js` and `sortable.js`
61 | - loading one CSS file `bar.css`
62 | - one function call `createChart()` which will create our chart for us within the `` tag
63 |
64 | ```html
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | ```
80 | This is what you would see if you right-click *View page source Ctrl+U*. Note that this is different than what you would see if you inspected the Elements tab in Chrome using your browser.
81 |
82 | ## Graph Setup
83 | Now, let's move onto our JavaScript file `sortable.js` and create the function `createChart` that we're going to call. We need to first set up our graph. We start by specifying the dimensions and margins of our graph. The standard D3 convention for setting up margins can be found at https://bl.ocks.org/mbostock/3019563
84 |
85 | Let us follow along with our code.
86 |
87 | First define the `margin` object with properties for the four sides:
88 | ```javascript
89 | let margin = {top: 80, right: 90, bottom: 30+50, left: 120}
90 | ```
91 |
92 | Next define `width` and `height`as the inner dimensions of the chart area:
93 | ```javascript
94 | width = 900 - margin.left - margin.right
95 | height = 1500 - margin.top - margin.bottom
96 | ```
97 |
98 | Lastly, define a `svg` element with three attributes (`class`, `width`, and `height`) and translate its origin to the top-left corner of the chart area with a `g` element.
99 | ```javascript
100 | let svg = d3.select('body').append('svg')
101 | .attr("class", "chart")
102 | .attr("width", width + margin.left + margin.right)
103 | .attr("height", height + margin.top + margin.bottom)
104 | .append("g")
105 | .attr("transform", "translate(${margin.left},${margin.top})")
106 | ```
107 | Let's breakdown what this D3 code block is doing.
108 | - The first line selects the `` tag in the HTML and appends (or nests) a `svg` element within the `` element.
109 | - The next three lines define attributes for the `svg` element.
110 | - The second-to-last line appends a `g` element to `svg`.
111 | - The last line applies a transformation (a translation) to the `g` element.
112 |
113 | **Note**: There is a D3 distinction between a 2 space and 4 space indentation. Two space indentation means you are returning a new selection and 4 space indentation means working with an existing selection. Don't worry if that means nothing to you at the moment. Just know that its intentional.
114 |
115 | ## Reading in Data
116 | We will be looking at data from Major League Baseball 2018 season and the accumulation of wins by each team as the season progresses.
117 |
118 | The data resides in a Github gist. There is also a copy in our folder but I wanted to show you that you can also read files from the web.
119 | ```javascript
120 | const fileLocation = 'https://gist.githubusercontent.com/caocscar/8cdb75721ea4f6c8a032a00ebc73516c/raw/854bbee2faffb4f6947b6b6c2424b18ca5a8970e/mlb2018.csv'
121 | ```
122 |
123 | Next we will read the file, parse it, convert it into an array of objects (line 1) and filter it by date using our custom `filterData` function (lines 2 & 3). I chose April 4th as the start date as every team has won at least one game by then (helping avoid some other complications with adding teams dynamically).
124 | ```javascript
125 | DATA = await d3.csv(fileLocation, type)
126 | let chartDate = new Date(2018,3,3)
127 | let data = filterData(chartDate)
128 | ```
129 | **Note**: JavaScript starts month at index 0.
130 |
131 | The function `type` takes in the data and parses the date strings into a JavaScript `Date` format. You can think of `d` as a row of the data.
132 | ```javascript
133 | function type(d) {
134 | const formatDate = d3.timeParse('%Y%m%d')
135 | d.date = formatDate(d.date)
136 | return d
137 | }
138 | ```
139 | **Note**: `d3.csv` reads in all columns as strings and you usually convert them to numeric using a `type` function. There is a `d3.autoType` function which is also available to do automatic conversions.
140 |
141 | The `filterData` function filters the data up to the given date. Here I'm using arrow `=>` functions for brevity. They are similar to `lambda` functions in Python.
142 | ```javascript
143 | function filterData(chartDate) {
144 | const snapshot = DATA.filter(d => d.date <= chartDate)
145 | const wins = d3.rollup(snapshot, v => v.length, d => d.team)
146 | return Array.from(wins, ([key, value]) => ({'team':key, 'value':value}))
147 | }
148 | ```
149 | The function does some data wrangling for us.
150 | 1. Filters the data by date `DATA.filter(d => d.date <= chartDate)`.
151 | 2. Performs a groupby operation by team and counts how many times they appear (or win) in the data `d3.rollup(snapshot, v => v.length, d => d.team)`.
152 | 3. Returns the data in the desired format for us (an `Array` of JavaScript Objects) `Array.from(wins, ([key, value]) => ({'team':key, 'value':value}))`.
153 |
154 | I could have done the data wrangling in another language like Python or R and created a different dataset to be read in but wanted to show you that D3 and JavaScript can also do similar things. Our data should look like this after being returned by `filterData`.
155 | 
156 |
157 | ## Setting up D3 Scales
158 | Since we are making a horizontal bar chart, we will utilize two scale functions: `scaleLinear` and `scaleBand`. `scaleLinear` creates a linear mapping while `scaleBand` is a mapping specific for bar charts. It will split the range into n (number of teams) bands and compute the positions and widths of the bands.
159 | ```javascript
160 | let y = d3.scaleBand()
161 | .domain(data.map(d => d.team).reverse())
162 | .range([height, 0])
163 | .padding(0.33)
164 |
165 | let x = d3.scaleLinear()
166 | .domain([0, Math.ceil(d3.max(data, d => d.value)/5)*5])
167 | .range([0, width])
168 | ```
169 | ## Adding Axes
170 | We define our axis properties. Here it's the scale we want to use `x`, orientation `axisTop()` and approximately how many ticks it should display `ticks(6)`. We append another group element to the already defined `svg` variable using `.append("g")`, assign two classes `x axis` and then call the axis generator `xAxis` to draw it with the specified arguments. Similarly for the y-axis.
171 | ```javascript
172 | let xAxis = d3.axisTop(x)
173 | .ticks(6)
174 |
175 | svg.append("g")
176 | .attr("class", "x axis")
177 | .call(xAxis);
178 |
179 | let yAxis = d3.axisLeft(y)
180 | .tickFormat('')
181 |
182 | svg.append("g")
183 | .attr("class", "y axis")
184 | .call(yAxis);
185 | ```
186 | 
187 |
188 | In addition to the axes, we want to add gridlines to the x-axis. Gridlines are coded very similarly to axes.
189 | ```javascript
190 | let gridlines = d3.axisTop(x)
191 | .ticks(6)
192 | .tickSize(-height)
193 | .tickFormat("")
194 |
195 | svg.append("g")
196 | .attr("class", "grid")
197 | .call(gridlines)
198 | ```
199 |
200 | Let's add axes labels now.
201 | ```javascript
202 | labels = svg.append('g')
203 | .attr('class', 'label')
204 |
205 | labels.append('text')
206 | .attr('transform', `translate(${width},-40)`)
207 | .text('Wins')
208 |
209 | ylabel = labels.append('text')
210 | .attr('transform', `translate(-80,${height/2} rotate(-90)`)
211 | .text('Teams')
212 | ```
213 |
214 | ## Individual Team Bar Charts
215 | Next, we will start displaying our data on the graph. Ultimately, we want to show the progression of total games won for each baseball game over a period of time. Each team will be represented by a bar, text, logo and a label.
216 |
217 | To set up the teams, we must first create groups to contain the collective information for each team.
218 |
219 | In D3, instead of telling D3 what to do, think of it as you are telling D3 what you want. The following piece of code constructs groups for each of the teams.
220 | ```javascript
221 | let bar = svg.selectAll(".bar")
222 | .data(data)
223 | .join("g") // equivalent to .enter().append('g')
224 | .attr("class", "bar")
225 | .attr("transform", d => `translate(0,${y(d.team)})`)
226 | ```
227 | Here's a breakdown of the above code block:
228 | - `svg.selectAll(".bar)` says you want to select all element of type `class="bar"` (even if they don't exist at the beginning)
229 | - `.data(data)` binds the data to this empty D3 selection
230 | - `.join(g)` creates `g` elements for each row of data (i.e. team)
231 |
232 | Then for each `g` element:
233 | - `.attr('class', 'bar')` assigns `class="bar"`
234 | - `.attr("transform", d => 'translate(0,${y(d.team)})')` assigns a transformation (x,y)
235 | 
236 |
237 | **Note**: Data joins in D3 are probably one of the more harder concepts to grasp. Here is Mike Bostock's blog on [joins](https://bost.ocks.org/mike/join/) and [selections](https://bost.ocks.org/mike/selection/).
238 |
239 | Now we will add rectangles to each bar element and set the bar width corresponding to the wins for each respective team `.attr("width", d => x(d.value))`.
240 | We style the bar using `.style('fill', d => d3.interpolateRdYlBu(d.value/100))` which defines the number of games won by each team as a fraction between 0 and 1. We will add a color scheme to visually encode the number of wins (the bars will gradually change from red to yellow to blue as teams win more games). Its really not needed for the graph but I wanted to show you how to do it.
241 | ```javascript
242 | let rects = bar.append('rect')
243 | .attr("width", d => x(d.value))
244 | .attr("height", y.bandwidth())
245 | .style('fill', d => d3.interpolateRdYlBu(d.value/100))
246 | ```
247 | 
248 |
249 | More information on chromatic schemes can be found here:
250 | https://observablehq.com/@d3/color-schemes?collection=@d3/d3-scale-chromatic
251 |
252 | Let's add labels to identify each team.
253 | ```javascript
254 | bar.append('text')
255 | .attr('class', 'team')
256 | .attr('x', -10)
257 | .attr('y', y.bandwidth()/2 + 5)
258 | .text(d => d.team)
259 | ```
260 |
261 | As well as logos for each team.
262 | ```javascript
263 | const imgsize = 40
264 | let imgs = bar.append("svg:image")
265 | .attr('class', 'logo')
266 | .attr('x', d => x(d.value) + 5)
267 | .attr('y', -5)
268 | .attr('width', imgsize)
269 | .attr('height', imgsize)
270 | .attr("xlink:href", d => `http://www.capsinfo.com/images/MLB_Team_Logos/${urls[d.team]}.png`)
271 | ```
272 |
273 | And a label for the number of games the team has won.
274 | ```javascript
275 | let barLabels = bar.append('text')
276 | .attr('class', 'barlabel')
277 | .attr('x', d => x(d.value) + 10 + imgsize)
278 | .attr('y', y.bandwidth()/2 + 5)
279 | .text(d => d.value)
280 | ```
281 | 
282 |
283 | And lastly, we add the date to display the time frame.
284 | ```javascript
285 | const formatDate = d3.timeFormat('%b %-d, %Y')
286 | let dateLabel = labels.append('text')
287 | .attr('id', 'date')
288 | .attr('transform', `translate(0,-40)`)
289 | .text(formatDate(chartDate))
290 | ```
291 |
292 | ## Exercise #1 (Vertical Bar Chart)
293 | Lets try to apply what we've learned so far with an exercise.
294 |
295 | Go into the `exercise_1` folder and open up `exercise_1.js`. Change the url to `localhost:8000/exercise_1`. You should see a horizontal bar chart.
296 | 
297 |
298 | The goal of the exercise is to transform this horizontal bar chart into a vertical bar chart with the labels on the outside of the bars.
299 | 
300 |
301 | To approach this, we can split it up into a couple steps:
302 | 1. Switching from a bar chart horiziontal to vertical is essentially changing the scaling of the axes. Try using `scaleBand` for x and `scaleLinear` for y and change the `range` appropriately.
303 |
304 | 2. We must switch the range of data in the `domain`. How should we do that?
305 |
306 | 3. We also have to consider how the rectangles were specified. What is the new `height` and `width`? What is the new `x` and `y`?
307 |
308 | 4. Position the bar labels accordingly.
309 |
310 | ## Animating the Graph
311 | Now that we have an idea of how the graph set up and plotting works, we will dive into animation. Going back to our inital example, the animation happens within HTML's `setInterval()` method. The `setInterval()` method calls a function at specified intervals (in our case, `T`). Our function will perform one transition every interval.
312 |
313 | For each transition, we need to do the following:
314 | - update the date
315 | - re-construct a new dataset up to current date
316 | - update the x-axis and gridlines
317 | - update the bar chart
318 | - sort the data and update the y-axis
319 |
320 | Here we set up the period, `T`, and assign a variable name to our setInterval method. The first thing we do is increment the date by one day and then update the `dateLabel` variable with new text.
321 | ```javascript
322 | const T = 300
323 | let dailyUpdate = setInterval(function() {
324 | chartDate = d3.timeDay.offset(chartDate,1)
325 | dateLabel.text(formatDate(chartDate))
326 | // ...
327 | }, T);
328 | ```
329 |
330 | We need to update our dataset with our new date.
331 | ```javascript
332 | data = filterData(chartDate)
333 | ```
334 |
335 | We also need to update the graph's axes to make them responsive to the changing scores. We do this by updating the `x.domain` and then re-calling the `xAxis` and `gridlines` variables which are dependent on the variable `x`.
336 |
337 | For the x-axis, we are incrementing the scale by fives (arbitrary). D3 also has a `.nice()` method which you can add to let D3 choose a "nice" limit for you.
338 | ```javascript
339 | x.domain([0, Math.ceil(d3.max(data, d => d.value)/5)*5]);
340 | svg.select('.x.axis').transition().duration(T)
341 | .call(xAxis);
342 | svg.select('.grid').transition().duration(T)
343 | .call(gridlines);
344 | ```
345 |
346 | Here we update each team's bar graph. We attach our updated dataset to `rects`. We then re-specify the `width` attribute and `fill` style. If we did just this, this would actually give it the effect of stop-motion animation. We add the `.transition().duration(T)` part to smooth the transition. Similarly, we do the same for the logos and the bar labels. For the logos, we just need to update the `x` attribute and for the labels, we need to update the `x, y, text` attributes.
347 | ```javascript
348 | rects.data(data)
349 | .transition().duration(T)
350 | .attr("width", d => x(d.value))
351 | .style('fill', d => d3.interpolateRdYlBu(d.value/100))
352 |
353 | imgs.data(data)
354 | .transition().duration(T)
355 | .attr('x', d => x(d.value) + 5)
356 |
357 | barLabels.data(data)
358 | .transition().duration(T)
359 | .attr('x', d => x(d.value) + 10 + imgsize)
360 | .attr('y', y.bandwidth()/2 + 5)
361 | .text(d => d.value)
362 | ```
363 |
364 | For the y-axis, we need to update the `y.domain`. But we need to sort our new dataset first. We then update our `bar` variable by changing the `transform` attribute.
365 | ```javascript
366 | data.sort((a,b) => d3.descending(a.value,b.value));
367 | y.domain(data.map(d => d.team).reverse());
368 | bar.transition().duration(T)
369 | .attr("transform", d => `translate(0,${y(d.team)})`)
370 | ```
371 | Recall that the `bar` variable points to a group of `g` elements where we have grouped a bunched of other elements together. This has the advantage of allowing us to make one call to move them all instead of multiple separate calls. These elements include the rectangle, team text, logo, and bar label.
372 | 
373 |
374 | Lastly, we have to define an `if` statement to check if its the end of the season so we can stop the forever loop using `clearInterval()` to clear the timer we set earlier with `setInterval()`.
375 | ```javascript
376 | if (chartDate > new Date(2018,9,1)) {
377 | clearInterval(dailyUpdate)
378 | }
379 | ```
380 |
381 | ## Exercise #2 (Sort the Chart)
382 | We will start with the solution from Exercise #1.
383 |
384 | Go into folder `exercise_2` and open up `index.html` and `exercise_2.js`.
385 |
386 | Our goal is to add sorting to our graph. We want to be able to sort alphabetically and numerically with a click of a button.
387 |
388 | Sort alphabetically:
389 | 
390 |
391 | Sort numerically:
392 | 
393 |
394 | We will need to update both the HTML as well as the JavaScript. Here are some hints.
395 |
396 | Add this HTML snippet to the body of `index.html` to add two buttons
397 | ```html
398 |