@dra2020/district-analytics 10.0.9 → 10.1.0

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.
@@ -122,6 +122,8 @@ const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
122
122
  const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
123
123
  const results_1 = __webpack_require__(/*! ./results */ "./src/results.ts");
124
124
  const results_2 = __webpack_require__(/*! ./results */ "./src/results.ts");
125
+ // 11-03-2020 - Added for racially polarized voting analysis
126
+ const minority_1 = __webpack_require__(/*! ./minority */ "./src/minority.ts");
125
127
  const Poly = __importStar(__webpack_require__(/*! @dra2020/poly */ "@dra2020/poly"));
126
128
  const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
127
129
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
@@ -173,6 +175,11 @@ class AnalyticsSession {
173
175
  }
174
176
  return true;
175
177
  }
178
+ // 11-03-2020 - Added for racially polarized voting analysis
179
+ // NOTE - This assumes that analyzePlan() has been run!
180
+ analyzeRacialPolarization(id, bLog = false) {
181
+ return minority_1.doAnalyzeRacialPolarization(this, id, bLog);
182
+ }
176
183
  // NOTE - This assumes that analyzePlan() has been run!
177
184
  getDistrictStatistics(bLog = false) {
178
185
  return results_2.prepareDistrictStatistics(this, bLog);
@@ -276,56 +283,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
276
283
  const G = __importStar(__webpack_require__(/*! @dra2020/dra-graph */ "@dra2020/dra-graph"));
277
284
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
278
285
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
279
- // import { isConnected, isEmbedded } from './valid';
280
286
  const compact_1 = __webpack_require__(/*! ./compact */ "./src/compact.ts");
281
287
  const political_1 = __webpack_require__(/*! ./political */ "./src/political.ts");
282
288
  // DEBUG COUNTERS
283
289
  let nMissingDataset = 0;
284
290
  let nMissingProperty = 0;
285
- // DISTRICT STATISTICS
286
- // Indexes for statistics fields in Districts
287
- // NOTE - Not a const, so the number can be determined dynamically
288
- var DistrictField;
289
- (function (DistrictField) {
290
- DistrictField[DistrictField["TotalPop"] = 0] = "TotalPop";
291
- DistrictField[DistrictField["PopDevPct"] = 1] = "PopDevPct";
292
- DistrictField[DistrictField["bEqualPop"] = 2] = "bEqualPop";
293
- DistrictField[DistrictField["bNotEmpty"] = 3] = "bNotEmpty";
294
- DistrictField[DistrictField["bContiguous"] = 4] = "bContiguous";
295
- DistrictField[DistrictField["bNotEmbedded"] = 5] = "bNotEmbedded";
296
- DistrictField[DistrictField["CountySplits"] = 6] = "CountySplits";
297
- // 10-22-2020 - Added Other votes
298
- DistrictField[DistrictField["DemVotes"] = 7] = "DemVotes";
299
- DistrictField[DistrictField["RepVotes"] = 8] = "RepVotes";
300
- DistrictField[DistrictField["OtherVotes"] = 9] = "OtherVotes";
301
- // TwoPartyVote, // Two-party total ()= Dem + Rep) not all votes!
302
- DistrictField[DistrictField["DemPct"] = 10] = "DemPct";
303
- DistrictField[DistrictField["RepPct"] = 11] = "RepPct";
304
- DistrictField[DistrictField["OtherPct"] = 12] = "OtherPct";
305
- // End - OtherVotes changes
306
- DistrictField[DistrictField["DemSeat"] = 13] = "DemSeat";
307
- DistrictField[DistrictField["TotalVAP"] = 14] = "TotalVAP";
308
- DistrictField[DistrictField["MinorityPop"] = 15] = "MinorityPop";
309
- DistrictField[DistrictField["WhitePop"] = 16] = "WhitePop";
310
- DistrictField[DistrictField["BlackPop"] = 17] = "BlackPop";
311
- DistrictField[DistrictField["HispanicPop"] = 18] = "HispanicPop";
312
- DistrictField[DistrictField["PacificPop"] = 19] = "PacificPop";
313
- DistrictField[DistrictField["AsianPop"] = 20] = "AsianPop";
314
- DistrictField[DistrictField["NativePop"] = 21] = "NativePop";
315
- DistrictField[DistrictField["WhitePct"] = 22] = "WhitePct";
316
- DistrictField[DistrictField["MinorityPct"] = 23] = "MinorityPct";
317
- DistrictField[DistrictField["BlackPct"] = 24] = "BlackPct";
318
- DistrictField[DistrictField["HispanicPct"] = 25] = "HispanicPct";
319
- DistrictField[DistrictField["PacificPct"] = 26] = "PacificPct";
320
- DistrictField[DistrictField["AsianPct"] = 27] = "AsianPct";
321
- DistrictField[DistrictField["NativePct"] = 28] = "NativePct"; // Display
322
- })(DistrictField = exports.DistrictField || (exports.DistrictField = {}));
323
291
  class Districts {
324
292
  constructor(s, ds) {
325
293
  this._geoProperties = {};
326
294
  this._session = s;
327
295
  this._shapes = ds;
328
- this.statistics = this.initStatistics();
296
+ this.table = this.initTable();
329
297
  }
330
298
  getDistrictShapes() {
331
299
  return this._shapes;
@@ -348,21 +316,44 @@ class Districts {
348
316
  return null;
349
317
  }
350
318
  setGeoProperties(i, p) { this._geoProperties[i] = p; }
351
- numberOfColumns() { return U.countEnumValues(DistrictField); }
352
319
  // +1 for dummy unassigned 0 "district" and +1 for N+1 summary "district" for
353
320
  // state-level values. Real districts are 1–N.
354
321
  numberOfRows() { return this._session.state.nDistricts + 2; }
355
322
  numberOfWorkingDistricts() { return this._session.state.nDistricts + 1; }
356
- // This is the core statistics 2D matrix:
357
- // Fields on the outside, districts on the inside
358
- initStatistics() {
359
- let nRows = this.numberOfRows();
360
- let nCols = this.numberOfColumns();
361
- let outer = U.initArray(nCols, undefined);
362
- for (let i = 0; i < nCols; i++) {
363
- outer[i] = U.initArray(nRows, null);
364
- }
365
- return outer;
323
+ initTable() {
324
+ let nRows = this.numberOfRows(); // # of districts plus unassigned & summary
325
+ const t = {
326
+ totalPop: U.initArray(nRows, null),
327
+ popDevPct: U.initArray(nRows, null),
328
+ bEqualPop: U.initArray(nRows, null),
329
+ bNotEmpty: U.initArray(nRows, null),
330
+ bContiguous: U.initArray(nRows, null),
331
+ bNotEmbedded: U.initArray(nRows, null),
332
+ countySplits: U.initArray(nRows, null),
333
+ demVotes: U.initArray(nRows, null),
334
+ repVotes: U.initArray(nRows, null),
335
+ otherVotes: U.initArray(nRows, null),
336
+ demPct: U.initArray(nRows, null),
337
+ repPct: U.initArray(nRows, null),
338
+ otherPct: U.initArray(nRows, null),
339
+ demSeat: U.initArray(nRows, null),
340
+ totalVAP: U.initArray(nRows, null),
341
+ minorityPop: U.initArray(nRows, null),
342
+ whitePop: U.initArray(nRows, null),
343
+ blackPop: U.initArray(nRows, null),
344
+ hispanicPop: U.initArray(nRows, null),
345
+ pacificPop: U.initArray(nRows, null),
346
+ asianPop: U.initArray(nRows, null),
347
+ nativePop: U.initArray(nRows, null),
348
+ whitePct: U.initArray(nRows, null),
349
+ minorityPct: U.initArray(nRows, null),
350
+ blackPct: U.initArray(nRows, null),
351
+ hispanicPct: U.initArray(nRows, null),
352
+ pacificPct: U.initArray(nRows, null),
353
+ asianPct: U.initArray(nRows, null),
354
+ nativePct: U.initArray(nRows, null)
355
+ };
356
+ return t;
366
357
  }
367
358
  // This is the workhorse computational routine!
368
359
  recalcStatistics(bLog = false) {
@@ -559,36 +550,34 @@ class Districts {
559
550
  }
560
551
  { // UPDATE THE DISTRICT STATISTICS
561
552
  // NOTE - These are set below for both non-empty & empty districts:
562
- // this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
563
- // this.statistics[DistrictField.bContiguous][i] = bContiguous;
564
- // this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
565
- // this.statistics[DistrictField.TotalPop][i] = totalPop;
566
- // this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
567
- this.statistics[DistrictField.PopDevPct][i] = popDevPct;
568
- // 10-22-2020 - Added Other votes
569
- this.statistics[DistrictField.DemVotes][i] = demVotes;
570
- this.statistics[DistrictField.RepVotes][i] = repVotes;
571
- this.statistics[DistrictField.OtherVotes][i] = othVotes;
572
- // this.statistics[DistrictField.TwoPartyVote][i] = totVotes;
573
- this.statistics[DistrictField.DemPct][i] = demPct;
574
- this.statistics[DistrictField.RepPct][i] = repPct;
575
- this.statistics[DistrictField.OtherPct][i] = othPct;
576
- this.statistics[DistrictField.DemSeat][i] = DemSeat;
577
- this.statistics[DistrictField.WhitePop][i] = whitePop;
578
- this.statistics[DistrictField.MinorityPop][i] = minorityPop;
579
- this.statistics[DistrictField.BlackPop][i] = blackPop;
580
- this.statistics[DistrictField.HispanicPop][i] = hispanicPop;
581
- this.statistics[DistrictField.PacificPop][i] = pacificPop;
582
- this.statistics[DistrictField.AsianPop][i] = asianPop;
583
- this.statistics[DistrictField.NativePop][i] = nativePop;
584
- this.statistics[DistrictField.TotalVAP][i] = totalVAP;
585
- this.statistics[DistrictField.WhitePct][i] = whitePct;
586
- this.statistics[DistrictField.MinorityPct][i] = minorityPct;
587
- this.statistics[DistrictField.BlackPct][i] = blackPct;
588
- this.statistics[DistrictField.HispanicPct][i] = hispanicPct;
589
- this.statistics[DistrictField.PacificPct][i] = pacificPct;
590
- this.statistics[DistrictField.AsianPct][i] = asianPct;
591
- this.statistics[DistrictField.NativePct][i] = nativePct;
553
+ // * bNotEmpty;
554
+ // * bContiguous;
555
+ // * bNotEmbedded;
556
+ // * totalPop;
557
+ // * bEqualPop;
558
+ this.table.popDevPct[i] = popDevPct;
559
+ this.table.demVotes[i] = demVotes;
560
+ this.table.repVotes[i] = repVotes;
561
+ this.table.otherVotes[i] = othVotes;
562
+ this.table.demPct[i] = demPct;
563
+ this.table.repPct[i] = repPct;
564
+ this.table.otherPct[i] = othPct;
565
+ this.table.demSeat[i] = DemSeat;
566
+ this.table.whitePop[i] = whitePop;
567
+ this.table.minorityPop[i] = minorityPop;
568
+ this.table.blackPop[i] = blackPop;
569
+ this.table.hispanicPop[i] = hispanicPop;
570
+ this.table.pacificPop[i] = pacificPop;
571
+ this.table.asianPop[i] = asianPop;
572
+ this.table.nativePop[i] = nativePop;
573
+ this.table.totalVAP[i] = totalVAP;
574
+ this.table.whitePct[i] = whitePct;
575
+ this.table.minorityPct[i] = minorityPct;
576
+ this.table.blackPct[i] = blackPct;
577
+ this.table.hispanicPct[i] = hispanicPct;
578
+ this.table.pacificPct[i] = pacificPct;
579
+ this.table.asianPct[i] = asianPct;
580
+ this.table.nativePct[i] = nativePct;
592
581
  }
593
582
  { // ACCUMULATE STATE STATISTICS FROM DISTRICT TOTALS
594
583
  // 10-22-2020 - Added Other votes
@@ -608,60 +597,58 @@ class Districts {
608
597
  }
609
598
  }
610
599
  else { // If a district is empty, zero these results (vs. null)
611
- this.statistics[DistrictField.PopDevPct][i] = popDevPct;
612
- // 10-22-2020 - Added Other votes
613
- this.statistics[DistrictField.DemVotes][i] = 0;
614
- this.statistics[DistrictField.RepVotes][i] = 0;
615
- this.statistics[DistrictField.OtherVotes][i] = 0;
616
- // this.statistics[DistrictField.TwoPartyVote][i] = 0;
617
- this.statistics[DistrictField.DemPct][i] = 0;
618
- this.statistics[DistrictField.RepPct][i] = 0;
619
- this.statistics[DistrictField.OtherPct][i] = 0;
620
- this.statistics[DistrictField.DemSeat][i] = 0;
621
- this.statistics[DistrictField.WhitePop][i] = 0;
622
- this.statistics[DistrictField.MinorityPop][i] = 0;
623
- this.statistics[DistrictField.BlackPop][i] = 0;
624
- this.statistics[DistrictField.HispanicPop][i] = 0;
625
- this.statistics[DistrictField.PacificPop][i] = 0;
626
- this.statistics[DistrictField.AsianPop][i] = 0;
627
- this.statistics[DistrictField.NativePop][i] = 0;
628
- this.statistics[DistrictField.TotalVAP][i] = 0;
629
- this.statistics[DistrictField.WhitePct][i] = 0;
630
- this.statistics[DistrictField.MinorityPct][i] = 0;
631
- this.statistics[DistrictField.BlackPct][i] = 0;
632
- this.statistics[DistrictField.HispanicPct][i] = 0;
633
- this.statistics[DistrictField.PacificPct][i] = 0;
634
- this.statistics[DistrictField.AsianPct][i] = 0;
635
- this.statistics[DistrictField.NativePct][i] = 0;
600
+ this.table.popDevPct[i] = popDevPct;
601
+ this.table.demVotes[i] = 0;
602
+ this.table.repVotes[i] = 0;
603
+ this.table.otherVotes[i] = 0;
604
+ this.table.demPct[i] = 0;
605
+ this.table.repPct[i] = 0;
606
+ this.table.otherPct[i] = 0;
607
+ this.table.demSeat[i] = 0;
608
+ this.table.whitePop[i] = 0;
609
+ this.table.minorityPop[i] = 0;
610
+ this.table.blackPop[i] = 0;
611
+ this.table.hispanicPop[i] = 0;
612
+ this.table.pacificPop[i] = 0;
613
+ this.table.asianPop[i] = 0;
614
+ this.table.nativePop[i] = 0;
615
+ this.table.totalVAP[i] = 0;
616
+ this.table.whitePct[i] = 0;
617
+ this.table.minorityPct[i] = 0;
618
+ this.table.blackPct[i] = 0;
619
+ this.table.hispanicPct[i] = 0;
620
+ this.table.pacificPct[i] = 0;
621
+ this.table.asianPct[i] = 0;
622
+ this.table.nativePct[i] = 0;
636
623
  }
637
624
  { // UPDATE THESE DISTRICT STATISTICS, EVEN WHEN THEY ARE EMPTY
638
- this.statistics[DistrictField.TotalPop][i] = totalPop;
639
- this.statistics[DistrictField.bNotEmpty][i] = bNotEmpty;
640
- this.statistics[DistrictField.bContiguous][i] = bContiguous;
641
- this.statistics[DistrictField.bNotEmbedded][i] = bNotEmbedded;
642
- this.statistics[DistrictField.bEqualPop][i] = bEqualPop;
643
- this.statistics[DistrictField.CountySplits][i] = countySplits;
625
+ this.table.totalPop[i] = totalPop;
626
+ this.table.bNotEmpty[i] = bNotEmpty;
627
+ this.table.bContiguous[i] = bContiguous;
628
+ this.table.bNotEmbedded[i] = bNotEmbedded;
629
+ this.table.bEqualPop[i] = bEqualPop;
630
+ this.table.countySplits[i] = countySplits;
644
631
  }
645
632
  }
646
633
  // UPDATE STATE STATISTICS
647
634
  let summaryRow = this.numberOfRows() - 1;
648
635
  // 10-22-2020 - Added Other votes
649
636
  if (stateTotVote > 0) {
650
- this.statistics[DistrictField.DemVotes][summaryRow] = stateDemVote;
651
- this.statistics[DistrictField.RepVotes][summaryRow] = stateRepVote;
652
- this.statistics[DistrictField.DemPct][summaryRow] = stateDemVote / stateTotVote;
653
- this.statistics[DistrictField.RepPct][summaryRow] = stateRepVote / stateTotVote;
654
- this.statistics[DistrictField.OtherPct][summaryRow] = stateOthVote / stateTotVote;
637
+ this.table.demVotes[summaryRow] = stateDemVote;
638
+ this.table.repVotes[summaryRow] = stateRepVote;
639
+ this.table.demPct[summaryRow] = stateDemVote / stateTotVote;
640
+ this.table.repPct[summaryRow] = stateRepVote / stateTotVote;
641
+ this.table.otherPct[summaryRow] = stateOthVote / stateTotVote;
655
642
  }
656
643
  if (stateVAPPop > 0) {
657
- this.statistics[DistrictField.TotalVAP][summaryRow] = stateVAPPop;
658
- this.statistics[DistrictField.WhitePct][summaryRow] = stateWhitePop / stateVAPPop;
659
- this.statistics[DistrictField.MinorityPct][summaryRow] = stateMinorityPop / stateVAPPop;
660
- this.statistics[DistrictField.BlackPct][summaryRow] = stateBlackPop / stateVAPPop;
661
- this.statistics[DistrictField.HispanicPct][summaryRow] = stateHispanicPop / stateVAPPop;
662
- this.statistics[DistrictField.PacificPct][summaryRow] = statePacificPop / stateVAPPop;
663
- this.statistics[DistrictField.AsianPct][summaryRow] = stateAsianPop / stateVAPPop;
664
- this.statistics[DistrictField.NativePct][summaryRow] = stateNativePop / stateVAPPop;
644
+ this.table.totalVAP[summaryRow] = stateVAPPop;
645
+ this.table.whitePct[summaryRow] = stateWhitePop / stateVAPPop;
646
+ this.table.minorityPct[summaryRow] = stateMinorityPop / stateVAPPop;
647
+ this.table.blackPct[summaryRow] = stateBlackPop / stateVAPPop;
648
+ this.table.hispanicPct[summaryRow] = stateHispanicPop / stateVAPPop;
649
+ this.table.pacificPct[summaryRow] = statePacificPop / stateVAPPop;
650
+ this.table.asianPct[summaryRow] = stateAsianPop / stateVAPPop;
651
+ this.table.nativePct[summaryRow] = stateNativePop / stateVAPPop;
665
652
  }
666
653
  if (bLog) {
667
654
  console.log(`${nMissingDataset} features with missing datasets.`);
@@ -681,43 +668,75 @@ class Districts {
681
668
  let countyIndex = this._session.counties.indexFromFIPS(countyFIPS);
682
669
  return countyIndex;
683
670
  }
671
+ // 11-03-2020 - Added for racially polarized voting analysis
672
+ extractVotesByDemographic(districtID, bLog = false) {
673
+ let whitePts = [];
674
+ let minorityPts = [];
675
+ let blackPts = [];
676
+ let hispanicPts = [];
677
+ let pacificPts = [];
678
+ let asianPts = [];
679
+ let nativePts = [];
680
+ // Gather [demographic %, D %] points for the selected district ID
681
+ let i = districtID;
682
+ // HACK - Because "this" gets ghosted inside the forEach loop below
683
+ let outerThis = this;
684
+ // Get the geoIDs assigned to the district
685
+ // Guard against empty districts
686
+ let geoIDs = this._session.plan.geoIDsForDistrictID(i);
687
+ if (geoIDs && (geoIDs.size > 0)) {
688
+ // ... loop over the geoIDs collecting the points for ecological regression
689
+ geoIDs.forEach(function (geoID) {
690
+ // Skip water-only features
691
+ if (!(U.isWaterOnly(geoID))) {
692
+ // Map from geoID to feature index
693
+ let featureID = outerThis._session.features.featureID(geoID);
694
+ let f = outerThis._session.features.featureByIndex(featureID);
695
+ if (!(f == undefined)) {
696
+ // Calculate the Dem two-party vote
697
+ const featureDem = outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "D" /* DemVotes */);
698
+ const featureRep = outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "R" /* RepVotes */);
699
+ const featureTot = outerThis._session.features.fieldForFeature(f, "ELECTION" /* ELECTION */, "Tot" /* TotalVotes */);
700
+ const pctDem = featureDem / (featureDem + featureRep);
701
+ // Calculate the VAP/CVAP percentages by demographic
702
+ const totalVAP = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Tot" /* TotalPop */);
703
+ if (totalVAP > 0) {
704
+ const whitePop = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "Wh" /* WhitePop */);
705
+ const pctWhite = whitePop / totalVAP;
706
+ const minorityPop = totalVAP - whitePop;
707
+ const pctMinority = minorityPop / totalVAP;
708
+ const pctBlack = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "BlC" /* BlackPop */) / totalVAP;
709
+ const pctHispanic = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "His" /* HispanicPop */) / totalVAP;
710
+ const pctPacific = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "PacC" /* PacificPop */) / totalVAP;
711
+ const pctAsian = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "AsnC" /* AsianPop */) / totalVAP;
712
+ const pctNative = outerThis._session.features.fieldForFeature(f, "VAP" /* VAP */, "NatC" /* NativePop */) / totalVAP;
713
+ whitePts.push({ x: pctWhite, y: pctDem });
714
+ minorityPts.push({ x: pctMinority, y: pctDem });
715
+ blackPts.push({ x: pctBlack, y: pctDem });
716
+ hispanicPts.push({ x: pctHispanic, y: pctDem });
717
+ pacificPts.push({ x: pctPacific, y: pctDem });
718
+ asianPts.push({ x: pctAsian, y: pctDem });
719
+ nativePts.push({ x: pctNative, y: pctDem });
720
+ }
721
+ }
722
+ }
723
+ });
724
+ return {
725
+ white: whitePts,
726
+ minority: minorityPts,
727
+ black: blackPts,
728
+ hispanic: hispanicPts,
729
+ pacific: pacificPts,
730
+ asian: asianPts,
731
+ native: nativePts
732
+ };
733
+ }
734
+ // If a district is empty
735
+ return undefined;
736
+ }
684
737
  }
