Преглед на файлове

✨ add new chart (per year per region)

jmacura преди 3 години
родител
ревизия
28abf78e72

+ 1 - 0
src/app/app.component.html

@@ -35,6 +35,7 @@
       <a ngbNavLink>1st ("Discs")</a>
       <ng-template ngbNavContent>
         <year-graph></year-graph>
+        <factor-year-graph [region]="'Apulia'"></factor-year-graph>
       </ng-template>
     </ng-container>
     <ng-container ngbNavItem>

+ 3 - 2
src/app/discs-chart/discs.module.ts

@@ -2,13 +2,14 @@ import {CommonModule} from '@angular/common';
 import {FormsModule} from '@angular/forms';
 import {NgModule} from '@angular/core';
 
+import {FactorYearGraphComponent} from './factor-year-graph/factor-year-graph.component';
 import {QuarterPipe} from './quarter.pipe';
 import {YearGraphComponent} from './year-graph/year-graph.component';
 
 @NgModule({
   imports: [CommonModule, FormsModule],
-  exports: [YearGraphComponent],
-  declarations: [YearGraphComponent, QuarterPipe],
+  exports: [FactorYearGraphComponent, YearGraphComponent],
+  declarations: [FactorYearGraphComponent, YearGraphComponent, QuarterPipe],
   providers: [],
 })
 export class DiscsModule {}

+ 14 - 0
src/app/discs-chart/factor-year-graph/factor-year-graph.component.html

@@ -0,0 +1,14 @@
+<h2>Rural attractiveness index of region {{region}} in {{yearGraphService.selectedYear}}</h2>
+<div *ngIf="sdmDihService.yearsLoaded; else loading">
+  <!--select
+    class="form-select form-select-lg mb-3"
+    aria-label=".form-select-lg example"
+    [(ngModel)]="selectedYear"
+    (ngModelChange)="redrawGraphs()"
+  >
+    <option [ngValue]="year" *ngFor="let year of years">{{ year }}</option>
+  </select-->
+</div>
+<ng-template #loading> Loading data... </ng-template>
+<div id="year-graph-place-{{region}}"></div>
+<div id="year-graph-legend-place-{{region}}"></div>

+ 0 - 0
src/app/discs-chart/factor-year-graph/factor-year-graph.component.scss


+ 161 - 0
src/app/discs-chart/factor-year-graph/factor-year-graph.component.ts

