-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.js
411 lines (367 loc) · 19.5 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
"use strict"
/* define the canves space */
let width = 1200; // svg width
let height = 800; // svg height
/* define the margin and also make it easier to set positon */
let margin = {
left: 100,
right: 100,
top: 200,
bottom: 100
};
/* add svg tag to the div which ID is canvas and set by definesd width and height */
let svg = d3.select("#canvas")
.append("svg")
.attr("width", width) //the width for draw
.attr("height", height); //the height for draw
let dataset;//claim the dataset name
/* get data from data.json and use the drawScatter func */
(async function () {
dataset = await d3.json("data.json").then(drawScatter);
})();
/** function drawScatter(dataset)
draw scatterplot from my json data
* @param {object} dataset - a json data for draw visualization
**/
function drawScatter(dataset) {
/* sort data and make small data shows on top
"Hits" here use first character upper case since I would use Object.entries to show keys and values on page */
dataset.sort(function(a, b) {return b.brothersStat.Hits - a.brothersStat.Hits}) //shows smaller circle at top
/* define the x linear scales
domain: from min number of data to max,
range: the px range on page */
let xScale = d3.scaleLinear() //the scale of Batting Average (x)
/* d3.min(a, b) > the min number of b in a
d3.max(a, b) > the max number of b in a
-0.01 is to make the bubble do not shows on the line */
.domain([d3.min(dataset, function(v) {return v.brothersStat.Hits / v.brothersStat["At Bat"]-0.01}), d3.max(dataset, function(v) {return v.brothersStat.Hits / v.brothersStat["At Bat"]})]) //detail of => https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/Arrow_functions
.range([margin.left, width - margin.right]);
/* define the y linear scales */
let yScale = d3.scaleLinear() //the scale of scores (y),
/* -1 is to make the bubble do not shows on the line */
.domain([d3.min(dataset, function(v) {return v.brothersStat.Scores})-1, d3.max(dataset, function(v) {return v.brothersStat.Scores})])
.range([height - margin.bottom, margin.top]);
/* define the radius of circles, I use scaleLinear instead of d3.scaleSqrt here for make bigger numbers more different */
let rScale = d3.scaleLinear() //the scale of Hits (r), .range is radius of the circle
.domain([d3.min(dataset, function(v) {return v.brothersStat.Hits}), d3.max(dataset, function(v) {return v.brothersStat.Hits})])
.range([5, 55])
/* draw circles */
let circles = svg.selectAll("circle")//select circle tags
.data(dataset) //use the dateset that give in the function
.join("circle") //add circle tags
/* set the x of circles by the following defined xScale */
.attr("cx", function(v) {
/* "Hits" here use first character upper case since I use Object.entries to show keys and values on page */
return xScale(v.brothersStat.Hits / v.brothersStat["At Bat"]) //Batting Average of Brothers (Hits/At Bat)
})
/* set the y of circles by the following defined yScale */
.attr("cy", function(v) {
return yScale(v.brothersStat.Scores) //The scores Brothers Got in a game
})
/* set the radius of circles by the following defined rScale */
.attr("r", function(v) {
return rScale(v.brothersStat.Hits) //Hits of Brothers
})
//.attr("stroke", "white") //shows storke of circle for easier to read
.style("opacity", .5) //use opacity for shows circles clearer if they covered each other
.attr("fill", function(v) { //set colors for satisfaction level, from 0 to 2
if (v.satisfaction==2) { //Satisfied
return "Orange";
} else if (v.satisfaction==1) { //Medium
return "Gold";
} else { //Not Satisfied
return "Silver";
}
})
/* register the event when mouseover element to show circle values clearly and use data as v
learn from http://bl.ocks.org/WilliamQLiu/76ae20060e19bf42d774 */
.on("mouseover", function (v) {
d3.select(this) //select this circle
.style("opacity", 1) //change opacity
/* draw date labels for circles, select all texts that the class is label */
svg.append("text") //add text tags
.attr("class", "hoverLable") //give it hoverLable class
.attr("font-family", "sans-serif") //use sans-serif font
.attr("font-size", "11px") //set font size
.attr("font-weight", 600) //bold texts
.attr("fill", function() { //fill color by game result //we don't use v in the function since v is in mouseover function
if (v.brothersStat.Scores-v.opponentStat.Scores > 0) {//if Brothers win
return "#DD5F8B"; //red
} else if (v.brothersStat.Scores-v.opponentStat.Scores < 0) {//if Brothers lose
return "#119CBF"; //blue
}//if tie use default color
})
.text(function() {return v.date}) //defint what the lable will show
/* make the x offset are in the middle of the circle
the x of circle middle is "v.brothersStat.Hits / v.brothersStat["At Bat"]"
the middle of text is "this.getComputedTextLength/2"
so use "circle middle x" - "middle of text" could put lable on the center of circle
or we can use .attr("text-anchor", "middle") to set x on the center*/
.attr("x", function () {return xScale(v.brothersStat.Hits / v.brothersStat["At Bat"]) - this.getComputedTextLength()/2})
/* I want put the label under the circle, so the y should plus the radius of the circle */
.attr("y", function () {return yScale(v.brothersStat.Scores)+rScale(v.brothersStat.Hits)+10})
/* draw error labels for circles, select all texts that the class is label2*/
svg.append("text") //add text tags
.attr("class", "hoverLable2") //give it hoverLable class
.attr("font-family", "Arial", "sans-serif") //first use Arial font to make the circle shows the same on differet pc
.attr("font-size", "12px")
.attr("font-weight", 800)
/* shows error & unearned run as labels */
.text(function() {
let text=""; //if no error and unearned run, show nothing
/* add a "。" in text by error count */
for (let i=0; i<v.brothersStat.Errors; i++) {
text=text+"○";
}
//if (v.brothersStat["Unearned Run"]>0) {
//text=text+"/";
/* add a "‧" in text by unearned run count */
for (let i=0; i<v.brothersStat["Unearned Run"]; i++) {
text=text+"●";
}
return text; //return text to label text
//}
})
/* make the x offset are under the date lable
or we can use .attr("text-anchor", "middle") to set x on the center */
.attr("x", function () {return xScale(v.brothersStat.Hits / v.brothersStat["At Bat"]) - this.getComputedTextLength()/2})
.attr("y", function () {return yScale(v.brothersStat.Scores)+rScale(v.brothersStat.Hits)+20})
d3.selectAll(".label") //selece elements that class is label
.style("visibility", "hidden") //hide it when mouse over
d3.selectAll(".label2")
.style("visibility", "hidden")
})
/* register the event when mouseout element to show circle values clearly and use data as v*/
.on("mouseout", function () {
d3.select(this)
.style("opacity", .5)
d3.selectAll(".hoverLable").remove(); //remove the class that added after mouse over
d3.selectAll(".hoverLable2").remove();
d3.selectAll(".label")
.style("visibility", "visible") //show the element we hide again when mouse leave
d3.selectAll(".label2")
.style("visibility", "visible")
})
/* draw date labels for circles, select all texts that the class is label */
let labels = svg.selectAll("text.label")
.data(dataset) //use the dateset that give in function
.join("text") //add text tags
.attr("class", "label") //give it label class
.attr("font-family", "sans-serif") //use sans-serif font
.attr("font-size", "11px")
.attr("font-weight", 600) //bold texts
.attr("fill", function(v) { //fill color by game result
if (v.brothersStat.Scores-v.opponentStat.Scores > 0) {//if Brothers win
return "#DD5F8B"; //red
} else if (v.brothersStat.Scores-v.opponentStat.Scores < 0) {//if Brothers lose
return "#119CBF"; //blue
}//if tie use default color
})
.text(function(v) {return v.date}) //defint what the lable will show
/* make the x and y offset are in the middle of the circle
the x of circle middle is "v.brothersStat.Hits / v.brothersStat["At Bat"]"
the middle of text is "this.getComputedTextLength/2"
so use "circle middle x" - "middle of text" could put lable on the center of circle
or we can use .attr("text-anchor", "middle") to set x on the center
//=> is not work here since "this" can not be used */
.attr("x", function (v) {return xScale(v.brothersStat.Hits / v.brothersStat["At Bat"]) - this.getComputedTextLength()/2})
.attr("y", function (v) {return yScale(v.brothersStat.Scores)+5}) //5 is the half of the text height
/* draw error labels for circles, select all texts that the class is label2*/
let labels2 = svg.selectAll("text.label2")
.data(dataset) //use the dateset that give in function
.join("text") //add text tags
.attr("class", "label2") //give it label2 class
.attr("font-family", "Arial", "sans-serif") //first use Arial font to make the circle shows the same on differet pc
.attr("font-size", "10px")
.attr("font-weight", 800)
/* shows error & unearned run as labels*/
.text(function(v) {
let text=""; //if no error and unearned run, show nothing
/* add a "。" in text by error count*/
for (let i=0; i<v.brothersStat.Errors; i++) {
text=text+"○";
}
//if (v.brothersStat["Unearned Run"]>0) {
//text=text+"/";
/* add a "‧" in text by unearned run count*/
for (let i=0; i<v.brothersStat["Unearned Run"]; i++) {
text=text+"●";
}
return text; //return text to label text
//}
})
/* make the x and y offset are under the date lable
or we can use .attr("text-anchor", "middle") to set x on the center*/
.attr("x", function (v) {return xScale(v.brothersStat.Hits / v.brothersStat["At Bat"]) - this.getComputedTextLength()/2})
.attr("y", function (v) {return yScale(v.brothersStat.Scores)+18})
/* add g tag in svg to give sapce for xAxis and show it */
let xAxis = svg.append("g")
.attr("class", "axis") //give it axis class
.attr("transform", `translate(0, ${height-margin.bottom})`) //direct by use translate the position to put the axis
/* call d3 function to make an axis and use the definded xScale for x axis
shows 3 numbers after decimal point https://github.com/d3/d3-axis, baseball game shows 3 numbers in AVG
the 1st arg in ticks() is the min count of ticks, it's multiples of five */
.call(d3.axisBottom().scale(xScale).ticks(15, ",.3f"));
/* add g tag in svg to give sapce for yAxis and show it */
let yAxis = svg.append("g")
.attr("class", "axis")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft().scale(yScale));
/* add text tag in svg for xAxisLabel */
let xAxisLabel = svg.append("text")
.attr("class", "axisLabel") //use css axisLabel class
/* if margin left and right are different
|-margin.left-|-----xAxis-----|-margin.right-|
|-------------------width--------------------|
center of the axis should be:
.attr("x", (width-margin.left-margin.right)/2+margin.left)
if margin left and right are the same, then width / 2 is center */
.attr("x", width / 2) //set x offset
.attr("y", height - margin.bottom / 2) //set y offset
.attr("text-anchor", "middle") //anchor by middle for make label shows on center
.text("Batting Average of Brothers (Hits/At Bat)"); //the text of label
/* add text tag in svg for yAxisLabel */
let yAxisLabel = svg.append("text")
.attr("class", "axisLabel")
.attr("transform", "rotate(-90)")
/* margin top and bottom are different
|-margin.top-|-----yAxis-----|-margin.bottom-|
|-------------------width--------------------|
so center of the axis should be: */
.attr("x", -(height-margin.top-margin.bottom)/2-margin.top)
.attr("y", margin.left - margin.left /4)
.attr("text-anchor", "middle")
.text("Scores of Brothers");
/* the title of the plot */
svg.append("text")
.attr("x", 80)
.attr("y", 30)
.attr("text-anchor", "left")
.style("font-size", "22px")
.text("The Relationship with My Satisfaction and Brothers' Data (Interactable)");
/* start of draw 3 color circles and texts that for users to understand the meaning */
for (let i=0; i<3; i++) {
let color = "Orange"; //color of circle. if not 1 and 2. set default value as orange
let text = "Satisfied (2)"; //label of circle. if not 1 and 2. set default value as Satisfied (2)
if (i==1) {
color = "Gold";
text = "Medium (1)";
} else if (i==2) {
color = "Silver";
text = "Not Satisfied the game (0)";
}
svg.append("circle") //draw the circles for indicating
.attr("r", 15) //the radius of circles
.attr("fill", color) //fill color by defined value
.attr("cx", 120+i*150) //the x are 120 270 420
.attr("cy", 65) //the y offset of circles
svg.append("text") //texts to explain the colors of circles
.text(text) //write text by defined value
.attr("x", 140+i*150) //the x are 140 290 440
.attr("y", 70)
.attr("font-family", "sans-serif") //use sans-serif font
.attr("font-size", "13px"); //set font size
} //end of draw these circles
/* write texts to explain the meaning of radius */
svg.append("text")
.attr("x", 110) //set x offset
.attr("y", 105) //set y offset
.attr("font-family", "sans-serif")
.attr("font-size", "13px")
.attr("font-weight", 600) //use bold for following texts https://richardbrath.wordpress.com/2018/11/24/using-font-attributes-with-d3-js/
.text("Radius: ") //write the description
.append("tspan") //separate two texts then make them different https://richardbrath.wordpress.com/2018/11/24/using-font-attributes-with-d3-js/
.attr("font-weight", 400) //normal texts https://richardbrath.wordpress.com/2018/11/24/using-font-attributes-with-d3-js/
.text("Hits of Brothers");
/* write texts to explain the meaning of date color */
svg.append("text")
.attr("x", 110)
.attr("y", 125)
.attr("font-family", "sans-serif")
.attr("font-size", "13px")
.attr("font-weight", 600) //use bold for following texts
.text("Date Color: ")
.append("tspan") //separate two texts so we can make it different
.attr("font-weight", 400) //normal texts
.attr("fill", "#DD5F8B") //set the text color for following text (red)
.text("Win | ")
.append("tspan") //separate two texts
.attr("fill", "#119CBF") //set the text color for following text (blue)
.text("Lose | ")
.append("tspan") //separate two texts
.attr("fill", "#000") //set the text color for following text
.text("Tie");
/* write texts to explain the dots on circle */
svg.append("text")
.attr("x", 110)
.attr("y", 145)
.attr("font-family", "sans-serif")
.attr("font-size", "13px")
.attr("font-weight", 600) //bold texts
.text("[○]: ")
.append("tspan") //separate two texts
.attr("font-weight", 400) //normal texts
.text("1 error from Brothers / ")
.append("tspan") //separate two texts
.attr("font-weight", 600) //bold texts
.text("[●]: ")
.append("tspan") //separate two texts
.attr("font-weight", 400) //normal texts
.text("1 unearned run gave to opponent")
/* the description of the plot */
svg.append("text")
.attr("class", "subtitle")
.attr("x", 650)
.attr("y", 55)
.attr("text-anchor", "left")
.style("font-size", "14px")
.style("fill", "grey")
.text("Since the bubbles are covering each other, I made the the scatterplot to be interactive. This scatterplot shows the relationship between the different variables of Brothers and my satisfaction.")
.call(wrap, 450); //use the wrap function in the js file
return dataset; // global it and we can use later
}
/* wrote by example in class, I didn't edit it including comments */
function wrap(text, chartWidth) {
text.each(function () {
let text = d3.select(this);
let words = text.text().split(/\s+/).reverse(); // split on spaces
let word;
let line = [];
let lineNumber = 0;
let linechartHeight = 1.1; // ems
let x = text.attr("x"); // get the "x" attribute of our text that we're adding tspans to
let y = text.attr("y"); // get the "y" attribute of our text that we're adding tspans to
let dy = 1.1; // extra spacing
let tspan = text.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) { // note assignment here, not comparison! will get "false" when no words left
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > chartWidth) { // if we exceeded line chartWidth...
line.pop(); // pop that last added item off
tspan.text(line.join(" "));
line = [word]; // start the next line with the word we are currently processing
// note prefix ++ which means "add 1 to lineNumber before using it"!
tspan = text.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * linechartHeight + dy + "em").text(word); // generate the tspan element for the line
} // end condition of line chartWidth exceeded (and thus draw line)
} // end loop to process words
});
}
/* abnormal data
}, {
"date": "10/19",
"brothersStat": {
"Scores": 6,
"Hits": 2,
"At Bat": 29,
"Errors": 0,
"Unearned Run": 0
},
"opponentStat": {
"Scores": 5,
"Hits": 9,
"At Bat": 35,
"Errors": 3,
"Unearned Run": 2
},
"satisfaction": 0
*/