685
738
  exports.Districts = Districts;
686
739
  // CLASSES, ETC. FOR FEATURE & COUNTY DATA
687
- // Types of datasets by feature
688
- /* 08-13-2020 - Moved to types.ts
689
- export const enum Dataset
690
- {
691
- SHAPES = "SHAPES",
692
- CENSUS = "CENSUS",
693
- VAP = "VAP",
694
- ELECTION = "ELECTION"
695
- }
696
- */
697
- /* 08-13-2020 - Moved to types.ts
698
- export type DatasetKeys = {
699
- SHAPES: string; // A shapefile
700
- CENSUS: string; // A total population Census dataset
701
- VAP: string; // A voting age (or citizen voting age) dataset
702
- ELECTION: string; // An election dataset
703
- }
704
- */
705
- // Identifiers of fields for each feature in the datasets
706
- /* 08-13-2020 - Moved to types.ts
707
- export const enum FeatureField
708
- {
709
- TotalPop = "Tot",
710
- WhitePop = "Wh",
711
- BlackPop = "BlC",
712
- HispanicPop = "His",
713
- AsianPop = "AsnC",
714
- PacificPop = "PacC",
715
- NativePop = "NatC",
716
- DemVotes = "D",
717
- RepVotes = "R",
718
- TotalVotes = "Tot"
719
- }
720
- */
721
740
  // Wrap data by feature, to abstract the specifics of the internal structure