@@ -0,0 +1,161 @@
+import {Component, Input, OnInit} from '@angular/core';
+import {interpolate, quantize, select} from 'd3';
+
+import {SdmDihService} from '../../sdm-dih.service';
+import {YearGraphService} from '../year-graph.service';
+
+@Component({
+  selector: 'factor-year-graph',
+  templateUrl: './factor-year-graph.component.html',
+  styleUrls: ['./factor-year-graph.component.scss'],
+})
+export class FactorYearGraphComponent implements OnInit {
+  @Input() region: string;
+
+  constructor(
+    public sdmDihService: SdmDihService,
+    public yearGraphService: YearGraphService
+  ) {}
+
+  ngOnInit() {
+    this.sdmDihService.dataLoads.subscribe((loaded) => {
+      if (!loaded) {
+        return;
+      }
+      this.redrawGraphs();
+      this.drawLegend();
+    });
+  }
+
+  drawGraph(
+    region: string,
+    {factor}: {factor?: string} = {factor: 'aggregated'}
+  ) {
+    const year = this.yearGraphService.selectedYear.toString().includes('.')
+      ? this.yearGraphService.selectedYear
+      : this.yearGraphService.selectedYear + '.0';
+    const regionData = this.sdmDihService.sdmData[year].find(
+      (row) => row['MODEL'] === region
+    );
+    const width = 100 / this.sdmDihService.regions.length;
+    const height = 100 / this.sdmDihService.regions.length;
+    // append the svg object to the div called 'year-graph-place'
+    const svg = select(`#year-graph-place-${region}`)
+      .append('svg')
+      .attr('width', `${width}%`)
+      .attr('height', `${height}%`)
+      .append('svg')
+      .attr('x', '50%')
+      .attr('y', '50%')
+      .attr('overflow', 'visible');
+    // arrow symbol for later re-use
+    const arrow = svg.append('symbol').attr('id', 'arrow');
+    // horizontal line
+    arrow
+      .append('line')
+      .attr('x1', 0)
+      .attr('y1', 30)
+      .attr('x2', 60)
+      .attr('y2', 30)
+      .attr('stroke', 'black')
+      .style('stroke-width', 4);
+    // upper line
+    arrow
+      .append('line')
+      .attr('x1', 40)
+      .attr('y1', 10)
+      .attr('x2', 60)
+      .attr('y2', 30)
+      .attr('stroke', 'black')
+      .style('stroke-width', 4);
+    // bottom line
+    arrow
+      .append('line')
+      .attr('x1', 40)
+      .attr('y1', 50)
+      .attr('x2', 60)
+      .attr('y2', 30)
+      .attr('stroke', 'black')
+      .style('stroke-width', 4);
+    const svgContent = svg.append('g').data([regionData]);
+    //.attr('transform', 'translate(' + width / 2 + '% ,' + height / 2 + '% )');
+    svgContent
+      .append('circle')
+      .attr('cx', 0)
+      .attr('cy', 0)
+      .attr('r', 50)
+      .attr('stroke', 'black')
+      .attr('fill', (d) => {
+        return this.sdmDihService.perc2color(d[factor]?.index);
+      });
+    svgContent
+      .append('text')
+      .attr('x', 0)
+      .attr('y', -60)
+      .attr('dy', '.35em')
+      .text((d) => factor.split('/').pop())
+      .style('text-anchor', 'middle');
+    svgContent
+      .append('text')
+      .attr('x', 0)
+      .attr('y', 60)
+      .attr('dy', '.35em')
+      .text(
+        (d) => `${(Number.parseFloat(d[factor]?.index) * 100).toFixed(2)} %`
+      )
+      .style('text-anchor', 'middle');
+    svgContent
+      .append('use')
+      .attr('xlink:href', '#arrow')
+      .attr('x', -30)
+      .attr('y', -30)
+      .attr(
+        'transform',
+        // rotation is defined clockwise, hence -45 deg will turn the arrow up
+        (d) =>
+          `rotate(${-45 * this.yearGraphService.getRegionProgress(d)}, 0, 0)`
+      );
+  }
+
+  drawLegend() {
+    const width = 200;
+    const height = 20;
+    const g = select(`#year-graph-legend-place-${this.region}`)
+      .append('svg')
+      .attr('width', width)
+      .attr('height', height)
+      .attr('viewBox', [0, 0, width, height])
+      .style('overflow', 'visible')
+      .style('display', 'block')
+      .append('g');
+    for (let i = 0; i < 10; i++) {
+      g.append('rect')
+        .attr('x', i * 20)
+        .attr('y', 0)
+        .attr('width', width / 10)
+        .attr('height', height / 2)
+        .attr('fill', this.sdmDihService.perc2color(i / 10));
+    }
+    g.append('text')
+      .attr('x', 0 - 10)
+      .attr('y', 20)
+      .attr('dy', '.5em')
+      .text('0 %');
+    g.append('text')
+      .attr('x', 200 - 10)
+      .attr('y', 20)
+      .attr('dy', '.5em')
+      .text('100 %');
+  }
+
+  redrawGraphs() {
+    const width = 200;
+    const height = 200;
+    select(`#year-graph-place-${this.region}`).selectAll('svg')?.remove();
+    //for (const region of this.sdmDihService.regions) {
+    this.drawGraph(this.region, {
+      factor: 'http://www.semanticweb.org/attractiveness/economic',
+    });
+    //}
+  }
+}

+ 45 - 0
src/app/discs-chart/year-graph.service.ts

