@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.
@@ -1,18 +1,281 @@
1
+ import { Util } from "@dra2020/baseclient";
2
+ import { PlanType } from './dra-types';
3
+
4
+ // **** Dataset Codes Explained ****
5
+ // Elections:
6
+ // EYYGCC, where YY = year and CC = contest [PR, SE, GO, AG]
7
+ // C16GCO = composite (spans number of years)
8
+ // PYYGPR = PVI, where YY is year [16, 20]
9
+ // Census/ACS:
10
+ // DYYF = Census/ACS Total Pop, where YY = year [10, 18, 19, 20]
11
+ // DYYT = Census/ACS VAP/CVAP, where YY = year [10, 18, 19, 20]
12
+ // D20FA = Census 2020 Total Prisoner-Adjusted Pop
13
+ // D20TNH = Census 2020 VAP with Non-Hispanic Alone Race fields
14
+
15
+ // **** Dataset Fields Explained ****
16
+ // Elections:
17
+ // D: Democratic
18
+ // R: Republican
19
+ // Tot (Total R + D + Other)
20
+ // Census/ACS/VAP/CVAP:
21
+ // Tot: total
22
+ // Wh: White alone, not Hispanic
23
+ // His: All Hispanics
24
+ // Bl: Black alone, not Hispanic; BlC: Black combo, incl Hispanic
25
+ // Asn: Asian alone, not Hispanic; AsnC: Asian combo, incl Hispanic
26
+ // Nat: Native alone, not Hispanic; NatC: Native combo, incl Hispanic
27
+ // Pac (also PI): Pacific alone, not Hispanic; PacC: Pacific combo, incl Hispanic
28
+ // OthAl: Other alone, not Hispanic; Oth: Other + Two or more races, incl Hispanic
29
+ // Mix: Two or more races, not Hispanic
30
+ // AsnPI: Asian + Pacific, not Hispanic
31
+
32
+
33
+ export const AGG_DEMOGRAPHIC = 'demographic';
34
+ export const AGG_DEMOGRAPHIC18 = 'demographic18';
35
+ export const AGG_pres2008 = 'pres2008';
36
+ export const AGG_pres2016 = 'pres2016';
37
+ export const AGG_pvi = 'pvi';
38
+
39
+ export const DATASET_TYPE_DEMOGRAPHIC = 'demographic';
40
+ export const DATASET_TYPE_ELECTION = 'election';
41
+ export const DATASET_TYPE_PVI = 'pvi';
42
+
43
+ export const DS_PVI2020 = 'P20GPR';
44
+ export const PVI2020_Title = 'PVI 2016/2020';
45
+ export const DS_PVI2016 = 'P16GPR';
46
+ export const DS_PRES2020 = 'E20GPR';
47
+ export const DS_PRES2016 = 'E16GPR';
48
+
49
+ export interface StateMeta
50
+ {
51
+ state: string,
52
+ pop: number,
53
+ reps: number,
54
+ }
55
+
56
+ export interface StatesMetaIndex
57
+ {
58
+ [key: string]: StateMeta; // key is shortstate (2 letter) state name
59
+ }
60
+
61
+ export interface StatesMeta
62
+ {
63
+ [key: string]: StatesMetaIndex; // key is one of the datasource strings
64
+ }
65
+
66
+ export type FieldGetter = (f: string) => number;
67
+ export function fieldGetterNotLoaded(f: string): number { return undefined }
1
68
  export type PackedFields = Float64Array;
