-
Notifications
You must be signed in to change notification settings - Fork 0
/
p2.js
504 lines (453 loc) · 16.8 KB
/
p2.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
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
/*
You do your work for Project 2 in this file, within the region
indicated below. Comments throughout give details.
The information exported from this "p2" (see bottom) are:
p2.transDur: duration of transitions (in ms)
p2.hexWidth: per-state hexagon size
p2.circRad: radius of circles use to indicate states in bivarate maps
p2.cmlSize: width=height of colormap legend picture
p2.rowFinish: function called for each datum (row of .csv) by d3.csv()
p2.dataFinish: function called once at the end data read by d3.csv()
p2.choiceSet: function called with radioButton changes
Note that index.html sets:
p2.usData: data as read by d3.csv() and post-processed by p2.dataFinish()
p2.cmlContext, p2.cmlImage: canvas context and image for colormap legend
Beyond that, how you set this up is entirely up to you, and what you
put in here, is up to you. New functions or variables that are created
by rowFinish or dataFinish, that are used by choiceSet, should be in the
"p2" namespace (their names should started with "p2." e.g. "p2.helper")
*/
/* module syntax directly copied from d3.v4.js */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.p2 = global.p2 || {})));
}(this, (function (exports) { 'use strict';
// the marks in the colormap legend should transition() with this duration
const transDur = 500;
const hexWidth = 60; // size of hexagons in US map
const circRad = 5; // size of circle marks in bivariate map
const cmlSize = 210; // width and height of picture of colormap
/* ------------------------- Do not change anything above this line */
/* ALL YOUR WORK GOES IN HERE: the three important functions (rowFinish,
dataFinish and choiceSet), as well as any other variables or utility
functions you want to help implement those three functions. All the comments
(including this) are tips/hints that you can erase if you want. */
//declare range global vars
var ARrange, EMrange, UNrange, OBrange, IMrange,
VUrange, WUrange, VBrange, WBrange,
ESwrange, ESmrange, ERwrange, ERmrange, Emax, EWrange, EWWrange;
function rowFinish(d) {
/* compute here the information about each state that will be needed for
visualization later (e.g. unemployment rate)) */
// loading in the variables
d.Area = +d.Area;
d.Population = +d.Population;
// calculating unemployment rate
d.UnempRate = 100-100*(+d.Employed/+d.LaborForce);
// calculating employment rate
d.EmpRate = 100*(+d.Employed/+d.Population);
d.WomenEarning = +d.WomenEarning;
d.MenEarning = +d.MenEarning;
d.Obesity = +d.Obesity;
d.InfantMortality = +d.InfantMortality;
// calculating political leanings in the elections
d.PL1 = (+d.Obama)/(1+(+d.Romney)+(+d.Obama));
d.PL2 = (+d.Clinton)/(1+(+d.Trump)+(+d.Clinton));
// calculating total voters, initiating variables for further use
d.VT = Math.max(((+d.Obama)+(+d.Romney)), ((+d.Clinton)+(+d.Trump)));
d.VF = 0;
d.VA = 0;
d.WF = 0;
d.WA = 0;
d.esw = 0;
d.esm = 0;
d.erw = 0;
d.erm = 0;
console.log(d);
return d; // keep this line, or else data becomes empty
}
function dataFinish(data) {
/* compute here, with the help of one or more "data.map(function(d) {
... })", per-state information that can only be computed once all the
data has read in (e.g. the political leaning variable). Also, learn
here (once) the extents (the min-to-max range, as learned by
d3.extent()) for variables that need to be displayed with the colormap
and indicated in the colormap legend. Then, create some convenient and
uniform way to refer to the information needed for every variable
display: e.g. how to retrieve that variable from each element of the
data array, the min-to-max extent (as from d3.extent()) of that
variable, and which colormap to use. */
// calculating parameters for voter maps
var VTmax = d3.max(data, function(d) { return +d.VT; });
data.map(function(d) { d.VF = ((+d.Obama)+(+d.Romney))/VTmax});
data.map(function(d) { d.VA = 1 - Math.pow((1-d.VF),3) });
data.map(function(d) { d.WF = ((+d.Clinton)+(+d.Trump))/VTmax});
data.map(function(d) { d.WA = 1 - Math.pow((1-d.WF),3) });
// calculating params for earnings
Emax = d3.max(data, function(d) { return Math.max(d.WomenEarning,d.MenEarning); });
var EWmax = d3.max(data, function(d) { return +d.WomenEarning});
var EMmax = d3.max(data, function(d) { return +d.MenEarning});
var EWmin = d3.min(data, function(d) { return +d.WomenEarning});
var EMmin = d3.min(data, function(d) { return +d.MenEarning});
data.map(function(d) { d.esw = d.WomenEarning/Emax});
data.map(function(d) { d.esm = d.MenEarning/Emax});
data.map(function(d) { d.erw = (d.WomenEarning-EWmin)/(EWmax-EWmin)});
data.map(function(d) { d.erm = (d.MenEarning-EMmin)/(EMmax-EMmin)});
// calculating ranges
ARrange = d3.extent(data, function(d){ return +d.Area});
EMrange = d3.extent(data, function(d){ return +d.EmpRate});
UNrange = d3.extent(data, function(d){ return +d.UnempRate});
OBrange = d3.extent(data, function(d){ return +d.Obesity});
IMrange = d3.extent(data, function(d){ return +d.InfantMortality});
VUrange = d3.extent(data, function(d){ return +d.PL1});
WUrange = d3.extent(data, function(d){ return +d.PL2});
VBrange = d3.extent(data, function(d){ return +d.VA});
WBrange = d3.extent(data, function(d){ return +d.WA});
ESwrange = d3.extent(data, function(d){ return +d.esw});
ESmrange = d3.extent(data, function(d){ return +d.esm});
ERwrange = d3.extent(data, function(d){ return +d.erw});
ERmrange = d3.extent(data, function(d){ return +d.erm});
EWrange = d3.extent(data, function(d){ return +d.WomenEarning});
EWWrange = d3.extent(data, function(d){ return +d.MenEarning});
}
// function that returns appropriate colorscale given type of map
function getColorScale(wat) {
var cs;
switch(wat) {
// area : simple cube root scale
case "AR":
cs = d3.scalePow()
.domain(ARrange)
.range([d3.rgb(0,0,0), d3.rgb(255,255,255)])
.exponent(1/3);
return cs;
// unemployment rate: rgb scale with 4 steps
case "UN":
cs = d3.scaleLinear()
.domain([UNrange[0],
(1/3)*(UNrange[1]-UNrange[0])+UNrange[0],
(2/3)*(UNrange[1]-UNrange[0])+UNrange[0],
UNrange[1]])
.range([d3.rgb(0,0,0),
d3.rgb(230,0,0),
d3.rgb(255,230,0),
d3.rgb(255,255,255)]);
return cs;
// employment rate: inverse of above
case "EM":
cs = d3.scaleLinear()
.domain([EMrange[0],
(1/3)*(EMrange[1]-EMrange[0])+EMrange[0],
(2/3)*(EMrange[1]-EMrange[0])+EMrange[0],
EMrange[1]])
//.interpolate(d3.interpolateRgb)
.range([d3.rgb(255,255,255),
d3.rgb(255,230,0),
d3.rgb(230,0,0),
d3.rgb(0,0,0)]);
return cs;
// infant mortality: transform domain to [0,1], hcl scales
case "IM":
var X = d3.scaleLinear().domain(IMrange).range([0,1]);
var H = d3.scaleLinear().domain([0,1])
.range([330,0]);
function C(x) { return 23*Math.pow(Math.sin(Math.PI*x),2)};
function L(x) { return 10+90*x};
cs = function(a) { return d3.hcl(H(X(a)), C(X(a)), L(X(a)))};
return cs;
// obesity: hsl scale with 3 steps, 10 ticks
case "OB":
var mid = (1/2)*(OBrange[1]-OBrange[0]) + OBrange[0];
var hslcolors = ["hsl(150,100%,50%)",
"hsl(150,0%,50%)",
"hsl(330,100%,50%)"];
var quant = d3.scaleQuantile()
.domain([OBrange[0], mid, OBrange[1]])
.range([0,1,2,3,4,5,6,7,8]);
cs = d3.scaleLinear()
.domain([0,4,8])
.range(hslcolors);
return function(x){return cs(quant(x))};
// earnings: simple lab mapping
case "ES":
case "ER":
cs = function (a,b) { return d3.lab((30+45*(a+b)),
0,
230*(a-b))};
return cs;
// electoral uni :
case "VU":
case "WU":
var c1 = d3.rgb(210,0,0);
var c2 = d3.rgb(0,0,210);
var H1 = d3.hcl(c1).h;
var H2 = d3.hcl(c2).h;
function Hu(x) { if (x<0.5)
{return H1}
else
{return H2}};
var Cmin = d3.hcl(c1).c;
var Cmax = d3.hcl(c2).c;
function Cu(x) { if (x<0.5)
{return Cmin*(1-Math.pow((1-Math.abs(x-0.5)/0.5),4))}
else
{return Cmax*(1-Math.pow((1-Math.abs(x-0.5)/0.5),4))}};
var Lu = d3.scaleLinear()
.domain([0,0.5,1])
.range([d3.hcl(c1).l,
d3.hcl("darkslategray").l,
d3.hcl(c2).l]);
cs = function(xu)
{return d3.hcl(Hu(xu),Cu(xu),Lu(xu))};
return cs;
// electoral bivariate:
case "VB":
case "WB":
var c1 = d3.rgb(210,0,0);
var c2 = d3.rgb(0,0,210);
var H1 = d3.hcl(c1).h;
var H2 = d3.hcl(c2).h;
var Cmin = d3.hcl(c1).c;
var Cmax = d3.hcl(c2).c;
var Lu = d3.scaleLinear()
.domain([0,0.5,1])
.range([d3.hcl(c1).l,
d3.hcl("darkslategray").l,
d3.hcl(c2).l]);
function bi_chroma(x) {return d3.scaleLinear()
.domain([0,1])
.range([0, Cu(x)])};
function bi_lumi(x) {return d3.scaleLinear()
.domain([0,1])
.range([100, Lu(x)])};
cs = function(xu, v)
{return d3.hcl(Hu(xu),bi_chroma(xu)(v),bi_lumi(xu)(v))};
return cs;
}
}
//apply coloscale for appropriate data
function colorMe(wat,d){
var cs = getColorScale(wat);
var dt = getData(wat,d);
if (uni(wat)){
return cs(dt);
} else {
return cs(dt[0], dt[1]);
}
}
//return range given type of map
function getRange(wat){
var cs = getColorScale(wat);
switch(wat){
case "AR": return ARrange;
case "UN": return UNrange;
case "EM": return EMrange;
case "OB": return OBrange;
case "IM": return IMrange;
case "ES": return ERmrange;
case "ER": return ERmrange;
case "VU":
case "WU":
case "VB":
case "WB":
return [0,1]
}
}
//return the appropriate data for map
function getData(wat,d){
switch(wat){
case "AR": return (d.Area);
case "UN": return (d.UnempRate);
case "EM": return (d.EmpRate);
case "OB": return (d.Obesity);
case "IM": return (d.InfantMortality);
case "ES": return [d.esm, d.esw];
case "ER": return [d.erm, d.erw];
case "VU": return (d.PL1);
case "WU": return (d.PL2);
case "VB": return [d.PL1, d.VA];
case "WB": return [d.PL2, d.VA];
}
}
//color the canvas with appropriate scheme
function canvasMe(wat,d){
var watrange = getRange(wat);
var cs = getColorScale(wat);
var canvasscl = d3.scaleLinear()
.range(watrange)
.domain([0,p2.cmlSize]);
if (uni(wat)){
for (var j=0, k=0; j < p2.cmlSize; ++j) {
for (var i=0; i < p2.cmlSize; ++i) {
var cc = cs(canvasscl(i));
p2.cmlImage.data[k++] = d3.rgb(cc).r; // red
p2.cmlImage.data[k++] = d3.rgb(cc).g; // green
p2.cmlImage.data[k++] = d3.rgb(cc).b; // blue
p2.cmlImage.data[k++] = 255; // opacity; keep at 255
}
}
} else {
var canvasscly = d3.scaleLinear()
.range(watrange)
.domain([p2.cmlSize,0]);
for (var j=0, k=0; j < p2.cmlSize; ++j) {
var yval = canvasscly(j);
for (var i=0; i < p2.cmlSize; ++i) {
var cc = cs(canvasscl(i),yval);
p2.cmlImage.data[k++] = d3.rgb(cc).r; // red
p2.cmlImage.data[k++] = d3.rgb(cc).g; // green
p2.cmlImage.data[k++] = d3.rgb(cc).b; // blue
p2.cmlImage.data[k++] = 255; // opacity; keep at 255
}
}
}
p2.cmlContext.putImageData(p2.cmlImage, 0, 0);
}
//get position of cml ticks by axis
function getPos(wat,d,axis){
var r = getRange(wat);
var cmlScalex = d3.scaleLinear()
.domain(r)
.range([0,p2.cmlSize]);
var cmlScaley = d3.scaleLinear()
.domain(r)
.range([p2.cmlSize,0]);
var dt = getData(wat,d);
if (axis=="x"){
if (uni(wat)){
return cmlScalex(dt);
} else {
return cmlScalex(dt[0]);
}
} else {
return cmlScaley(dt[1]);
}
}
//if not integer, format to 2 decimal places
function formatFloat(x) {
if (x % 1 === 0) {
return x;
} else {
return d3.format(",.2f")(x);
}
}
//return appropriate labels
function labelMe(wat){
var Xrange, Yrange;
switch (wat){
case "ES":
Xrange = EWWrange;
Yrange = EWrange;
break;
case "ER":
Xrange = Yrange = [0, Emax];
break;
default:
Xrange = Yrange = getRange(wat);
}
var x0 = formatFloat(Xrange[0]);
var x1 = formatFloat(Xrange[1]);
if (uni(wat)){
var y0="";
var y1="";
} else {
var y0 = Yrange[0];
var y1 = Yrange[1];
}
d3.select("#xminlabel").html("<text>" + x0 + "</text>");
d3.select("#xmaxlabel").html("<text>" + x1 + "</text>");
d3.select("#yminlabel").html("<text>" + y0 + "</text>");
d3.select("#ymaxlabel").html("<text>" + y1 + "</text>");
}
//return if map univariate or not
function uni(wat){
switch(wat){
case "ES":
case "ER":
case "VB":
case "WB":
return 0;
}
return 1;
}
function choiceSet(wat) {
/* is this a univariate map? */
var uni = (["AR", "EM", "UN", "OB", "IM", "VU", "WU"].indexOf(wat) >= 0);
console.log("choiceSet(", wat, ", ", (uni ? "uni" : "bi") + "variate)"); /* feel free to remove this line before handing in */
// define transition properties
var t = d3.transition()
.duration(p2.transDur)
.ease(d3.easeLinear);
/* 0) based on "wat", get all the information (created in dataFinish())
about how to visualize "wat" */
/* 1) apply colormap to the states in #mapUS. Be sure to use a transition
of duration p2.transDur. Try starting with:
d3.select("#mapUS").selectAll("path").data(p2.usData) ... and set the
color with .style("fill", function(d){ return ... color ...; })*/
d3.select("#mapUS").selectAll("path").data(p2.usData)
.transition(t)
.style("fill", function(d){ return colorMe(wat,d)});
/* 2) reset pixels of cmlImage.data, by traversing it via (see index.html):
for (var j=0, k=0; j < p2.cmlSize; j++) {
for (var i=0; i < p2.cmlSize; i++) {
p2.cmlImage.data[k++] = ... red (from 0 to 255) ... ;
p2.cmlImage.data[k++] = ... green ... ;
p2.cmlImage.data[k++] = ... blue ... ;
p2.cmlImage.data[k++] = 255; // opacity
}
}
For the univariate colormaps, only compute the colormap values for the
first row, and then on subsequent rows just copy the values from
previous row. Finally, redisplay image with:
p2.cmlContext.putImageData(p2.cmlImage, 0, 0);
Transitions on canvases are more work, so it is okay for this colormap
image to change suddenly (w/out transition of duration p2.transDur) */
canvasMe(wat,p2.usData);
/* 3) Update the labels at the corners of the colormap with
d3.select("#xminlabel").html("<text>" + x0 + "</text>"); where x0 is
the minimum value shown along the X axis of the colormap, and similarly
for the other three labels (xmaxlabel, yminlabel, ymaxlabel). The
labels should show the range of the "wat" values that are being
colormapped. For univariate maps, set xminlabel and yminlabel to show
the range, and set yminlabel and ymaxlabel to an empty string. For
bivariate maps, set all labels to show the X and Y ranges. */
labelMe(wat);
/* 4) update (with a transition of duration p2.transDur) the attributes of
the #cmlMarks ellipses to display the appropriate set of per-state
marks over the colormap legend. For univariate maps, set:
rx = 0.5 (e.g. .attr("rx", 0.5))
ry = p2.cmlSize/4
cx = ... position to indicate data value ...
cy = p2.cmlSize/2
For bivariate maps, set:
rx = p2.circRad
ry = p2.circRad
cx = ... position to indicate data value along X ...
cy = ... position to indicate data value along Y ... */
if (uni){
d3.select("#cmlMarks").selectAll("ellipse").data(p2.usData)
.transition(t)
.attr("rx",0.5)
.attr("ry", p2.cmlSize/4)
.attr("cx", function(d){ return getPos(wat,d,"x")})
.attr("cy", p2.cmlSize/2)
} else {
d3.select("#cmlMarks").selectAll("ellipse").data(p2.usData)
.transition(t)
.attr("rx",p2.circRad)
.attr("ry", p2.circRad)
.attr("cx", function(d){ return getPos(wat,d,"x")})
.attr("cy", function(d){ return getPos(wat,d,"y")})
}
}
/* ------------------------- Do not change anything below this line */
exports.hexWidth = hexWidth;
exports.transDur = transDur;
exports.circRad = circRad;
exports.cmlSize = cmlSize;
exports.rowFinish = rowFinish;
exports.dataFinish = dataFinish;
exports.choiceSet = choiceSet;
Object.defineProperty(exports, '__esModule', { value: true });
})));