@@ -0,0 +1,45 @@
+import {Injectable} from '@angular/core';
+
+import {SdmDihService} from '../sdm-dih.service';
+
+@Injectable({providedIn: 'root'})
+export class YearGraphService {
+  selectedYear = '';
+  constructor(public sdmDihService: SdmDihService) {
+    this.sdmDihService.dataLoads.subscribe((loaded) => {
+      if (!loaded) {
+        return;
+      }
+      this.selectedYear = this.sdmDihService.firstYear;
+    });
+  }
+
+  getRegionProgress(regionData): -1 | 0 | 1 {
+    //TODO: parametrize the PARAM_TO_COMPARE
+    //TODO: use i-2, i-1 comparison based on 2 previous steps
+    const PARAM_TO_COMPARE = 'aggregated';
+    const region = regionData['MODEL'];
+    const year = regionData['TIME_STEP'];
+    const thisYearValue = +Number.parseFloat(
+      regionData[PARAM_TO_COMPARE]
+    ).toFixed(3);
+    let pastYear = Number.parseFloat(year) - 0.25 + '';
+    pastYear = pastYear.includes('.') ? pastYear : pastYear + '.0';
+    const pastYearData = this.sdmDihService.sdmData[pastYear];
+    if (!pastYearData) {
+      // We are in the first time step, so there is nothing to compare
+      return 0;
+    }
+    let pastYearValue = pastYearData.find(
+      (regionPastYear) => regionPastYear['MODEL'] === region
+    )?.[PARAM_TO_COMPARE];
+    pastYearValue = +Number.parseFloat(pastYearValue).toFixed(3);
+    if (pastYearValue < thisYearValue) {
+      return 1;
+    }
+    if (pastYearValue > thisYearValue) {
+      return -1;
+    }
+    return 0;
+  }
+}

+ 2 - 2
src/app/discs-chart/year-graph/year-graph.component.html

@@ -1,4 +1,4 @@
-<h2>Rural attractiveness index of regions in {{ selectedYear }}</h2>
+<h2>Rural attractiveness index of regions in {{yearGraphService.selectedYear}}</h2>
 <div *ngIf="sdmDihService.yearsLoaded; else loading">
   <div class="year-range">
     <span><button (click)="prevYear()">&#9665;</button></span>
@@ -8,7 +8,7 @@
     <input
       type="range"
       class="form-range"
-      [(ngModel)]="selectedYear"
+      [(ngModel)]="yearGraphService.selectedYear"
       (ngModelChange)="redrawGraphs()"
       [min]="sdmDihService.firstYear"
       [max]="sdmDihService.lastYear"

+ 23 - 51
src/app/discs-chart/year-graph/year-graph.component.ts

@@ -2,6 +2,7 @@ import {Component, OnInit} from '@angular/core';
 import {interpolate, quantize, select} from 'd3';
 
 import {SdmDihService} from '../../sdm-dih.service';
