year-graph.component.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import {Component} from '@angular/core';
  2. import {interpolate, quantize, select} from 'd3';
  3. import {SdmDihService} from '../sdm-dih.service';
  4. @Component({
  5. selector: 'year-graph',
  6. templateUrl: './year-graph.component.html',
  7. styleUrls: ['./year-graph.component.scss'],
  8. })
  9. export class YearGraphComponent {
  10. selectedYear = '';
  11. constructor(public sdmDihService: SdmDihService) {
  12. this.sdmDihService.dataLoads.subscribe(() => {
  13. this.selectedYear = this.sdmDihService.firstYear;
  14. this.redrawGraphs();
  15. this.drawLegend();
  16. });
  17. }
  18. animateGraphs() {
  19. const MILLISECONDS_TO_ANIMATE = 100 as const;
  20. let i = 1;
  21. for (const year of this.sdmDihService.years) {
  22. setTimeout(() => {
  23. this.selectedYear = year;
  24. this.redrawGraphs();
  25. }, MILLISECONDS_TO_ANIMATE * i);
  26. i++;
  27. }
  28. }
  29. drawGraph(region: string) {
  30. const year = this.selectedYear.toString().includes('.')
  31. ? this.selectedYear
  32. : this.selectedYear + '.0';
  33. const regionData = this.sdmDihService.sdmData[year].find(
  34. (row) => row['MODEL'] === region
  35. );
  36. const width = 100 / this.sdmDihService.regions.length;
  37. const height = 100 / this.sdmDihService.regions.length;
  38. // append the svg object to the div called 'year-graph-place'
  39. const svg = select('#year-graph-place')
  40. .append('svg')
  41. .attr('width', `${width}%`)
  42. .attr('height', `${height}%`)
  43. .append('svg')
  44. .attr('x', '50%')
  45. .attr('y', '50%')
  46. .attr('overflow', 'visible');
  47. // arrow symbol for later re-use
  48. const arrow = svg.append('symbol').attr('id', 'arrow');
  49. // horizontal line
  50. arrow
  51. .append('line')
  52. .attr('x1', 0)
  53. .attr('y1', 30)
  54. .attr('x2', 60)
  55. .attr('y2', 30)
  56. .attr('stroke', 'black')
  57. .style('stroke-width', 4);
  58. // upper line
  59. arrow
  60. .append('line')
  61. .attr('x1', 40)
  62. .attr('y1', 10)
  63. .attr('x2', 60)
  64. .attr('y2', 30)
  65. .attr('stroke', 'black')
  66. .style('stroke-width', 4);
  67. // bottom line
  68. arrow
  69. .append('line')
  70. .attr('x1', 40)
  71. .attr('y1', 50)
  72. .attr('x2', 60)
  73. .attr('y2', 30)
  74. .attr('stroke', 'black')
  75. .style('stroke-width', 4);
  76. const svgContent = svg.append('g').data([regionData]);
  77. //.attr('transform', 'translate(' + width / 2 + '% ,' + height / 2 + '% )');
  78. svgContent
  79. .append('circle')
  80. .attr('cx', 0)
  81. .attr('cy', 0)
  82. .attr('r', 50)
  83. .attr('stroke', 'black')
  84. .attr('fill', (d) => {
  85. return this.sdmDihService.perc2color(d['aggregated']);
  86. });
  87. svgContent
  88. .append('text')
  89. .attr('x', 0)
  90. .attr('y', -60)
  91. .attr('dy', '.35em')
  92. .text((d) => d['MODEL'])
  93. .style('text-anchor', 'middle');
  94. svgContent
  95. .append('text')
  96. .attr('x', 0)
  97. .attr('y', 60)
  98. .attr('dy', '.35em')
  99. .text((d) => `${(Number.parseFloat(d['aggregated']) * 100).toFixed(2)} %`)
  100. .style('text-anchor', 'middle');
  101. svgContent
  102. .append('text')
  103. .attr('x', 0)
  104. .attr('y', 60)
  105. .attr('dy', '.35em')
  106. .text((d) => `${(Number.parseFloat(d['aggregated']) * 100).toFixed(2)} %`)
  107. .style('text-anchor', 'middle');
  108. svgContent
  109. .append('use')
  110. .attr('xlink:href', '#arrow')
  111. .attr('x', -30)
  112. .attr('y', -30)
  113. .attr(
  114. 'transform',
  115. // rotation is defined clockwise, hence -45 deg will turn the arrow up
  116. (d) => `rotate(${-45 * this.getRegionProgress(d)}, 0, 0)`
  117. );
  118. }
  119. drawLegend() {
  120. const width = 200;
  121. const height = 20;
  122. const g = select('#year-graph-legend-place')
  123. .append('svg')
  124. .attr('width', width)
  125. .attr('height', height)
  126. .attr('viewBox', [0, 0, width, height])
  127. .style('overflow', 'visible')
  128. .style('display', 'block')
  129. .append('g');
  130. for (let i = 0; i < 10; i++) {
  131. g.append('rect')
  132. .attr('x', i * 20)
  133. .attr('y', 0)
  134. .attr('width', width / 10)
  135. .attr('height', height / 2)
  136. .attr('fill', this.sdmDihService.perc2color(i / 10));
  137. }
  138. g.append('text')
  139. .attr('x', 0 - 10)
  140. .attr('y', 20)
  141. .attr('dy', '.5em')
  142. .text('0 %');
  143. g.append('text')
  144. .attr('x', 200 - 10)
  145. .attr('y', 20)
  146. .attr('dy', '.5em')
  147. .text('100 %');
  148. }
  149. redrawGraphs() {
  150. const width = 200;
  151. const height = 200;
  152. select('#year-graph-place').selectAll('svg')?.remove();
  153. for (const region of this.sdmDihService.regions) {
  154. this.drawGraph(region);
  155. }
  156. }
  157. getRegionProgress(regionData): -1 | 0 | 1 {
  158. //TODO: parametrize the PARAM_TO_COMPARE
  159. //TODO: use i-2, i-1 comparison based on 2 previous steps
  160. const PARAM_TO_COMPARE = 'aggregated';
  161. const region = regionData['MODEL'];
  162. const year = regionData['TIME_STEP'];
  163. const thisYearValue = +Number.parseFloat(
  164. regionData[PARAM_TO_COMPARE]
  165. ).toFixed(3);
  166. let pastYear = Number.parseFloat(year) - 0.25 + '';
  167. pastYear = pastYear.includes('.') ? pastYear : pastYear + '.0';
  168. const pastYearData = this.sdmDihService.sdmData[pastYear];
  169. if (!pastYearData) {
  170. // We are in the first time step, so there is nothing to compare
  171. return 0;
  172. }
  173. let pastYearValue = pastYearData.find(
  174. (regionPastYear) => regionPastYear['MODEL'] === region
  175. )?.[PARAM_TO_COMPARE];
  176. pastYearValue = +Number.parseFloat(pastYearValue).toFixed(3);
  177. if (pastYearValue < thisYearValue) {
  178. return 1;
  179. }
  180. if (pastYearValue > thisYearValue) {
  181. return -1;
  182. }
  183. return 0;
  184. }
  185. }