@dra2020/dra-types 1.8.88 → 1.8.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,563 @@
1
+ // App libraries
2
+ import * as PF from "./packedfields";
3
+ import {Util, Colors} from '@dra2020/baseclient'
4
+
5
+ // All Groups Mosaic has 16 colors
6
+ // Index values into the 16 colors
7
+ export const ColorBySolidWhite = 0;
8
+ export const ColorBySolidBlack = 1;
9
+ export const ColorBySolidHispanic = 2;
10
+ export const ColorBySolidAsian = 3;
11
+ export const ColorByMostlyWhite = 4;
12
+ export const ColorByMostlyBlack = 5;
13
+ export const ColorByMostlyHispanic = 6;
14
+ export const ColorByMostlyAsian = 7;
15
+ export const ColorByMostlyNative = 8;
16
+ export const ColorByMix = 9;
17
+ export const ColorByHispanicWhite = 10;
18
+ export const ColorByBlackWhite = 11;
19
+ export const ColorByHispanicBlack = 12;
20
+ export const ColorByAsianWhite = 13;
21
+ export const ColorByAsianHispanic = 14;
22
+ export const ColorByBlackAsian = 15;
23
+
24
+ // Text color for All Groups Mosaic (Legend)
25
+ export const EthnicTextColor: string[] = [
26
+ '#ffffff',
27
+ '#ffffff',
28
+ '#ffffff',
29
+ '#ffffff',
30
+ '#000000',
31
+ '#000000',
32
+ '#000000',
33
+ '#000000',
34
+ '#000000',
35
+ '#000000',
36
+ '#000000',
37
+ '#000000',
38
+ '#ffffff',
39
+ '#000000',
40
+ '#000000',
41
+ '#ffffff',
42
+ ];
43
+
44
+ export const ColorByGreatR = 0;
45
+ export const ColorByGoodR = 1;
46
+ export const ColorByFairR = 2;
47
+ export const ColorByEven = 3;
48
+ export const ColorByFairD = 4;
49
+ export const ColorByGoodD = 5;
50
+ export const ColorByGreatD = 6;
51
+
52
+
53
+ // ****************** Colors, Color Stops and Color Functions for "classic" palettes *******************
54
+ const PartisanFewStops = [
55
+ 0.00, // Carmine
56
+ 0.20, //
57
+ 0.30, //
58
+ 0.40, //
59
+ 0.45, //
60
+ 0.50, // pale red
61
+ 0.50, // pale blue
62
+ 0.55, //
63
+ 0.60, //
64
+ 0.70, //
65
+ 0.80, //
66
+ 1.00, // Dark blue
67
+ ];
68
+
69
+ // Like PartisanFewStops, but lose the gradient at two buckets at each edge to focus on competitive district differentiation
70
+ export let PartisanDistrictStops = [
71
+ // Dark red
72
+ //{ stop: 0.00, color: '#8B0000' }, // Dark red
73
+ //{ stop: 0.40, color: '#8B0000' }, // .00 <= .40
74
+
75
+ // Crimson
76
+ //{ stop: 0.00, color: '#DC143C' }, // Crimson
77
+ //{ stop: 0.40, color: '#DC143C' }, // .00 <= .40
78
+
79
+ // Chilli Red
80
+ //{ stop: 0.00, color: '#C21807' }, // Chilli Red
81
+ //{ stop: 0.40, color: '#C21807' }, // .00 <= .40
82
+
83
+ // Carmine
84
+ 0.00, // Carmine
85
+ 0.40, // .00 <= .40
86
+
87
+ 0.40, //
88
+ 0.45, // .40 <= .45
89
+ 0.45, //
90
+ 0.50, // .45 <= .50
91
+ 0.50, //
92
+ 0.55, // .50 <= .55
93
+ 0.55, //
94
+ 0.60, // .55 <= .60
95
+ 0.60, //
96
+ 1.00, // .60 <= 1.0
97
+ ];
98
+
99
+ /*export let PartisanCustomStops = [
100
+ { stop: 0.00, color: '#FF0000' }, // Dark red
101
+ { stop: 0.20, color: '#FF0000' }, //
102
+ { stop: 0.30, color: '#FF0000' }, //
103
+ { stop: 0.40, color: '#CD5C5C' }, // Indian red
104
+ { stop: 0.45, color: '#DB7093' }, // Pale Violet Red
105
+ { stop: 0.499, color: '#DB7093' }, // Light Pink
106
+ { stop: 0.50, color: '#FFFFFF' }, // white
107
+ { stop: 0.501, color: '#87CEFA' }, // Light Sky Blue
108
+ { stop: 0.55, color: '#00bFFF' }, // Deep Sky Blue
109
+ { stop: 0.60, color: '#002366' }, // Royal Blue
110
+ { stop: 0.70, color: '#0000CD' }, // Medium Blue
111
+ { stop: 0.80, color: '#191970' }, //
112
+ { stop: 1.00, color: '#191970' }, // Midnight Blue
113
+ ];*/
114
+
115
+ export let PartisanPrecinctStops = PartisanFewStops;
116
+
117
+ export let EthnicFewStops = [
118
+ 0.00, //
119
+ 0.40, //
120
+ 0.50, //
121
+ 1.00, //
122
+ ];
123
+
124
+ export type PaletteDefaults = { [key: string]: string };
125
+ export const DefaultPaletteDefaults: PaletteDefaults = {
126
+ partisanScale: 'partisanclassic',
127
+ partisanDistrictsScale: 'partisandistrictsclassic',
128
+ demographicsScale: 'demographicsclassic',
129
+ demographicsAllGroups: 'allgroupsclassic',
130
+ };
131
+
132
+ export function makeStops(stops: number[], colors: string[]): Util.Stop[]
133
+ {
134
+ // Lengths should be equal
135
+ const len: number = stops.length <= colors.length ? stops.length : colors.length;
136
+ let result: Util.Stop[] = []
137
+ for (let i = 0; i < len; i++)
138
+ result.push({stop: stops[i], color: colors[i]});
139
+ return result;
140
+ }
141
+
142
+ function partisanStops(stops: number[], pd: PaletteDefaults): Util.Stop[]
143
+ {
144
+ const palette: PaletteName = pd['partisanScale'] as PaletteName;
145
+ return makeStops(stops, colorsFromStopsPartisan(palette, 'partisanScale', stops));
146
+ }
147
+
148
+ function partisanDistrictStops(stops: number[], pd: PaletteDefaults): Util.Stop[]
149
+ {
150
+ const palette: PaletteName = pd['partisanDistrictsScale'] as PaletteName;
151
+ return makeStops(stops, colorsFromStopsPartisan(palette, 'partisanDistrictsScale', stops));
152
+ }
153
+
154
+ function ethnicStops(stops: number[], pd: PaletteDefaults): Util.Stop[]
155
+ {
156
+ const palette: PaletteName = pd['demographicsScale'] as PaletteName;
157
+ if (palette === 'demographicsclassic')
158
+ return makeStops(stops, Colors.EthnicFewClassicColors);
159
+ return makeStops(stops, colorsFromStops(palette, stops, Colors.EthnicFewClassicColors));
160
+ }
161
+
162
+ export function ethnicBackgroundColor(index: number, pd: PaletteDefaults): string
163
+ {
164
+ const palette: PaletteName = pd['demographicsAllGroups'] as PaletteName;
165
+ const colors: string[] = allGroups16Colors(palette, Colors.getPalette(palette));
166
+ if (index < colors.length)
167
+ return colors[index];
168
+ return '#ffffff';
169
+ }
170
+
171
+ export function ToAllEthnicColor(agg: PF.PackedFields, dc: PF.DatasetContext, pd: PaletteDefaults): number
172
+ {
173
+ // Use VAP/CVAP if it exists
174
+ const dataset: string = dc.primeVDS ? dc.primeVDS : dc.primeDDS
175
+ return AggregateEthnicColor(PF.ToGetter(agg, dc, dataset), pd, dataset.endsWith('NH'));
176
+ }
177
+
178
+ export function ToPartisanColorStr(agg: PF.PackedFields, dc: PF.DatasetContext, pd: PaletteDefaults): string
179
+ {
180
+ return ToPartisanColor(agg, dc, partisanStops(PartisanPrecinctStops, pd));
181
+ }
182
+
183
+ export function ToPartisanDistrictColor(agg: PF.PackedFields, dc: PF.DatasetContext, pd: PaletteDefaults): string
184
+ {
185
+ return ToPartisanColor(agg, dc, partisanDistrictStops(PartisanDistrictStops, pd));
186
+ }
187
+
188
+ function ToPartisanColor(agg: PF.PackedFields, dc: PF.DatasetContext, stops: Util.GradientStops): string
189
+ {
190
+ if (dc.primeEDS === PF.DS_PVI2020)
191
+ {
192
+ const getter16 = PF.ToGetter(agg, dc, PF.DS_PRES2016);
193
+ const getter20 = PF.ToGetter(agg, dc, PF.DS_PRES2020);
194
+
195
+ const pviRaw = PF.calcRaw2020Pvi(getter16, getter20);
196
+ const color: string = ColorFromRGBPcts((1 - pviRaw / 100), 0, pviRaw / 100, stops);
197
+ //console.log('Pvi (r, d, color): (' + (1 - pviRaw/100) + ', ' + pviRaw/100 + ', ' + color + ')');
198
+ return color;
199
+ }
200
+ else if (dc.primeEDS === PF.DS_PVI2016)
201
+ {
202
+ const getter = PF.ToGetter(agg, dc, dc.primeEDS);
203
+ const pviRaw = PF.calcRawPvi(getter);
204
+ const color: string = ColorFromRGBPcts((1 - pviRaw/100), 0, pviRaw/100, stops);
205
+ return color;
206
+ }
207
+ else
208
+ {
209
+ const getter = PF.ToGetter(agg, dc, dc.primeEDS);
210
+ return AggregatePartisanColorStr(getter, stops);
211
+ }
212
+ }
213
+
214
+ export function ToPartisanShiftColor(agg: PF.PackedFields, dc: PF.DatasetContext, datasets: string[], pd: PaletteDefaults): string
215
+ {
216
+ if (!datasets || datasets.length < 2)
217
+ return '';
218
+
219
+ const shift: number = PF.calcShift(agg, dc, datasets[0], datasets[1]);
220
+ if (shift == null)
221
+ return null;
222
+ const rep: number = 0.5 - (shift / 2);
223
+ const dem: number = 0.5 + (shift / 2);
224
+ const stops: Util.GradientStops = partisanStops(PartisanPrecinctStops, pd);
225
+ const color: string = ColorFromRGBPcts(rep, 0, dem, stops);
226
+ //console.log('Shift (r, d, color): (' + rep + ', ' + dem + ', ' + color + ')');
227
+ return color;
228
+ }
229
+
230
+ export function ToEthnicColorStr(agg: PF.PackedFields, dc: PF.DatasetContext, pd: PaletteDefaults, detail: string): string
231
+ {
232
+ let ethnic: string = 'Wh';
233
+ let total: string = 'Tot';
234
+ let bInvert: boolean = false;
235
+ const dataset = dc.primeVDS ? dc.primeVDS : dc.primeDDS;
236
+ switch (detail)
237
+ {
238
+ case null: case '': case 'all':
239
+ let c = ToAllEthnicColor(agg, dc, pd); // special case
240
+ return c >= 0 ? ethnicBackgroundColor(c, pd) : '#ffffff';
241
+ case 'white': ethnic = 'Wh'; break;
242
+ case 'nonwhite': ethnic = 'Wh'; bInvert = true; break;
243
+ case 'black': ethnic = dataset.endsWith('NH') ? 'Bl' : 'BlC'; break;
244
+ case 'hisp': ethnic = 'His'; break;
245
+ case 'native': ethnic = dataset.endsWith('NH') ? 'Nat' : 'NatC'; break;
246
+ case 'asianpi': ethnic = 'AsnPI'; break;
247
+ case 'asian': ethnic = dataset.endsWith('NH') ? 'Asn' : 'AsnC'; break;
248
+ case 'pac': ethnic = dataset.endsWith('NH') ? 'Pac' : 'PacC'; break;
249
+ case 'other': ethnic = 'OthAl'; break;
250
+ case 'mix': ethnic = 'Mix'; break;
251
+ default: break;
252
+ }
253
+
254
+ const getter = PF.ToGetter(agg, dc, dataset);
255
+ let den = getter(total);
256
+ let num = getter(ethnic);
257
+ if (den === undefined || isNaN(den) || num === undefined || isNaN(num))
258
+ return '#ffffff';
259
+
260
+ if (den == 0)
261
+ return '#ffffff';
262
+ const pct = bInvert ? 1 - (num / den) : num / den;
263
+ return Util.execGradient(ethnicStops(EthnicFewStops, pd), pct);
264
+ }
265
+
266
+ // All Groups Mosaic
267
+ export function AggregateEthnicColor(getter: PF.FieldGetter, pd: PaletteDefaults, nhAlone: boolean): number
268
+ {
269
+ // Dataset should have 'Tot' field
270
+ let totField = 'Tot';
271
+ let totalPop = getter(totField);
272
+ if (totalPop == 0) return -1;
273
+
274
+ // Field names hard-coded
275
+ let white: number = getter('Wh') / totalPop;
276
+ let hisp: number = getter('His') / totalPop;
277
+ let black: number = 0;
278
+ let native: number = 0;
279
+ let asianPI: number = 0;
280
+ if (nhAlone)
281
+ {
282
+ black= getter('Bl') / totalPop;
283
+ native = getter('Nat') / totalPop;
284
+ asianPI = (getter('Asn') + getter('Pac')) / totalPop;
285
+ }
286
+ else
287
+ {
288
+ black= getter('BlC') / totalPop;
289
+ native = getter('NatC') / totalPop;
290
+ asianPI = (getter('AsnC') + getter('PacC')) / totalPop;
291
+ }
292
+
293
+ if (white >= 0.75)
294
+ return ColorBySolidWhite;
295
+ if (black >= 0.75)
296
+ return ColorBySolidBlack;
297
+ if (hisp >= 0.75)
298
+ return ColorBySolidHispanic;
299
+ if (asianPI >= 0.75)
300
+ return ColorBySolidAsian;
301
+ if (white >= 0.5 && black < 0.3 && hisp < 0.3 && native < 0.3 && asianPI < 0.3)
302
+ return ColorByMostlyWhite;
303
+ if (black >= 0.5 && white < 0.3 && hisp < 0.3 && native < 0.3 && asianPI < 0.3)
304
+ return ColorByMostlyBlack;
305
+ if (hisp >= 0.5 && white < 0.3 && black < 0.3 && native < 0.3 && asianPI < 0.3)
306
+ return ColorByMostlyHispanic;
307
+ if (asianPI >= 0.5 && white < 0.3 && black < 0.3 && native < 0.3 && hisp < 0.3)
308
+ return ColorByMostlyAsian;
309
+ if (native > 0.5)
310
+ return ColorByMostlyNative;
311
+ if (hisp >= 0.3 && white >= 0.3 && hisp + white > 0.75)
312
+ return ColorByHispanicWhite;
313
+ if (black >= 0.3 && white >= 0.3 && black + white > 0.75)
314
+ return ColorByBlackWhite;
315
+ if (hisp >= 0.3 && black >= 0.3 && hisp + black > 0.75)
316
+ return ColorByHispanicBlack;
317
+ if (asianPI >= 0.3 && white >= 0.3 && asianPI + white > 0.75)
318
+ return ColorByAsianWhite;
319
+ if (asianPI >= 0.3 && hisp >= 0.3 && asianPI + hisp > 0.75)
320
+ return ColorByAsianHispanic;
321
+ if (black >= 0.3 && asianPI >= 0.3 && black + asianPI > 0.75)
322
+ return ColorByBlackAsian;
323
+ return ColorByMix;
324
+ }
325
+
326
+ // This is used only for 2016_BG analytics, seemingly only for values, not colors (dave 12/9/21)
327
+ export function AggregatePartisanColor(getter: PF.FieldGetter): number
328
+ {
329
+ // Dataset should have 'Tot' field
330
+ let totField = 'Tot';
331
+ let presTot = getter(totField);
332
+ if (presTot == 0) return -1;
333
+
334
+ // TODO: Field names hard-coded
335
+ let presR = getter('R');
336
+ let presD = getter(' D');
337
+
338
+ if (presTot === undefined || presD === undefined || presR === undefined || presTot <= 0)
339
+ return -1;
340
+
341
+ let pctD = presD / presTot;
342
+ let pctR = presR / presTot;
343
+ let diff = pctD - pctR;
344
+ if (diff < -0.20)
345
+ return ColorByGreatR;
346
+ if (diff < -0.10)
347
+ return ColorByGoodR;
348
+ if (diff < -0.02)
349
+ return ColorByFairR;
350
+ if (diff > 0.20)
351
+ return ColorByGreatD;
352
+ if (diff > 0.10)
353
+ return ColorByGoodD;
354
+ if (diff > 0.02)
355
+ return ColorByFairD;
356
+ return ColorByEven;
357
+ }
358
+
359
+ export function AggregatePartisanColorStr(getter: PF.FieldGetter, stops: Util.GradientStops): string
360
+ {
361
+ // Dataset should have 'Tot' field
362
+ let totField = 'Tot';
363
+ let presTot = getter(totField);
364
+ if (presTot == 0) return '';
365
+
366
+ // TODO: Field names hard-coded
367
+ let presR = getter('R');
368
+ let presD = getter('D');
369
+
370
+ if (presTot === undefined || presD === undefined || presR === undefined || presTot <= 0)
371
+ return '';
372
+
373
+ let pctD: number = presD / presTot;
374
+ let pctR: number = presR / presTot;
375
+ let pctOth: number = (presTot - presD - presR) / presTot;
376
+ const color: string = ColorFromRGBPcts(pctR, pctOth, pctD, stops);
377
+ //console.log('Agg (r, d, color): (' + pctR + ', ' + pctD + ', ' + color + ')');
378
+ return color;
379
+ }
380
+
381
+ // This is the new gradient code
382
+ export function ColorFromRGBPcts(pctRed: number, pctGreen: number, pctBlue: number, stops: Util.GradientStops): string
383
+ {
384
+ let pctTot = pctRed + pctBlue;
385
+ if (pctTot == 0) return '#ffffff';
386
+ pctBlue /= pctTot;
387
+ return Util.execGradient(stops, pctBlue);
388
+ }
389
+
390
+ // ****************** End of "classic" palettes *****************
391
+
392
+ export type ColorUse = 'districts' | 'partisanScale' | 'demographicsScale' | 'demographicsAllGroups' | 'partisanDistrictsScale';
393
+
394
+ // Currently supported palettes
395
+ export const PaletteNames = ['jet_r', 'turbo_r', 'inferno_r', 'viridis_r', 'magma_r', 'plasma_r', 'Greys', 'bone_r',
396
+ 'draclassic', 'demographicsclassic', 'partisanclassic', 'allgroupsclassic', 'partisandistrictsclassic'] as const;
397
+ export type PaletteName = typeof PaletteNames[number];
398
+
399
+ export function colorsFromStops(palette: PaletteName, stops: number[], classicColors: string[]): string[]
400
+ {
401
+ // Use classicColors to see where there are duplicate colors
402
+ const allColors: string[] = Colors.getPalette(palette);
403
+ let colors: string[] = [];
404
+ for (let i = 0; i < stops.length; i++)
405
+ {
406
+ const stop: number =
407
+ (i + 1 < classicColors.length && classicColors[i] == classicColors[i + 1]) ? (stops[i] + stops[i + 1]) / 2 :
408
+ (i > 0 && i < classicColors.length && classicColors[i - 1] == classicColors[i]) ? (stops[i - 1] + stops[i]) / 2 : stops[i];
409
+
410
+ let inx: number = Math.floor(allColors.length * stop);
411
+ if (inx >= allColors.length) inx = allColors.length - 1;
412
+ colors.push(allColors[inx]);
413
+ }
414
+ return colors;
415
+ }
416
+
417
+ export function colorsFromStopsPartisan(palette: PaletteName, colorUse: ColorUse, stops: number[]): string[]
418
+ {
419
+ if (palette === 'partisanclassic')
420
+ return Colors.PartisanPrecinctClassicColors;
421
+ if (palette === 'partisandistrictsclassic')
422
+ return Colors.PartisanDistrictClassicColors;
423
+
424
+ // For partisanScale and partisanDistrictsScale; indexes match classic color pattern
425
+ const allColors: string[] = Colors.getPalette(palette);
426
+ if (allColors.length < Colors.MaxColors)
427
+ return Colors.PartisanPrecinctClassicColors; // Safety check; shouldn't happen
428
+
429
+ let colors: string[] = [];
430
+
431
+ const indexes: number[] = colorUse === 'partisanScale' ?
432
+ [15, 27, 37, 47, 57, 67, 82, 92, 102, 112, 122, 134] : [15, 15, 37, 37, 47, 67, 82, 102, 122, 122, 134, 134];
433
+ for (let i = 0; i < stops.length; i++)
434
+ {
435
+ colors.push(allColors[indexes[i]]);
436
+ }
437
+ return colors;
438
+ }
439
+
440
+ export function allGroups16Colors(palette: PaletteName, colors: string[])
441
+ {
442
+ let modColors: string[] = [];
443
+ //if (palette === 'allgroupsclassic') // Only support classic for now
444
+ {
445
+ modColors.push(Colors.EthnicBackgroundColor[ColorBySolidWhite]); // pos 0
446
+ modColors.push(Colors.EthnicBackgroundColor[ColorBySolidBlack]); // pos 1
447
+ modColors.push(Colors.EthnicBackgroundColor[ColorBySolidHispanic]);// pos 2
448
+ modColors.push(Colors.EthnicBackgroundColor[ColorBySolidAsian]); // pos 3
449
+ modColors.push(Colors.EthnicBackgroundColor[ColorByMostlyWhite]); // pos 4
450
+ modColors.push(Colors.EthnicBackgroundColor[ColorByMostlyBlack]); // pos 5
451
+ modColors.push(Colors.EthnicBackgroundColor[ColorByMostlyHispanic]);// pos 6
452
+ modColors.push(Colors.EthnicBackgroundColor[ColorByMostlyAsian]); // pos 7
453
+ modColors.push(Colors.EthnicBackgroundColor[ColorByMostlyNative]); // pos 8
454
+ modColors.push(Colors.EthnicBackgroundColor[ColorByMix]); // pos 9
455
+ modColors.push(Colors.EthnicBackgroundColor[ColorByHispanicWhite]);// pos 10
456
+ modColors.push(Colors.EthnicBackgroundColor[ColorByBlackWhite]); // pos 11
457
+ modColors.push(Colors.EthnicBackgroundColor[ColorByHispanicBlack]);// pos 12
458
+ modColors.push(Colors.EthnicBackgroundColor[ColorByAsianWhite]); // pos 13
459
+ modColors.push(Colors.EthnicBackgroundColor[ColorByAsianHispanic]);// pos 14
460
+ modColors.push(Colors.EthnicBackgroundColor[ColorByBlackAsian]); // pos 15
461
+ }
462
+ /*else if (palette === 'viridis_r' || palette === 'plasma_r')
463
+ {
464
+ modColors.push(colors[149]);
465
+ modColors.push(colors[129]);
466
+ modColors.push(colors[109]);
467
+ modColors.push(colors[89]);
468
+ modColors.push(colors[139]);
469
+ modColors.push(colors[119]);
470
+ modColors.push(colors[99]);
471
+ modColors.push(colors[79]);
472
+ modColors.push(colors[0]);
473
+ modColors.push(colors[9]);
474
+ modColors.push(colors[29]);
475
+ modColors.push(colors[19]);
476
+ modColors.push(colors[49]);
477
+ modColors.push(colors[39]);
478
+ modColors.push(colors[69]);
479
+ modColors.push(colors[59]);
480
+ }
481
+ else if (palette === 'magma_r')
482
+ {
483
+ modColors.push(colors[134]);
484
+ modColors.push(colors[116]);
485
+ modColors.push(colors[98]);
486
+ modColors.push(colors[80]);
487
+ modColors.push(colors[125]);
488
+ modColors.push(colors[107]);
489
+ modColors.push(colors[89]);
490
+ modColors.push(colors[71]);
491
+ modColors.push(colors[0]);
492
+ modColors.push(colors[8]);
493
+ modColors.push(colors[26]);
494
+ modColors.push(colors[17]);
495
+ modColors.push(colors[44]);
496
+ modColors.push(colors[35]);
497
+ modColors.push(colors[62]);
498
+ modColors.push(colors[53]);
499
+ }*/
500
+ return modColors;
501
+ }
502
+
503
+ export interface DistrictCache
504
+ {
505
+ colorElection?: string;
506
+ colorEthnic?: string;
507
+ colorSolid?: string;
508
+ }
509
+
510
+ // Color property we need out of the full DistrictProps
511
+ export interface ColorProp
512
+ {
513
+ color: string,
514
+ }
515
+ export type ColorProps = Array<ColorProp>;
516
+
517
+ export interface DistrictColorParams
518
+ {
519
+ datasetContext: PF.DatasetContext,
520
+ mapColors: ColorProps,
521
+ aggregates: PF.PackedFields[],
522
+ paletteDefaults: PaletteDefaults,
523
+ useFirstColor: boolean,
524
+ usePalette: string,
525
+ colorDistrictsBy: string,
526
+ }
527
+
528
+ export function computeDistrictColors(params: DistrictColorParams): DistrictCache[]
529
+ {
530
+ let dcNew: DistrictCache[] = [];
531
+ for (let i: number = 0; i < params.aggregates.length; i++)
532
+ {
533
+ let agg = params.aggregates[i];
534
+ let mapColor = i < params.mapColors.length ? params.mapColors[i].color : Colors.genColor(i, params.useFirstColor, params.usePalette);
535
+ let dc: DistrictCache = {};
536
+
537
+ // Compute election color and partisan color
538
+ dc.colorEthnic = mapColor;
539
+ dc.colorElection = ToPartisanDistrictColor(agg, params.datasetContext, params.paletteDefaults);
540
+
541
+ switch (params.colorDistrictsBy)
542
+ {
543
+ case 'election':
544
+ dc.colorSolid = dc.colorElection;
545
+ break;
546
+ case 'nonwhite':
547
+ case 'hisp':
548
+ case 'black':
549
+ case 'native':
550
+ case 'asian':
551
+ case 'pac':
552
+ case 'all':
553
+ dc.colorEthnic = ToEthnicColorStr(agg, params.datasetContext, params.paletteDefaults, params.colorDistrictsBy);
554
+ dc.colorSolid = dc.colorEthnic;
555
+ break;
556
+ default:
557
+ dc.colorSolid = mapColor;
558
+ break;
559
+ }
560
+ dcNew.push(dc);
561
+ }
562
+ return dcNew;
563
+ }
package/lib/dra-types.ts CHANGED
@@ -42,6 +42,7 @@ export interface UserLikes
42
42
  // congress, upper, lower and other => the plan is for entire state
43
43
  // county and city => plan is for a single county or city within a state
44
44
  export type PlanType = 'congress' | 'upper' | 'lower' | 'county' | 'city' | 'coi' | 'other';
45
+ export type PlanTypeOrEmpty = PlanType | '';
45
46
 
46
47
  // Once we attempt to contact, they've optedin or optedout (both mean email is valid and not blocked, even if not "validated")
47
48
  // implicitoptedin means we contacted them successfully and they didn't optout, but also did not explicitly optin