69
+ export interface PackedFieldsIndex
70
+ {
71
+ [field: string]: number; // offset into PackedFields
72
+ }
73
+
74
+ export interface PackedMetaIndex
75
+ {
76
+ length: number;
77
+ fields: { [dataset: string]: PackedFieldsIndex };
78
+ getDatasetField: (f: any, dataset: string, field: string) => number;
79
+ }
80
+
81
+ export interface DatasetMeta
82
+ {
83
+ type: string,
84
+ year: number,
85
+ title: string,
86
+ fields: {
87
+ [key: string]: any,
88
+ },
89
+ votingAge?: boolean,
90
+ office?: string,
91
+ subtype?: string,
92
+ description?: string,
93
+ nhAlone?: boolean,
94
+ privateKey?: string, // key for private data
95
+ members?: {[key: number]: string},
96
+ }
97
+ export type DatasetsMeta = { [dataset: string]: DatasetMeta };
98
+
99
+ export interface PrimaryDatasetKeys
100
+ {
101
+ SHAPES?: string,
102
+ CENSUS: string,
103
+ VAP: string,
104
+ ELECTION: string,
105
+ }
106
+
107
+ // This integregates the information associated with a specific state and datasource as
108
+ // well as user selections around which datasets to view. Used to propagate through UI.
109
+ export interface DatasetContext
110
+ {
111
+ dsIndex: PackedMetaIndex;
112
+ primeDDS: string; // Demographic (Census)
113
+ primeVDS: string; // VAP/CVAP
114
+ primeEDS: string; // Election
115
+ datasetMetaDDS: DatasetMeta;
116
+ datasetMetaVDS: DatasetMeta;
117
+ datasetMetaEDS: DatasetMeta;
118
+ }
119
+
120
+ // Dataset Lists
121
+ export type DSListItem = {
122
+ key: string,
123
+ title: string,
124
+ order: number,
125
+ };
126
+
127
+ export type DSList = DSListItem[];
128
+
129
+ export type DSLists = {
130
+ census: DSList,
131
+ vap: DSList,
132
+ election: DSList,
133
+ };
134
+
135
+ export type PlanTypePlus = PlanType | '';
136
+
137
+ export function fGetJoined(f: any): any[]
138
+ {
139
+ return (f.properties && f.properties.joined) ? f.properties.joined : undefined;
140
+ }
141
+
142
+ export function fGet(f: any, p: string): any
143
+ {
144
+ return fGetW(f, null, p);
145
+ }
146
+
147
+ // Note f is a direct GeoJSON feature
148
+ // Called when building packedFields; after that f.properties.datasets is deleted, so then it's only useful for non-dataset properties.
149
+ function fGetW(f: any, datasetKey: string, p: string): any
150
+ {
151
+ // pBackup helps support 2016_BG, which don't have the 'C' fields
152
+ const pBackup: string = (p === 'BlC') ? 'Bl' : (p === 'AsnC' || p === 'PacC') ? 'AsnPI' : (p === 'NatC') ? 'Nat' : null;
153
+
154
+ // Direct property?
155
+ if (f.properties && f.properties[p] !== undefined)
156
+ return f.properties[p];
157
+ else if (datasetKey && f.properties && f.properties.datasets && f.properties.datasets[datasetKey])
158
+ {
159
+ if (f.properties.datasets[datasetKey][p] != null)
160
+ return f.properties.datasets[datasetKey][p];
161
+ else if (pBackup && f.properties.datasets[datasetKey][pBackup] != null)
162
+ return f.properties.datasets[datasetKey][pBackup];
163
+ }
164
+
165
+ // Joined property?
166
+ let a: any[] = fGetJoined(f);
167
+ if (a)
168
+ {
169
+ for (let i: number = 0; i < a.length; i++)
170
+ {
171
+ let o: any = a[i];
172
+ if (!datasetKey)
173
+ {
174
+ if (o[p] !== undefined)
175
+ return o[p];
176
+ }
177
+ else
178
+ {
179
+ if (o['datasets'] && o['datasets'][datasetKey] != null)
180
+ {
181
+ if (o['datasets'][datasetKey][p] != null)
182
+ return o['datasets'][datasetKey][p];
183
+ else if (pBackup && o['datasets'][datasetKey][pBackup] != null)
184
+ return o['datasets'][datasetKey][pBackup];
185
+ }
186
+ }
187
+ }
188
+ }
189
+ return undefined;
190
+ }
191
+
192
+ export function computeMetaIndex(meta: DatasetsMeta): PackedMetaIndex
193
+ {
194
+ if (meta == null) return null;
195
+ let offset = 1; // first entry is count of aggregates
196
+ let index: PackedMetaIndex = { length: 0, fields: {}, getDatasetField: null };
197
+ Object.keys(meta).forEach((datasetKey: string) => {
198
+ let dataset = meta[datasetKey];
199
+ let fieldsIndex: PackedFieldsIndex = {};
200
+ Object.keys(dataset.fields).forEach((field: string) => {
201
+ fieldsIndex[field] = offset++;
202
+ });
203
+ index.fields[datasetKey] = fieldsIndex;
204
+ });
205
+ index.length = offset;
206
+ index.getDatasetField = (f: any, dataset: string, field: string): number => {
207
+ let pf = retrievePackedFields(f);
208
+ return getPackedField(index, pf, dataset, field);
209
+ };
210
+ return index;
211
+ }
2
212
 