+import {YearGraphService} from '../year-graph.service';
 
 @Component({
   selector: 'year-graph',
@@ -9,16 +10,16 @@ import {SdmDihService} from '../../sdm-dih.service';
   styleUrls: ['./year-graph.component.scss'],
 })
 export class YearGraphComponent implements OnInit {
-  selectedYear = '';
-
-  constructor(public sdmDihService: SdmDihService) {}
+  constructor(
+    public sdmDihService: SdmDihService,
+    public yearGraphService: YearGraphService
+  ) {}
 
   ngOnInit() {
     this.sdmDihService.dataLoads.subscribe((loaded) => {
       if (!loaded) {
         return;
       }
-      this.selectedYear = this.sdmDihService.firstYear;
       this.redrawGraphs();
       this.drawLegend();
     });
@@ -29,7 +30,7 @@ export class YearGraphComponent implements OnInit {
     let i = 1;
     for (const year of this.sdmDihService.years) {
       setTimeout(() => {
-        this.selectedYear = year;
+        this.yearGraphService.selectedYear = year;
         this.redrawGraphs();
       }, MILLISECONDS_TO_ANIMATE * i);
       i++;
@@ -37,9 +38,9 @@ export class YearGraphComponent implements OnInit {
   }
 
   drawGraph(region: string) {
-    const year = this.selectedYear.toString().includes('.')
-      ? this.selectedYear
-      : this.selectedYear + '.0';
+    const year = this.yearGraphService.selectedYear.toString().includes('.')
+      ? this.yearGraphService.selectedYear
+      : this.yearGraphService.selectedYear + '.0';
     const regionData = this.sdmDihService.sdmData[year].find(
       (row) => row['MODEL'] === region
     );
@@ -109,13 +110,6 @@ export class YearGraphComponent implements OnInit {
       .text((d) => `${(Number.parseFloat(d['aggregated']) * 100).toFixed(2)} %`)
       .style('text-anchor', 'middle');
     svgContent
-      .append('text')
-      .attr('x', 0)
-      .attr('y', 60)
-      .attr('dy', '.35em')
-      .text((d) => `${(Number.parseFloat(d['aggregated']) * 100).toFixed(2)} %`)
-      .style('text-anchor', 'middle');
-    svgContent
       .append('use')
       .attr('xlink:href', '#arrow')
       .attr('x', -30)
@@ -123,7 +117,8 @@ export class YearGraphComponent implements OnInit {
       .attr(
         'transform',
         // rotation is defined clockwise, hence -45 deg will turn the arrow up
-        (d) => `rotate(${-45 * this.getRegionProgress(d)}, 0, 0)`
+        (d) =>
+          `rotate(${-45 * this.yearGraphService.getRegionProgress(d)}, 0, 0)`
       );
   }
 
@@ -160,23 +155,29 @@ export class YearGraphComponent implements OnInit {
 
   nextYear() {
     const selectedYearIndex = this.sdmDihService.years.findIndex(
-      (val) => val == this.selectedYear
+      (val) => val == this.yearGraphService.selectedYear
     );
-    this.selectedYear =
-      this.sdmDihService.years[selectedYearIndex + 1] ?? this.selectedYear;
+    this.yearGraphService.selectedYear =
+      this.sdmDihService.years[selectedYearIndex + 1] ??
+      this.yearGraphService.selectedYear;
     this.redrawGraphs();
   }
 
   prevYear() {
     const selectedYearIndex = this.sdmDihService.years.findIndex(
-      (val) => val == this.selectedYear
+      (val) => val == this.yearGraphService.selectedYear
     );
-    this.selectedYear =
-      this.sdmDihService.years[selectedYearIndex - 1] ?? this.selectedYear;
+    this.yearGraphService.selectedYear =
+      this.sdmDihService.years[selectedYearIndex - 1] ??
+      this.yearGraphService.selectedYear;
     this.redrawGraphs();
   }
 
   redrawGraphs() {
+    // Data not loaded yet
+    if (!this.sdmDihService.sdmData) {
+      return;
+    }
     const width = 200;
     const height = 200;
     select('#year-graph-place').selectAll('svg')?.remove();
@@ -184,33 +185,4 @@ export class YearGraphComponent implements OnInit {
       this.drawGraph(region);
     }
   }
-
-  getRegionProgress(regionData): -1 | 0 | 1 {
-    //TODO: parametrize the PARAM_TO_COMPARE
-    //TODO: use i-2, i-1 comparison based on 2 previous steps
-    const PARAM_TO_COMPARE = 'aggregated';
-    const region = regionData['MODEL'];
-    const year = regionData['TIME_STEP'];
-    const thisYearValue = +Number.parseFloat(
-      regionData[PARAM_TO_COMPARE]
-    ).toFixed(3);
-    let pastYear = Number.parseFloat(year) - 0.25 + '';
-    pastYear = pastYear.includes('.') ? pastYear : pastYear + '.0';
-    const pastYearData = this.sdmDihService.sdmData[pastYear];
-    if (!pastYearData) {
-      // We are in the first time step, so there is nothing to compare
-      return 0;
-    }
-    let pastYearValue = pastYearData.find(
-      (regionPastYear) => regionPastYear['MODEL'] === region
-    )?.[PARAM_TO_COMPARE];
-    pastYearValue = +Number.parseFloat(pastYearValue).toFixed(3);
-    if (pastYearValue < thisYearValue) {
-      return 1;
-    }
-    if (pastYearValue > thisYearValue) {
-      return -1;
-    }
-    return 0;
-  }
 }

+ 2 - 3
src/app/sdm-dih.service.ts

@@ -8,12 +8,11 @@ import vars2facts from '../assets/data/variables2factors.json';
 @Injectable({providedIn: 'root'})
 export class SdmDihService {
   readonly DATA_PATH = '../assets/data/normalized_data.csv';
-  sdmData = {};
+  sdmData;
   //aggregatedData = {};
   firstYear: string;
   lastYear: string;
   regions = [];
-  selectedYear = '';
   years = [];
   yearsLoaded = false;
   dataLoads: BehaviorSubject<boolean> = new BehaviorSubject(false);
@@ -32,7 +31,6 @@ export class SdmDihService {
           ),
         ];
         console.log(this.regions);
-        this.selectedYear = this.firstYear;
         this.dataLoads.next(true);
       })
       .catch((err) => {
@@ -73,6 +71,7 @@ export class SdmDihService {
   async loadData() {
     const data = await csv(this.DATA_PATH); // Array of objects, each object represents a row, where key is a heading and value is the cell
     console.log(data);
+    this.sdmData = {};
     for (const row of data) {
       const timeStep = row['TIME_STEP'];
       const regionData = this.splitDataByFactor(row);