722
741
  class Features {
723
742
  constructor(s, data, keys) {
@@ -1033,7 +1052,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
1033
1052
  };
1034
1053
  Object.defineProperty(exports, "__esModule", { value: true });
1035
1054
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1036
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1037
1055
  // NOTE - The active code is below the long multi-line section to be deleted.
1038
1056
  // ANALYZE SIMPLE COUNTY & VTD SPLITTING
1039
1057
  /*
@@ -1076,7 +1094,9 @@ function doFindCountiesSplitUnexpectedly(s, bLog = false) {
1076
1094
  let countiesSplitUnexpectedly = [];
1077
1095
  // FIRST, ANALYZE THE COUNTY SPLITTING FOR THE PLAN
1078
1096
  // Get the county-district pivot ("splits")
1079
- let countiesByDistrict = s.districts.statistics[D.DistrictField.CountySplits];
1097
+ let countiesByDistrict = s.districts.table.countySplits;
1098
+ // TODO - DELETE???
1099
+ // let countiesByDistrict = s.districts.statistics[D.DistrictField.CountySplits];
1080
1100
  // countiesByDistrict = countiesByDistrict.slice(1, -1);
1081
1101
  // Find the single-county districts, i.e., districts NOT split across counties.
1082
1102
  // Ignore the dummy unassigned 0 and N+1 summary "districts."
@@ -1307,15 +1327,7 @@ exports.scoreKIWYSICompactness = scoreKIWYSICompactness;
1307
1327
  //
1308
1328
  // EQUAL POPULATION
1309
1329
  //
1310
- var __importStar = (this && this.__importStar) || function (mod) {
1311
- if (mod && mod.__esModule) return mod;
1312
- var result = {};
1313
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
1314
- result["default"] = mod;
1315
- return result;
1316
- };
1317
1330
  Object.defineProperty(exports, "__esModule", { value: true });
1318
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1319
1331
  // NOTE - This validity check is *derived* and depends on population deviation %
1320
1332
  // being computed (above) and normalized in test log & scorecard generation.
1321
1333
  function doHasEqualPopulations(s, bLog = false) {
@@ -1338,7 +1350,7 @@ function doHasEqualPopulations(s, bLog = false) {
1338
1350
  test['details']['deviation'] = popDevPct;
1339
1351
  test['details']['thresholds'] = popDevTest['details']['scale'];
1340
1352
  // Populate the N+1 summary "district" in district.statistics
1341
- let bEqualPop = s.districts.statistics[D.DistrictField.bEqualPop];
1353
+ let bEqualPop = s.districts.table.bEqualPop;
1342
1354
  let summaryRow = s.districts.numberOfRows() - 1;
1343
1355
  bEqualPop[summaryRow] = test['score'];
1344
1356
  return test;
@@ -1365,7 +1377,6 @@ function __export(m) {
1365
1377
  }
1366
1378
  Object.defineProperty(exports, "__esModule", { value: true });
1367
1379
  __export(__webpack_require__(/*! ./_api */ "./src/_api.ts"));