3
- export function allocPackedFields(length: number): PackedFields
213
+ let nAlloc = 0;
214
+ function allocPackedFields(length: number): PackedFields
4
215
  {
5
216
  let ab = new ArrayBuffer(8 * length);
6
217
  let af = new Float64Array(ab);
218
+ nAlloc++;
219
+ //if ((nAlloc % 10000) == 0) console.log(`allocPackedFields: ${nAlloc} allocs`);
220
+ return af;
221
+ }
222
+
223
+ export function computePackedFields(f: any, index: PackedMetaIndex): PackedFields
224
+ {
225
+ if (f.properties.packedFields) return f.properties.packedFields as PackedFields;
226
+
227
+ let af = allocPackedFields(index.length);
228
+ af[0] = 0; // count of number of aggregates
229
+ Object.keys(index.fields).forEach((dataset: string) => {
230
+ let fields = index.fields[dataset];
231
+ Object.keys(fields).forEach((field: string) => {
232
+ let n = fGetW(f, dataset, field);
233
+ if (isNaN(n))
234
+ n = 0;
235
+ af[fields[field]] = n;
236
+ });
237
+ });
238
+ f.properties.packedFields = af; // cache here
239
+ f.properties.getDatasetField = index.getDatasetField;
240
+
241
+ // Major memory savings to delete this after packing
242
+ delete f.properties.datasets;
7
243
  return af;
8
244
  }
9
245
 
246
+ export function hasPackedFields(f: any): boolean
247
+ {
248
+ return f.properties.packedFields !== undefined;
249
+ }
250
+
251
+ export function setPackedFields(f: any, pf: PackedFields, fIndex: any): void
252
+ {
253
+ if (f.properties.packedFields !== undefined) throw 'Packed fields already set';
254
+ f.properties.packedFields = pf;
255
+ f.properties.getDatasetField = fIndex.properties.getDatasetField
256
+ }
257
+
258
+ export function retrievePackedFields(f: any): PackedFields
259
+ {
260
+ if (f.properties.packedFields === undefined) throw 'Feature should have pre-computed packed fields';
261
+ return f.properties.packedFields as PackedFields;
262
+ }
263
+
10
264
  // The first entry in the PackedFields aggregate is the count of items aggregated.
11
265
  // Treat a null instance as just a single entry with no aggregates.
12
266
  let abZero = new ArrayBuffer(8);
13
267
  let afZero = new Float64Array(abZero);
14
268
  afZero[0] = 0;
15
269
 
270
+ export function zeroPackedFields(index: PackedMetaIndex): PackedFields
271
+ {
272
+ if (index == null) return afZero;
273
+ let af = allocPackedFields(index.length);
274
+ for (let i = 0; i < af.length; i++)
275
+ af[i] = 0;
276
+ return af;
277
+ }
278
+
16
279
  export function zeroPackedCopy(pf: PackedFields): PackedFields
17
280
  {
18
281
  if (pf == null) return afZero;
@@ -45,3 +308,135 @@ export function aggregatePackedFields(agg: PackedFields, pf: PackedFields): Pack
45
308
  agg[0]++; // count of number of aggregates
46
309
  return agg;
47
310
  }
311
+
312
+ export function diffPackedFields(main: any, parts: any[]): PackedFields
313
+ {
314
+ main = packedCopy(retrievePackedFields(main));
315
+ if (main == null || parts == null || parts.length == 0) return null;
316
+ parts = parts.map(retrievePackedFields);
317
+ parts.forEach((pf: PackedFields) => {
318
+ for (let i = 0; i < main.length; i++)
319
+ main[i] -= pf[i];
320
+ });
321
+ return main;
322
+ }
323
+
324
+ export function getPackedField(index: PackedMetaIndex, pf: PackedFields, dataset: string, field: string): number
325
+ {
326
+ if (index == null || pf == null) return 0;
327
+ let fields = index.fields[dataset];
328
+ return fields ? (fields[field] !== undefined ? pf[fields[field]] : 0) : 0;
329
+ }
330
+
331
+ export function findPackedField(index: PackedMetaIndex, pf: PackedFields, dataset: string, field: string): number
332
+ {
333
+ let fields = index.fields[dataset];
334
+ return fields ? (fields[field] !== undefined ? fields[field] : -1) : -1;
335
+ }
336
+
337
+ export function ToGetter(agg: PackedFields, dc: DatasetContext, datasetKey: string): FieldGetter
338
+ {
339
+ return (field: string) => { return getPackedField(dc.dsIndex, agg, datasetKey, field) };
340
+ }
341
+
342
+ export function ToGetterPvi16(agg: PackedFields, dc: DatasetContext, datasetKey: string): FieldGetter
343
+ {
344
+ return (field: string) =>
345
+ {
346
+ if (field === 'R')
347
+ return Math.round((getPackedField(dc.dsIndex, agg, datasetKey, 'R12') + getPackedField(dc.dsIndex, agg, datasetKey, 'R16')) / 2);
348
+ if (field === 'D')
349
+ return Math.round((getPackedField(dc.dsIndex, agg, datasetKey, 'D12') + getPackedField(dc.dsIndex, agg, datasetKey, 'D16')) / 2);
350
+ if (field === 'Tot')
351
+ return Math.round((
352
+ getPackedField(dc.dsIndex, agg, datasetKey, 'R12') + getPackedField(dc.dsIndex, agg, datasetKey, 'R16') +
353
+ getPackedField(dc.dsIndex, agg, datasetKey, 'D12') + getPackedField(dc.dsIndex, agg, datasetKey, 'D16')) / 2);
354
+ return 0;
355
+ };
356
+ }
357
+
358
+ export function ToGetterPvi20(agg: PackedFields, dc: DatasetContext): FieldGetter
359
+ {
360
+ return (field: string) =>
361
+ {
362
+ if (field === 'R')
363
+ return Math.round((getPackedField(dc.dsIndex, agg, DS_PRES2016, 'R') + getPackedField(dc.dsIndex, agg, DS_PRES2020, 'R')) / 2);
364
+ if (field === 'D')
365
+ return Math.round((getPackedField(dc.dsIndex, agg, DS_PRES2016, 'D') + getPackedField(dc.dsIndex, agg, DS_PRES2020, 'D')) / 2);
366
+ if (field === 'Tot')
367
+ return Math.round((
368
+ getPackedField(dc.dsIndex, agg, DS_PRES2016, 'R') + getPackedField(dc.dsIndex, agg, DS_PRES2020, 'R') +
369
+ getPackedField(dc.dsIndex, agg, DS_PRES2016, 'D') + getPackedField(dc.dsIndex, agg, DS_PRES2020, 'D')) / 2);
370
+ return 0;
371
+ };
372
+
373
+ }
374
+
375
+ export function calcShift(agg: PackedFields, dc: DatasetContext, datasetOld: string, datasetNew: string): number
376
+ {
377
+ const getterOld = datasetOld === DS_PVI2016 ?
378
+ ToGetterPvi16(agg, dc, datasetOld) :
379
+ datasetOld === DS_PVI2020 ?
380
+ ToGetterPvi20(agg, dc) :
381
+ ToGetter(agg, dc, datasetOld);
382
+ const getterNew = datasetNew === DS_PVI2016 ?
383
+ ToGetterPvi16(agg, dc, datasetNew) :
384
+ datasetNew === DS_PVI2020 ?
385
+ ToGetterPvi20(agg, dc) :
386
+ ToGetter(agg, dc, datasetNew);
387
+
388
+ // Calc two-party Swing
389
+ const repOld = getterOld('R');
390
+ const demOld = getterOld('D');
391
+ const repNew = getterNew('R');
392
+ const demNew = getterNew('D');
393
+
394
+ if (repOld === undefined || demOld === undefined || repNew === undefined || demNew === undefined)
395
+ return null;
396
+
397
+ const totOld = demOld + repOld;
398
+ const totNew = demNew + repNew;
399
+ if (totOld <= 0 || totNew <= 0)
400
+ return null;
401
+
402
+ const pctDemOld: number = demOld / totOld;
403
+ const pctRepOld: number = repOld / totOld;
404
+ const pctDemNew: number = demNew / totNew;
405
+ const pctRepNew: number = repNew / totNew;
406
+ const shift: number = Math.max(Math.min((pctDemNew - pctDemOld) - (pctRepNew - pctRepOld), 1.0), -1.0);
407
+ return shift;
408
+ }
409
+
410
+ export function calcRawPvi(getter: FieldGetter): number
411
+ {
412
+ // ((((sum(d_2016) / (sum(d_2016) + sum(r_2016))) * 100) + ((sum(d_2012) / (sum(d_2012) + sum(r_2012))) * 100)) / 2) - 51.54
413
+ // Fields hard coded
414
+ let total2012 = getter('D12') + getter('R12');
415
+ let total2016 = getter('D16') + getter('R16');
416
+ let pct2012 = total2012 != 0 ? (getter('D12') / total2012) * 100 : 0;
417
+ let pct2016 = total2016 != 0 ? (getter('D16') / total2016) * 100 : 0;
418
+ return (pct2012 + pct2016) / ((total2012 != 0 && total2016 != 0) ? 2 : 1);
419
+ }
420
+
421
+ export function pviStr(getter: FieldGetter): string
422
+ {
423
+ const pviRaw: number = calcRawPvi(getter);
424
+ const pvi = Util.precisionRound(pviRaw != 0 ? pviRaw - 51.54 : 0, 2);
425
+ return pvi >= 0 ? 'D+' + pvi : 'R+' + (-pvi);
426
+ }
427
+
428
+ export function calcRaw2020Pvi(getter16: FieldGetter, getter20: FieldGetter): number
429
+ {
430
+ let total2016 = getter16('D') + getter16('R');
431
+ let total2020 = getter20('D') + getter20('R');
432
+ let pct2016 = total2016 != 0 ? (getter16('D') / total2016) * 100 : 0;
433
+ let pct2020 = total2020 != 0 ? (getter20('D') / total2020) * 100 : 0;
434
+ return (pct2020 + pct2016) / ((total2020 != 0 && total2016 != 0) ? 2 : 1);
435
+ }
436
+
437
+ export function pvi2020Str(getter16: FieldGetter, getter20: FieldGetter): string
438
+ {
439
+ const pviRaw: number = calcRaw2020Pvi(getter16, getter20);
440
+ const pvi = Util.precisionRound(pviRaw != 0 ? pviRaw - 51.54 : 0, 2);
441
+ return pvi >= 0 ? 'D+' + pvi : 'R+' + (-pvi);
442
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dra2020/dra-types",
3
- "version": "1.8.88",
3
+ "version": "1.8.89",
4
4
  "description": "Shared types used between client, server and tools.",
5
5
  "main": "dist/dra-types.js",
6
6
  "types": "./dist/all.d.ts",