1368
- __export(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1369
1380
  __export(__webpack_require__(/*! ./results */ "./src/results.ts"));
1370
1381
  __export(__webpack_require__(/*! ./types */ "./src/types.ts"));
1371
1382
 
@@ -1384,10 +1395,19 @@ __export(__webpack_require__(/*! ./types */ "./src/types.ts"));
1384
1395
  //
1385
1396
  // PROTECTS MINORITIES
1386
1397
  //
1398
+ var __importStar = (this && this.__importStar) || function (mod) {
1399
+ if (mod && mod.__esModule) return mod;
1400
+ var result = {};
1401
+ if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
1402
+ result["default"] = mod;
1403
+ return result;
1404
+ };
1387
1405
  var __importDefault = (this && this.__importDefault) || function (mod) {
1388
1406
  return (mod && mod.__esModule) ? mod : { "default": mod };
1389
1407
  };
1390
1408
  Object.defineProperty(exports, "__esModule", { value: true });
1409
+ const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1410
+ const score_1 = __webpack_require__(/*! ./score */ "./src/score.ts");
1391
1411
  const majority_minority_json_1 = __importDefault(__webpack_require__(/*! ../static/majority-minority.json */ "./static/majority-minority.json"));
1392
1412
  const vra5_preclearance_json_1 = __importDefault(__webpack_require__(/*! ../static/vra5-preclearance.json */ "./static/vra5-preclearance.json"));
1393
1413
  // TODO - 2020: Update/revise this, when the update comes out in September:
@@ -1409,6 +1429,75 @@ function getVRASection5(s) {
1409
1429
  return stateVRAPre;
1410
1430
  }
1411
1431
  exports.getVRASection5 = getVRASection5;
1432
+ // 11-03-2020 - Added for racially polarized voting analysis
1433
+ // Analyze the degree of racially polarized voting for a district
1434
+ function doAnalyzeRacialPolarization(s, districtID, bLog = false) {
1435
+ const points = s.districts.extractVotesByDemographic(districtID, bLog);
1436
+ if (points === undefined)
1437
+ return undefined;
1438
+ const nDistricts = s.state.nDistricts;
1439
+ const statewide = score_1.getStatewideDemographics(s, bLog);
1440
+ // Which minority groups are big enough that proportional representation would give them one or more seats?
1441
+ const bDoBlack = (calcProportionalDistricts(statewide.black, nDistricts) > 0) ? true : false;
1442
+ const bDoHispanic = (calcProportionalDistricts(statewide.hispanic, nDistricts) > 0) ? true : false;
1443
+ const bDoPacific = (calcProportionalDistricts(statewide.pacific, nDistricts) > 0) ? true : false;
1444
+ const bDoAsian = (calcProportionalDistricts(statewide.asian, nDistricts) > 0) ? true : false;
1445
+ const bDoNative = (calcProportionalDistricts(statewide.native, nDistricts) > 0) ? true : false;
1446
+ if (!(bDoBlack || bDoHispanic || bDoPacific || bDoAsian || bDoNative))
1447
+ return undefined;
1448
+ // At this point, at least one single-race/ethnicity minority demographic needs
1449
+ // to be analyzed, so white & all minorities combined will need to be as well.
1450
+ //
1451
+ // const bDoWhite: boolean = (calcProportionalDistricts(statewide.white, nDistricts) > 0) ? true : false;
1452
+ // const bDoMinority: boolean = (calcProportionalDistricts(statewide.minority, nDistricts) > 0) ? true : false;
1453
+ const result = {
1454
+ white: calcMaxVoteShare(points.white),
1455
+ minority: calcMaxVoteShare(points.minority),
1456
+ black: bDoBlack ? calcMaxVoteShare(points.black) : undefined,
1457
+ hispanic: bDoHispanic ? calcMaxVoteShare(points.hispanic) : undefined,
1458
+ pacific: bDoPacific ? calcMaxVoteShare(points.pacific) : undefined,
1459
+ asian: bDoAsian ? calcMaxVoteShare(points.asian) : undefined,
1460
+ native: bDoNative ? calcMaxVoteShare(points.native) : undefined
1461
+ };
1462
+ return result;
1463
+ }
1464
+ exports.doAnalyzeRacialPolarization = doAnalyzeRacialPolarization;
1465
+ // Cloned from dra-score
1466
+ function calcProportionalDistricts(proportion, nDistricts) {
1467
+ const roundUp = 0.0;
1468
+ const fractional = proportion * nDistricts;
1469
+ const integral = Math.round(fractional + roundUp);
1470
+ return integral;
1471
+ }
1472
+ // https://trentrichardson.com/compute-linear-regressions-in-javascript.html
1473
+ function linearRegression(points) {
1474
+ let result = {};
1475
+ const n = points.length;
1476
+ let sum_x = 0;
1477
+ let sum_y = 0;
1478
+ let sum_xy = 0;
1479
+ let sum_xx = 0;
1480
+ let sum_yy = 0;
1481
+ for (let i = 0; i < n; i++) {
1482
+ const pt = points[i];
1483
+ sum_x += pt.x;
1484
+ sum_y += pt.y;
1485
+ sum_xy += (pt.x * pt.y);
1486
+ sum_xx += (pt.x * pt.x);
1487
+ sum_yy += (pt.y * pt.y);
1488
+ }
1489
+ // y = mx + b
1490
+ result['m'] = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
1491
+ result['b'] = (sum_y - result.m * sum_x) / n;
1492
+ result['r2'] = Math.pow((n * sum_xy - sum_x * sum_y) / Math.sqrt((n * sum_xx - sum_x * sum_x) * (n * sum_yy - sum_y * sum_y)), 2);
1493
+ return result;
1494
+ }
1495
+ function calcMaxVoteShare(points) {
1496
+ const { m, b, r2 } = linearRegression(points);
1497
+ const maxVAPPct = 1.0; // 100%
1498
+ const maxDemPct = Math.max(0.0, Math.min(1.0, (m * maxVAPPct) + b));
1499
+ return U.trim(maxDemPct);
1500
+ }
1412
1501
 
1413
1502
 
1414
1503
  /***/ }),
@@ -1631,7 +1720,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
1631
1720
  Object.defineProperty(exports, "__esModule", { value: true });
1632
1721
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1633
1722
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
1634
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1635
1723
  const analyze_1 = __webpack_require__(/*! ./analyze */ "./src/analyze.ts");
1636
1724
  function prepareRequirementsChecklist(s, bLog = false) {
1637
1725
  if (!(s.bPostProcessingDone)) {
@@ -1706,37 +1794,29 @@ function prepareDistrictStatistics(s, bLog = false) {
1706
1794
  if (!(s.bPostProcessingDone)) {
1707
1795
  doAnalyzePostProcessing(s);
1708
1796
  }
1797
+ // Transpose the rows & columns, so rows are districts.
1709
1798
  let dsTable = [];
1710
1799
  for (let i = 0; i < s.districts.numberOfRows(); i++) {
1711
- let rawRow = [
1712
- i,
1713
- s.districts.statistics[D.DistrictField.TotalPop][i],
1714
- s.districts.statistics[D.DistrictField.PopDevPct][i],
1715
- s.districts.statistics[D.DistrictField.bEqualPop][i],
1716
- s.districts.statistics[D.DistrictField.bNotEmpty][i],
1717
- s.districts.statistics[D.DistrictField.bContiguous][i],
1718
- s.districts.statistics[D.DistrictField.bNotEmbedded][i],
1719
- s.districts.statistics[D.DistrictField.DemPct][i],
1720
- s.districts.statistics[D.DistrictField.RepPct][i],
1721
- s.districts.statistics[D.DistrictField.WhitePct][i],
1722
- s.districts.statistics[D.DistrictField.MinorityPct][i],
1723
- s.districts.statistics[D.DistrictField.BlackPct][i],
1724
- s.districts.statistics[D.DistrictField.HispanicPct][i],
1725
- s.districts.statistics[D.DistrictField.PacificPct][i],
1726
- s.districts.statistics[D.DistrictField.AsianPct][i],
1727
- s.districts.statistics[D.DistrictField.NativePct][i],
1728
- s.districts.statistics[D.DistrictField.TotalVAP][i],
1729
- // 10-23-2020 - Added Other vote percentage
1730
- s.districts.statistics[D.DistrictField.OtherPct][i],
1731
- ];
1732
- /* TriState
1733
- // NOTE - Until we add three-state support top to bottom in Requirements,
1734
- // map booleans to tri-states here.
1735
- rawRow[DistrictColumn.bEqualPop] = U.mapBooleanToTriState(rawRow[DistrictColumn.bEqualPop]);
1736
- rawRow[DistrictColumn.bNotEmpty] = U.mapBooleanToTriState(rawRow[DistrictColumn.bNotEmpty]);
1737
- rawRow[DistrictColumn.bContiguous] = U.mapBooleanToTriState(rawRow[DistrictColumn.bContiguous]);
1738
- rawRow[DistrictColumn.bNotEmbedded] = U.mapBooleanToTriState(rawRow[DistrictColumn.bNotEmbedded]);
1739
- */
1800
+ let rawRow = {
1801
+ districtID: i,
1802
+ totalPop: s.districts.table.totalPop[i],
1803
+ popDevPct: s.districts.table.popDevPct[i],
1804
+ bEqualPop: s.districts.table.bEqualPop[i],
1805
+ bNotEmpty: s.districts.table.bNotEmpty[i],
1806
+ bContiguous: s.districts.table.bContiguous[i],
1807
+ bNotEmbedded: s.districts.table.bNotEmbedded[i],
1808
+ demPct: s.districts.table.demPct[i],
1809
+ repPct: s.districts.table.repPct[i],
1810
+ othPct: s.districts.table.otherPct[i],
1811
+ whitePct: s.districts.table.whitePct[i],
1812
+ totalVAP: s.districts.table.totalVAP[i],
1813
+ minorityPct: s.districts.table.minorityPct[i],
1814
+ blackPct: s.districts.table.blackPct[i],
1815
+ hispanicPct: s.districts.table.hispanicPct[i],
1816
+ pacificPct: s.districts.table.pacificPct[i],
1817
+ asianPct: s.districts.table.asianPct[i],
1818
+ nativePct: s.districts.table.nativePct[i]
1819
+ };
1740
1820
  let readyRow = U.deepCopy(rawRow);
1741
1821
  dsTable.push(readyRow);
1742
1822
  }
@@ -1815,7 +1895,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
1815
1895
  Object.defineProperty(exports, "__esModule", { value: true });
1816
1896
  const Score = __importStar(__webpack_require__(/*! @dra2020/dra-score */ "@dra2020/dra-score"));
1817
1897
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
1818
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
1819
1898
  const M = __importStar(__webpack_require__(/*! ./minority */ "./src/minority.ts"));
1820
1899
  const C = __importStar(__webpack_require__(/*! ./compact */ "./src/compact.ts"));
1821
1900
  // PROFILE A PLAN
@@ -1826,18 +1905,17 @@ function profilePlan(s, bLog = false) {
1826
1905
  const nDistricts = s.state.nDistricts;
1827
1906
  const nCounties = s.counties.nCounties;
1828
1907
  const targetSize = Math.round(s.state.totalPop / nDistricts);
1829
- const popByDistrict = U.deepCopy(s.districts.statistics[D.DistrictField.TotalPop].slice(1, -1));
1908
+ const popByDistrict = U.deepCopy(s.districts.table.totalPop.slice(1, -1));
1830
1909
  const geoPropsByDistrict = makeArrayOfGeoProps(s, bLog);
1831
1910
  const splits = makeNakedCxD(s);
1832
1911
  const summaryRow = s.districts.numberOfRows() - 1;
1833
1912
  // 10-22-2020 - Converted Dem + Rep + Other = Total to two-party vote shares for analytics.
1834
- const demVote = s.districts.statistics[D.DistrictField.DemVotes][summaryRow];
1835
- const repVote = s.districts.statistics[D.DistrictField.RepVotes][summaryRow];
1913
+ const demVote = s.districts.table.demVotes[summaryRow];
1914
+ const repVote = s.districts.table.repVotes[summaryRow];
1836
1915
  const statewideVf = U.trim(demVote / (demVote + repVote), KEEP_DECIMALS);
1837
- // const statewideVf: number = U.trim(s.districts.statistics[D.DistrictField.DemPct][summaryRow], 6);
1838
1916
  let vpiArray = [];
1839
- const demVotes = U.deepCopy(s.districts.statistics[D.DistrictField.DemVotes].slice(1, -1));
1840
- const repVotes = U.deepCopy(s.districts.statistics[D.DistrictField.RepVotes].slice(1, -1));
1917
+ const demVotes = U.deepCopy(s.districts.table.demVotes.slice(1, -1));
1918
+ const repVotes = U.deepCopy(s.districts.table.repVotes.slice(1, -1));
1841
1919
  for (let districtID = 1; districtID <= nDistricts; districtID++) {
1842
1920
  const D = demVotes[districtID - 1];
1843
1921
  const R = repVotes[districtID - 1];
@@ -1845,7 +1923,6 @@ function profilePlan(s, bLog = false) {
1845
1923
  const v = (T > 0) ? U.trim(D / T, KEEP_DECIMALS) : 0;
1846
1924
  vpiArray.push(v);
1847
1925
  }
1848
- // const vpiArray: number[] = U.deepCopy(s.districts.statistics[D.DistrictField.DemPct].slice(1, -1)) as number[];
1849
1926
  const statewideDemographics = getStatewideDemographics(s);
1850
1927
  const demographicsByDistrict = getDemographicsByDistrict(s);
1851
1928
  const profile = {
@@ -1876,7 +1953,7 @@ exports.profilePlan = profilePlan;
1876
1953
  // unassigned precincts & state summary and an extra 0 county. But dra-score takes
1877
1954
  // a simple 1–D x 1–C splits array (zero-based, of course).
1878
1955
  function makeNakedCxD(s, bLog = false) {
1879
- const adornedCxD = s.districts.statistics[D.DistrictField.CountySplits];
1956
+ const adornedCxD = s.districts.table.countySplits;
1880
1957
  let CxD = [];
1881
1958
  // Remove the unassigned & total dummy "districts"
1882
1959
  for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
@@ -1905,13 +1982,13 @@ function getDemographicsByDistrict(s, bLog = false) {
1905
1982
  // Remove the unassigned & total dummy "districts"
1906
1983
  for (let districtID = 1; districtID <= s.state.nDistricts; districtID++) {
1907
1984
  const districtDemographics = {
1908
- white: U.trim(s.districts.statistics[D.DistrictField.WhitePct][districtID], KEEP_DECIMALS),
1909
- minority: U.trim(s.districts.statistics[D.DistrictField.MinorityPct][districtID], KEEP_DECIMALS),
1910
- black: U.trim(s.districts.statistics[D.DistrictField.BlackPct][districtID], KEEP_DECIMALS),
1911
- hispanic: U.trim(s.districts.statistics[D.DistrictField.HispanicPct][districtID], KEEP_DECIMALS),
1912
- pacific: U.trim(s.districts.statistics[D.DistrictField.PacificPct][districtID], KEEP_DECIMALS),
1913
- asian: U.trim(s.districts.statistics[D.DistrictField.AsianPct][districtID], KEEP_DECIMALS),
1914
- native: U.trim(s.districts.statistics[D.DistrictField.NativePct][districtID], KEEP_DECIMALS)
1985
+ white: U.trim(s.districts.table.whitePct[districtID], KEEP_DECIMALS),
1986
+ minority: U.trim(s.districts.table.minorityPct[districtID], KEEP_DECIMALS),
1987
+ black: U.trim(s.districts.table.blackPct[districtID], KEEP_DECIMALS),
1988
+ hispanic: U.trim(s.districts.table.hispanicPct[districtID], KEEP_DECIMALS),
1989
+ pacific: U.trim(s.districts.table.pacificPct[districtID], KEEP_DECIMALS),
1990
+ asian: U.trim(s.districts.table.asianPct[districtID], KEEP_DECIMALS),
1991
+ native: U.trim(s.districts.table.nativePct[districtID], KEEP_DECIMALS)
1915
1992
  };
1916
1993
  demographicsArray.push(districtDemographics);
1917
1994
  }
@@ -1920,16 +1997,17 @@ function getDemographicsByDistrict(s, bLog = false) {
1920
1997
  function getStatewideDemographics(s, bLog = false) {
1921
1998
  const summaryRow = s.districts.numberOfRows() - 1;
1922
1999
  const demographics = {
1923
- white: U.trim(s.districts.statistics[D.DistrictField.WhitePct][summaryRow], KEEP_DECIMALS),
1924
- minority: U.trim(s.districts.statistics[D.DistrictField.MinorityPct][summaryRow], KEEP_DECIMALS),
1925
- black: U.trim(s.districts.statistics[D.DistrictField.BlackPct][summaryRow], KEEP_DECIMALS),
1926
- hispanic: U.trim(s.districts.statistics[D.DistrictField.HispanicPct][summaryRow], KEEP_DECIMALS),
1927
- pacific: U.trim(s.districts.statistics[D.DistrictField.PacificPct][summaryRow], KEEP_DECIMALS),
1928
- asian: U.trim(s.districts.statistics[D.DistrictField.AsianPct][summaryRow], KEEP_DECIMALS),
1929
- native: U.trim(s.districts.statistics[D.DistrictField.NativePct][summaryRow], KEEP_DECIMALS)
2000
+ white: U.trim(s.districts.table.whitePct[summaryRow], KEEP_DECIMALS),
2001
+ minority: U.trim(s.districts.table.minorityPct[summaryRow], KEEP_DECIMALS),
2002
+ black: U.trim(s.districts.table.blackPct[summaryRow], KEEP_DECIMALS),
2003
+ hispanic: U.trim(s.districts.table.hispanicPct[summaryRow], KEEP_DECIMALS),
2004
+ pacific: U.trim(s.districts.table.pacificPct[summaryRow], KEEP_DECIMALS),
2005
+ asian: U.trim(s.districts.table.asianPct[summaryRow], KEEP_DECIMALS),
2006
+ native: U.trim(s.districts.table.nativePct[summaryRow], KEEP_DECIMALS)
1930
2007
  };
1931
2008
  return demographics;
1932
2009
  }
2010
+ exports.getStatewideDemographics = getStatewideDemographics;
1933
2011
  // SCORE A PLAN
1934
2012
  function scorePlan(s, p, bLog = false, overridesJSON) {
1935
2013
  let scorer = new Score.Scorer();
@@ -1943,9 +2021,9 @@ function scorePlan(s, p, bLog = false, overridesJSON) {
1943
2021
  test['score'] = popDev;
1944
2022
  test['details'] = { 'maxDeviation': scorecard.populationDeviation.notes['maxDeviation'] };
1945
2023
  // Populate the N+1 summary "district" in district.statistics
1946
- let totalPop = s.districts.statistics[D.DistrictField.TotalPop];
1947
- let popDevPct = s.districts.statistics[D.DistrictField.PopDevPct];
1948
- let totalVAP = s.districts.statistics[D.DistrictField.TotalVAP];
2024
+ let totalPop = s.districts.table.totalPop;
2025
+ let popDevPct = s.districts.table.popDevPct;
2026
+ let totalVAP = s.districts.table.totalVAP;
1949
2027
  const summaryRow = s.districts.numberOfRows() - 1;
1950
2028
  totalPop[summaryRow] = p.population.targetSize;
1951
2029
  popDevPct[summaryRow] = popDev;
@@ -2299,7 +2377,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
2299
2377
  Object.defineProperty(exports, "__esModule", { value: true });
2300
2378
  const U = __importStar(__webpack_require__(/*! ./utils */ "./src/utils.ts"));
2301
2379
  const S = __importStar(__webpack_require__(/*! ./settings */ "./src/settings.ts"));
2302
- const D = __importStar(__webpack_require__(/*! ./_data */ "./src/_data.ts"));
2303
2380
  //
2304
2381
  // COMPLETE - Are all geo's assigned to a district, and do all districts have
2305
2382
  // at least one geo assigned to them?
@@ -2308,7 +2385,7 @@ function doIsComplete(s, bLog = false) {
2308
2385
  let test = s.getTest(0 /* Complete */);
2309
2386
  // Get the by-district results, including the dummy unassigned district,
2310
2387
  // but ignoring the N+1 summary district
2311
- let bNotEmptyByDistrict = s.districts.statistics[D.DistrictField.bNotEmpty];
2388
+ let bNotEmptyByDistrict = s.districts.table.bNotEmpty;
2312
2389
  bNotEmptyByDistrict = bNotEmptyByDistrict.slice(0, -1);
2313
2390
  // Are all features assigned to districts?
2314
2391
  // Check the dummy district that holds any unassigned features.
@@ -2374,7 +2451,7 @@ function doIsComplete(s, bLog = false) {
2374
2451
  test['details']['emptyDistricts'] = emptyDistricts;
2375
2452
  }
2376
2453
  // Populate the N+1 summary "district" in district.statistics
2377
- let bNotEmpty = s.districts.statistics[D.DistrictField.bNotEmpty];
2454
+ let bNotEmpty = s.districts.table.bNotEmpty;
2378
2455
  let summaryRow = s.districts.numberOfRows() - 1;
2379
2456
  bNotEmpty[summaryRow] = test['score'];
2380
2457
  return test;
@@ -2392,7 +2469,7 @@ function doIsContiguous(s, bLog = false) {
2392
2469
  let test = s.getTest(1 /* Contiguous */);
2393
2470
  // Get the contiguity of each district. Ignore dummy unassigned district
2394
2471
  // and the N+1 summary district.
2395
- let bContiguousByDistrict = s.districts.statistics[D.DistrictField.bContiguous];
2472
+ let bContiguousByDistrict = s.districts.table.bContiguous;
2396
2473
  bContiguousByDistrict = bContiguousByDistrict.slice(1, -1);
2397
2474
  // If any real districts aren't contiguous, mark the plan as not contiguous
2398
2475
  let bMapContiguous = U.andArray(bContiguousByDistrict);
@@ -2410,7 +2487,7 @@ function doIsContiguous(s, bLog = false) {
2410
2487
  test['details'] = { 'discontiguousDistricts': discontiguousDistricts };
2411
2488
  }
2412
2489
  // Populate the N+1 summary "district" in district.statistics
2413
- let bContiguous = s.districts.statistics[D.DistrictField.bContiguous];
2490
+ let bContiguous = s.districts.table.bContiguous;
2414
2491
  let summaryRow = s.districts.numberOfRows() - 1;
2415
2492
  bContiguous[summaryRow] = test['score'];
2416
2493
  return test;
@@ -2433,7 +2510,7 @@ function doIsFreeOfHoles(s, bLog = false) {
2433
2510
  let embeddedDistricts = [];
2434
2511
  // Get the embeddedness of each district. Ignore dummy unassigned district
2435
2512
  // and the N+1 summary district.
2436
- let bNotEmbeddedByDistrict = s.districts.statistics[D.DistrictField.bNotEmbedded];
2513
+ let bNotEmbeddedByDistrict = s.districts.table.bNotEmbedded;
2437
2514
  bNotEmbeddedByDistrict = bNotEmbeddedByDistrict.slice(1, -1);
2438
2515
  let districtID = 1;
2439
2516
  bNotEmbeddedByDistrict.forEach(function (bDistrictNotEmbedded) {
@@ -2449,7 +2526,7 @@ function doIsFreeOfHoles(s, bLog = false) {
2449
2526
  test['details'] = { 'embeddedDistricts': embeddedDistricts };
2450
2527
  }
2451
2528
  // Populate the N+1 summary "district" in district.statistics
2452
- let bNotEmbedded = s.districts.statistics[D.DistrictField.bNotEmbedded];
2529
+ let bNotEmbedded = s.districts.table.bNotEmbedded;
2453
2530
  let summaryRow = s.districts.numberOfRows() - 1;
2454
2531
  bNotEmbedded[summaryRow] = test['score'];
2455
2532
  return test;