@aclymatepackages/modules 1.0.25 → 1.0.26

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.
@@ -2,27 +2,31 @@
2
2
 
3
3
  require("core-js/modules/es.symbol.description.js");
4
4
  require("core-js/modules/es.object.assign.js");
5
+ require("core-js/modules/es.weak-map.js");
5
6
  Object.defineProperty(exports, "__esModule", {
6
7
  value: true
7
8
  });
8
9
  exports.default = void 0;
9
10
  require("core-js/modules/es.array.reduce.js");
10
- require("core-js/modules/es.object.from-entries.js");
11
11
  require("core-js/modules/web.dom-collections.iterator.js");
12
- var _react = _interopRequireDefault(require("react"));
12
+ require("core-js/modules/es.object.from-entries.js");
13
+ var _react = _interopRequireWildcard(require("react"));
13
14
  var _dayjs = _interopRequireDefault(require("dayjs"));
14
- var _dayOfYear = _interopRequireDefault(require("dayjs/plugin/dayOfYear"));
15
15
  var _recharts = require("recharts");
16
16
  var _material = require("@mui/material");
17
17
  var _formatters = require("@aclymatepackages/formatters");
18
18
  var _otherHelpers = require("@aclymatepackages/other-helpers");
19
- var _EmissionsCustomTooltip = _interopRequireDefault(require("./EmissionsCustomTooltip"));
20
- var _useChartWarningLabels = _interopRequireDefault(require("./useChartWarningLabels"));
19
+ var _converters = require("@aclymatepackages/converters");
21
20
  var _chartHelpers = require("@aclymatepackages/chart-helpers");
22
21
  var _subcategories = require("@aclymatepackages/subcategories");
22
+ var _EmissionsCustomTooltip = _interopRequireDefault(require("./EmissionsCustomTooltip"));
23
+ var _useChartWarningLabels = _interopRequireDefault(require("./useChartWarningLabels"));
23
24
  const _excluded = ["totalEmissionsSumTons"],
24
- _excluded2 = ["subcategory", "color"];
25
+ _excluded2 = ["viewBox", "labels", "setLabels", "label"],
26
+ _excluded3 = ["subcategory", "color"];
25
27
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
28
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
29
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
26
30
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
27
31
  function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
28
32
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
@@ -31,7 +35,6 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
31
35
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
32
36
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
33
37
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
34
- _dayjs.default.extend(_dayOfYear.default);
35
38
  const findObjectValuesSum = object => Object.values(object).reduce((sum, value) => sum + value, 0);
36
39
  const buildGroupedChartData = _ref => {
37
40
  let {
@@ -40,7 +43,7 @@ const buildGroupedChartData = _ref => {
40
43
  viewMode
41
44
  } = _ref;
42
45
  return groupedEmissions.map((emissionsArray, idx) => {
43
- const subcategoriesObj = buildRealDataObj(emissionsArray, _subcategories.allSubcategoriesWithOther);
46
+ const subcategoriesObj = buildRealDataObj(emissionsArray, [..._subcategories.subcategories, _subcategories.otherSubcategoryObj]);
44
47
  const buildWarningObj = () => {
45
48
  const singleEmissionWarning = emissionsArray.find(_ref2 => {
46
49
  let {
@@ -111,17 +114,72 @@ const addTrendlineToChartData = chartData => {
111
114
  });
112
115
  });
113
116
  };
114
- const LabeledEmissionsChart = _ref8 => {
117
+ const ReferenceLineLabelChips = _ref8 => {
118
+ let {
119
+ x,
120
+ y,
121
+ label
122
+ } = _ref8;
123
+ const CHIP_HEIGHT_PX = 24;
124
+ const {
125
+ palette
126
+ } = (0, _material.useTheme)();
127
+ const [chipWidth, setChipWidth] = (0, _react.useState)(0);
128
+ const chipRef = (0, _react.useRef)();
129
+ (0, _react.useEffect)(() => {
130
+ if (chipRef.current) {
131
+ setChipWidth(chipRef.current.offsetWidth);
132
+ }
133
+ }, [chipRef]);
134
+ return /*#__PURE__*/_react.default.createElement(_material.Chip, {
135
+ elevation: 3,
136
+ ref: chipRef,
137
+ label: label,
138
+ style: {
139
+ position: "absolute",
140
+ top: y - CHIP_HEIGHT_PX / 2,
141
+ left: x - chipWidth / 2,
142
+ background: (0, _converters.hexToRgba)(palette.backgroundGray.main, 0.85)
143
+ // boxShadow:
144
+ // "0px 2px 1px -1px rgba(0,0,0,0.2), 0px 1px 1px 0px rgba(0,0,0,0.14), 0px 1px 3px 0px rgba(0,0,0,0.12)",
145
+ },
146
+ size: "small"
147
+ });
148
+ };
149
+ const ChartLineLabelSetter = _ref9 => {
150
+ let {
151
+ viewBox,
152
+ labels,
153
+ setLabels,
154
+ label
155
+ } = _ref9,
156
+ otherProps = _objectWithoutProperties(_ref9, _excluded2);
157
+ (0, _react.useEffect)(() => {
158
+ const {
159
+ y
160
+ } = viewBox;
161
+ if (!labels.find(existingLabel => existingLabel.label === label)) {
162
+ setLabels(existingLabels => [...existingLabels, _objectSpread({
163
+ y,
164
+ label
165
+ }, otherProps)]);
166
+ }
167
+ }, [viewBox, label, labels, setLabels, otherProps]);
168
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null);
169
+ };
170
+ const LabeledEmissionsChart = _ref10 => {
115
171
  var _data$, _data;
116
172
  let {
173
+ graphPeriod,
117
174
  data,
118
- chartRef,
119
175
  type,
120
176
  displayUnitLabel,
121
177
  chartArray,
122
178
  aspect = 3,
123
- showTooltip
124
- } = _ref8;
179
+ showTooltip,
180
+ netZeroPercentage,
181
+ baseline
182
+ } = _ref10;
125
183
  const {
126
184
  palette
127
185
  } = (0, _material.useTheme)();
@@ -133,17 +191,66 @@ const LabeledEmissionsChart = _ref8 => {
133
191
  warningField: "warning",
134
192
  barSumField: "totalEmissionsSumTons"
135
193
  });
194
+ const [chartWidth, setChartWidth] = (0, _react.useState)(0);
195
+ const [referenceLineLabels, setReferenceLineLabels] = (0, _react.useState)([]);
196
+ const chartContainerRef = (0, _react.useRef)();
197
+ (0, _react.useEffect)(() => {
198
+ if (chartContainerRef.current) {
199
+ setChartWidth(chartContainerRef.current.offsetWidth);
200
+ }
201
+ }, [chartContainerRef]);
136
202
  const isTrendLineGood = ((_data$ = data[0]) === null || _data$ === void 0 ? void 0 : _data$.trendLine) > ((_data = data[data.length - 1]) === null || _data === void 0 ? void 0 : _data.trendLine);
137
203
  const ChartElement = type === "bar" ? _recharts.Bar : _recharts.Area;
204
+ const buildReferenceLinesArray = () => {
205
+ if (!baseline) {
206
+ return [];
207
+ }
208
+ const baselineReferenceLine = {
209
+ value: baseline,
210
+ label: "".concat((0, _formatters.ucFirstLetters)(graphPeriod), "ly Baseline"),
211
+ labelPosition: "top"
212
+ };
213
+ const ghgReductionLine = {
214
+ value: baseline * 0.5,
215
+ label: "GHG Reduction Mandate (50% by 2030)",
216
+ labelPosition: "top"
217
+ };
218
+ const companyPledgeLabel = "Company pledge to reduce emissions by ".concat(netZeroPercentage, "% by 2030");
219
+ const pledgeReductionValue = baseline * (1 - netZeroPercentage / 100);
220
+ if (!netZeroPercentage) {
221
+ return [baselineReferenceLine, ghgReductionLine];
222
+ }
223
+ if (netZeroPercentage >= 55) {
224
+ return [baselineReferenceLine, ghgReductionLine, {
225
+ value: pledgeReductionValue,
226
+ label: companyPledgeLabel,
227
+ labelPosition: "bottom"
228
+ }];
229
+ }
230
+ return [baselineReferenceLine, {
231
+ label: companyPledgeLabel,
232
+ value: pledgeReductionValue,
233
+ labelPosition: "bottom"
234
+ }];
235
+ };
236
+ const referenceLines = buildReferenceLinesArray();
237
+
238
+ //This weird hack is needed because for some reason every label shows up in referenceLineLabels twice.
239
+ const uniqueReferenceLineLabels = [...new Set(referenceLineLabels.map(_ref11 => {
240
+ let {
241
+ label
242
+ } = _ref11;
243
+ return label;
244
+ }))].map(label => referenceLineLabels.find(lineLabel => lineLabel.label === label));
138
245
  return /*#__PURE__*/_react.default.createElement(_material.Box, {
139
- position: "relative"
246
+ position: "relative",
247
+ ref: chartContainerRef
140
248
  }, /*#__PURE__*/_react.default.createElement(_recharts.ResponsiveContainer, {
141
249
  aspect: aspect
142
250
  }, /*#__PURE__*/_react.default.createElement(_recharts.ComposedChart, {
143
251
  width: 500,
144
252
  height: 300,
145
- data: data,
146
- ref: chartRef
253
+ data: data
147
254
  }, /*#__PURE__*/_react.default.createElement(_recharts.XAxis, {
148
255
  dataKey: "label",
149
256
  interval: "preserveStartEnd",
@@ -161,12 +268,12 @@ const LabeledEmissionsChart = _ref8 => {
161
268
  categoriesArray: chartArray,
162
269
  displayUnitLabel: displayUnitLabel
163
270
  })
164
- }), chartArray.map(_ref9 => {
271
+ }), chartArray.map(_ref12 => {
165
272
  let {
166
273
  subcategory,
167
274
  color
168
- } = _ref9,
169
- otherProps = _objectWithoutProperties(_ref9, _excluded2);
275
+ } = _ref12,
276
+ otherProps = _objectWithoutProperties(_ref12, _excluded3);
170
277
  return /*#__PURE__*/_react.default.createElement(ChartElement, _extends({
171
278
  key: "emissions-chart-element-".concat(subcategory),
172
279
  type: "monotone",
@@ -184,15 +291,34 @@ const LabeledEmissionsChart = _ref8 => {
184
291
  strokeWidth: 4,
185
292
  dot: false,
186
293
  strokeDasharray: "5 5"
187
- }))), warningLabels);
294
+ }), !!referenceLines.length && referenceLines.map((_ref13, idx) => {
295
+ let {
296
+ label,
297
+ value,
298
+ labelPosition
299
+ } = _ref13;
300
+ return /*#__PURE__*/_react.default.createElement(_recharts.ReferenceLine, {
301
+ key: "chart-reference-line-".concat(idx),
302
+ y: value,
303
+ strokeWidth: 2,
304
+ stroke: palette.backgroundGray.dark,
305
+ label: /*#__PURE__*/_react.default.createElement(ChartLineLabelSetter, {
306
+ label: label,
307
+ labels: referenceLineLabels,
308
+ setLabels: setReferenceLineLabels
309
+ })
310
+ });
311
+ }))), warningLabels, !!uniqueReferenceLineLabels.length && uniqueReferenceLineLabels.map((label, idx) => /*#__PURE__*/_react.default.createElement(ReferenceLineLabelChips, _extends({
312
+ key: "reference-line-label-chip-".concat(idx),
313
+ x: chartWidth / 2
314
+ }, label))));
188
315
  };
189
- const EmissionsChart = _ref10 => {
316
+ const EmissionsChart = _ref14 => {
190
317
  let {
191
318
  dataArray: emissions,
192
319
  type,
193
320
  viewMode = "subcategories",
194
321
  graphPeriod,
195
- chartRef,
196
322
  showTrendline,
197
323
  displayUnit,
198
324
  unitConverter,
@@ -202,8 +328,9 @@ const EmissionsChart = _ref10 => {
202
328
  startDate,
203
329
  convertCarbonUnits,
204
330
  displayUnitLabel,
205
- branding
206
- } = _ref10;
331
+ branding,
332
+ netZeroPercentage
333
+ } = _ref14;
207
334
  const {
208
335
  chartLabelsArray,
209
336
  scopesArray,
@@ -217,8 +344,8 @@ const EmissionsChart = _ref10 => {
217
344
  buildRealDataObj,
218
345
  viewMode
219
346
  });
220
- const convertChartDataObject = (chartDataObj, converter) => Object.fromEntries(Object.entries(chartDataObj).map(_ref11 => {
221
- let [key, value] = _ref11;
347
+ const convertChartDataObject = (chartDataObj, converter) => Object.fromEntries(Object.entries(chartDataObj).map(_ref15 => {
348
+ let [key, value] = _ref15;
222
349
  if (typeof value !== "number") {
223
350
  return [key, value];
224
351
  }
@@ -229,28 +356,28 @@ const EmissionsChart = _ref10 => {
229
356
  }));
230
357
  if (isPercentageChart) {
231
358
  const percentageConvertedChartData = preliminaryChartData.map(chartDataObj => {
232
- const objectSubcategoryProperties = Object.keys(chartDataObj).filter(key => _subcategories.subcategories.find(_ref12 => {
359
+ const objectSubcategoryProperties = Object.keys(chartDataObj).filter(key => _subcategories.subcategories.find(_ref16 => {
233
360
  let {
234
361
  subcategory
235
- } = _ref12;
362
+ } = _ref16;
236
363
  return subcategory === key;
237
364
  }));
238
365
  const objectSubcategoryValues = objectSubcategoryProperties.map(subcategory => ({
239
366
  key: subcategory,
240
367
  value: chartDataObj[subcategory]
241
368
  }));
242
- const periodEmissionsSum = objectSubcategoryValues.reduce((sum, _ref13) => {
369
+ const periodEmissionsSum = objectSubcategoryValues.reduce((sum, _ref17) => {
243
370
  let {
244
371
  value
245
- } = _ref13;
372
+ } = _ref17;
246
373
  return value + sum;
247
374
  }, 0);
248
375
  const percentageConverter = value => value / periodEmissionsSum * 100;
249
- const newObject = Object.fromEntries(objectSubcategoryValues.map(_ref14 => {
376
+ const newObject = Object.fromEntries(objectSubcategoryValues.map(_ref18 => {
250
377
  let {
251
378
  key,
252
379
  value
253
- } = _ref14;
380
+ } = _ref18;
254
381
  return [key, value];
255
382
  }));
256
383
  return convertChartDataObject(newObject, percentageConverter);
@@ -265,6 +392,26 @@ const EmissionsChart = _ref10 => {
265
392
  return labeledChartData;
266
393
  };
267
394
  const chartData = buildChartData();
395
+ const findFirstYearBaseline = () => {
396
+ if ((0, _dayjs.default)().diff((0, _dayjs.default)(startDate), "month") < 12) {
397
+ return false;
398
+ }
399
+ const {
400
+ groupedEmissions: firstYearBaselineMonthlyEmissions
401
+ } = (0, _chartHelpers.buildEmissionGroupData)(emissions, "month", startDate);
402
+ const firstYearMonthlyGroupedEmissions = [...new Array(11)].map((_, idx) => firstYearBaselineMonthlyEmissions[idx]);
403
+ const firstYearMonthlyEmissionsSum = firstYearMonthlyGroupedEmissions.map(emissions => ({
404
+ tonsCo2e: (0, _otherHelpers.sumTonsCo2e)(emissions)
405
+ }));
406
+ const monthlyBaseline = (0, _otherHelpers.sumTonsCo2e)(firstYearMonthlyEmissionsSum) / 12;
407
+ if (graphPeriod === "year") {
408
+ return monthlyBaseline * 12;
409
+ }
410
+ if (graphPeriod === "quarter") {
411
+ return monthlyBaseline * 3;
412
+ }
413
+ return monthlyBaseline;
414
+ };
268
415
  const chartArray = viewMode === "scopes" ? scopesArray : subcategoriesArray;
269
416
  return /*#__PURE__*/_react.default.createElement(_material.Grid, {
270
417
  container: true,
@@ -273,14 +420,16 @@ const EmissionsChart = _ref10 => {
273
420
  item: true
274
421
  }, /*#__PURE__*/_react.default.createElement(LabeledEmissionsChart, {
275
422
  data: chartData,
276
- chartRef: chartRef,
277
423
  type: type,
278
424
  displayUnitLabel: isPercentageChart ? "%" : displayUnit || displayUnitLabel,
279
425
  chartArray: chartArray.map(obj => _objectSpread(_objectSpread({}, obj), {}, {
280
426
  viewMode
281
427
  })),
282
428
  aspect: aspect,
283
- showTooltip: showTooltip
429
+ showTooltip: showTooltip,
430
+ graphPeriod: graphPeriod,
431
+ netZeroPercentage: netZeroPercentage,
432
+ baseline: findFirstYearBaseline()
284
433
  })));
285
434
  };
286
435
  var _default = exports.default = EmissionsChart;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aclymatepackages/modules",
3
- "version": "1.0.25",
3
+ "version": "1.0.26",
4
4
  "description": "Aclymate modules",
5
5
  "author": "William Loopesko",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,5 @@
1
- import React from "react";
1
+ import React, { useEffect, useState, useRef } from "react";
2
2
  import dayjs from "dayjs";
3
- import dayOfYear from "dayjs/plugin/dayOfYear";
4
3
 
5
4
  import {
6
5
  XAxis,
@@ -11,17 +10,15 @@ import {
11
10
  Bar,
12
11
  ComposedChart,
13
12
  Line,
13
+ ReferenceLine,
14
14
  Tooltip as ChartTooltip,
15
15
  } from "recharts";
16
16
 
17
- import { Grid, Box, useTheme } from "@mui/material";
17
+ import { Grid, Box, Chip, useTheme } from "@mui/material";
18
18
 
19
- import { formatDecimal } from "@aclymatepackages/formatters";
19
+ import { formatDecimal, ucFirstLetters } from "@aclymatepackages/formatters";
20
20
  import { sumTonsCo2e } from "@aclymatepackages/other-helpers";
21
-
22
- import EmissionsCustomTooltip from "./EmissionsCustomTooltip";
23
- import useChartWarningLabels from "./useChartWarningLabels";
24
-
21
+ import { hexToRgba } from "@aclymatepackages/converters";
25
22
  import {
26
23
  buildScopesRealDataObj,
27
24
  buildSubcategoriesDataObj,
@@ -29,10 +26,11 @@ import {
29
26
  } from "@aclymatepackages/chart-helpers";
30
27
  import {
31
28
  subcategories,
32
- allSubcategoriesWithOther,
29
+ otherSubcategoryObj,
33
30
  } from "@aclymatepackages/subcategories";
34
31
 
35
- dayjs.extend(dayOfYear);
32
+ import EmissionsCustomTooltip from "./EmissionsCustomTooltip";
33
+ import useChartWarningLabels from "./useChartWarningLabels";
36
34
 
37
35
  const findObjectValuesSum = (object) =>
38
36
  Object.values(object).reduce((sum, value) => sum + value, 0);
@@ -43,10 +41,10 @@ const buildGroupedChartData = ({
43
41
  viewMode,
44
42
  }) => {
45
43
  return groupedEmissions.map((emissionsArray, idx) => {
46
- const subcategoriesObj = buildRealDataObj(
47
- emissionsArray,
48
- allSubcategoriesWithOther
49
- );
44
+ const subcategoriesObj = buildRealDataObj(emissionsArray, [
45
+ ...subcategories,
46
+ otherSubcategoryObj,
47
+ ]);
50
48
 
51
49
  const buildWarningObj = () => {
52
50
  const singleEmissionWarning = emissionsArray.find(
@@ -110,14 +108,69 @@ const addTrendlineToChartData = (chartData) => {
110
108
  }));
111
109
  };
112
110
 
111
+ const ReferenceLineLabelChips = ({ x, y, label }) => {
112
+ const CHIP_HEIGHT_PX = 24;
113
+
114
+ const { palette } = useTheme();
115
+
116
+ const [chipWidth, setChipWidth] = useState(0);
117
+ const chipRef = useRef();
118
+
119
+ useEffect(() => {
120
+ if (chipRef.current) {
121
+ setChipWidth(chipRef.current.offsetWidth);
122
+ }
123
+ }, [chipRef]);
124
+
125
+ return (
126
+ <Chip
127
+ elevation={3}
128
+ ref={chipRef}
129
+ label={label}
130
+ style={{
131
+ position: "absolute",
132
+ top: y - CHIP_HEIGHT_PX / 2,
133
+ left: x - chipWidth / 2,
134
+ background: hexToRgba(palette.backgroundGray.main, 0.85),
135
+ // boxShadow:
136
+ // "0px 2px 1px -1px rgba(0,0,0,0.2), 0px 1px 1px 0px rgba(0,0,0,0.14), 0px 1px 3px 0px rgba(0,0,0,0.12)",
137
+ }}
138
+ size="small"
139
+ />
140
+ );
141
+ };
142
+
143
+ const ChartLineLabelSetter = ({
144
+ viewBox,
145
+ labels,
146
+ setLabels,
147
+ label,
148
+ ...otherProps
149
+ }) => {
150
+ useEffect(() => {
151
+ const { y } = viewBox;
152
+
153
+ if (!labels.find((existingLabel) => existingLabel.label === label)) {
154
+ setLabels((existingLabels) => [
155
+ ...existingLabels,
156
+ { y, label, ...otherProps },
157
+ ]);
158
+ }
159
+ }, [viewBox, label, labels, setLabels, otherProps]);
160
+
161
+ return <></>;
162
+ };
163
+
113
164
  const LabeledEmissionsChart = ({
165
+ graphPeriod,
114
166
  data,
115
- chartRef,
116
167
  type,
117
168
  displayUnitLabel,
118
169
  chartArray,
119
170
  aspect = 3,
120
171
  showTooltip,
172
+ netZeroPercentage,
173
+ baseline,
121
174
  }) => {
122
175
  const { palette } = useTheme();
123
176
 
@@ -127,14 +180,80 @@ const LabeledEmissionsChart = ({
127
180
  barSumField: "totalEmissionsSumTons",
128
181
  });
129
182
 
183
+ const [chartWidth, setChartWidth] = useState(0);
184
+ const [referenceLineLabels, setReferenceLineLabels] = useState([]);
185
+
186
+ const chartContainerRef = useRef();
187
+
188
+ useEffect(() => {
189
+ if (chartContainerRef.current) {
190
+ setChartWidth(chartContainerRef.current.offsetWidth);
191
+ }
192
+ }, [chartContainerRef]);
193
+
130
194
  const isTrendLineGood = data[0]?.trendLine > data[data.length - 1]?.trendLine;
131
195
 
132
196
  const ChartElement = type === "bar" ? Bar : Area;
133
197
 
198
+ const buildReferenceLinesArray = () => {
199
+ if (!baseline) {
200
+ return [];
201
+ }
202
+
203
+ const baselineReferenceLine = {
204
+ value: baseline,
205
+ label: `${ucFirstLetters(graphPeriod)}ly Baseline`,
206
+ labelPosition: "top",
207
+ };
208
+
209
+ const ghgReductionLine = {
210
+ value: baseline * 0.5,
211
+ label: "GHG Reduction Mandate (50% by 2030)",
212
+ labelPosition: "top",
213
+ };
214
+
215
+ const companyPledgeLabel = `Company pledge to reduce emissions by ${netZeroPercentage}% by 2030`;
216
+ const pledgeReductionValue = baseline * (1 - netZeroPercentage / 100);
217
+
218
+ if (!netZeroPercentage) {
219
+ return [baselineReferenceLine, ghgReductionLine];
220
+ }
221
+
222
+ if (netZeroPercentage >= 55) {
223
+ return [
224
+ baselineReferenceLine,
225
+ ghgReductionLine,
226
+ {
227
+ value: pledgeReductionValue,
228
+ label: companyPledgeLabel,
229
+ labelPosition: "bottom",
230
+ },
231
+ ];
232
+ }
233
+
234
+ return [
235
+ baselineReferenceLine,
236
+ {
237
+ label: companyPledgeLabel,
238
+ value: pledgeReductionValue,
239
+ labelPosition: "bottom",
240
+ },
241
+ ];
242
+ };
243
+
244
+ const referenceLines = buildReferenceLinesArray();
245
+
246
+ //This weird hack is needed because for some reason every label shows up in referenceLineLabels twice.
247
+ const uniqueReferenceLineLabels = [
248
+ ...new Set(referenceLineLabels.map(({ label }) => label)),
249
+ ].map((label) =>
250
+ referenceLineLabels.find((lineLabel) => lineLabel.label === label)
251
+ );
252
+
134
253
  return (
135
- <Box position="relative">
254
+ <Box position="relative" ref={chartContainerRef}>
136
255
  <ResponsiveContainer aspect={aspect}>
137
- <ComposedChart width={500} height={300} data={data} ref={chartRef}>
256
+ <ComposedChart width={500} height={300} data={data}>
138
257
  <XAxis dataKey="label" interval="preserveStartEnd" height={20} />
139
258
  <YAxis
140
259
  tickFormatter={(tick) =>
@@ -179,9 +298,33 @@ const LabeledEmissionsChart = ({
179
298
  dot={false}
180
299
  strokeDasharray="5 5"
181
300
  />
301
+ {!!referenceLines.length &&
302
+ referenceLines.map(({ label, value, labelPosition }, idx) => (
303
+ <ReferenceLine
304
+ key={`chart-reference-line-${idx}`}
305
+ y={value}
306
+ strokeWidth={2}
307
+ stroke={palette.backgroundGray.dark}
308
+ label={
309
+ <ChartLineLabelSetter
310
+ label={label}
311
+ labels={referenceLineLabels}
312
+ setLabels={setReferenceLineLabels}
313
+ />
314
+ }
315
+ />
316
+ ))}
182
317
  </ComposedChart>
183
318
  </ResponsiveContainer>
184
319
  {warningLabels}
320
+ {!!uniqueReferenceLineLabels.length &&
321
+ uniqueReferenceLineLabels.map((label, idx) => (
322
+ <ReferenceLineLabelChips
323
+ key={`reference-line-label-chip-${idx}`}
324
+ x={chartWidth / 2}
325
+ {...label}
326
+ />
327
+ ))}
185
328
  </Box>
186
329
  );
187
330
  };
@@ -191,7 +334,6 @@ const EmissionsChart = ({
191
334
  type,
192
335
  viewMode = "subcategories",
193
336
  graphPeriod,
194
- chartRef,
195
337
  showTrendline,
196
338
  displayUnit,
197
339
  unitConverter,
@@ -202,6 +344,7 @@ const EmissionsChart = ({
202
344
  convertCarbonUnits,
203
345
  displayUnitLabel,
204
346
  branding,
347
+ netZeroPercentage,
205
348
  }) => {
206
349
  const {
207
350
  chartLabelsArray,
@@ -287,6 +430,33 @@ const EmissionsChart = ({
287
430
 
288
431
  const chartData = buildChartData();
289
432
 
433
+ const findFirstYearBaseline = () => {
434
+ if (dayjs().diff(dayjs(startDate), "month") < 12) {
435
+ return false;
436
+ }
437
+
438
+ const { groupedEmissions: firstYearBaselineMonthlyEmissions } =
439
+ buildEmissionGroupData(emissions, "month", startDate);
440
+
441
+ const firstYearMonthlyGroupedEmissions = [...new Array(11)].map(
442
+ (_, idx) => firstYearBaselineMonthlyEmissions[idx]
443
+ );
444
+ const firstYearMonthlyEmissionsSum = firstYearMonthlyGroupedEmissions.map(
445
+ (emissions) => ({ tonsCo2e: sumTonsCo2e(emissions) })
446
+ );
447
+ const monthlyBaseline = sumTonsCo2e(firstYearMonthlyEmissionsSum) / 12;
448
+
449
+ if (graphPeriod === "year") {
450
+ return monthlyBaseline * 12;
451
+ }
452
+
453
+ if (graphPeriod === "quarter") {
454
+ return monthlyBaseline * 3;
455
+ }
456
+
457
+ return monthlyBaseline;
458
+ };
459
+
290
460
  const chartArray = viewMode === "scopes" ? scopesArray : subcategoriesArray;
291
461
 
292
462
  return (
@@ -294,7 +464,6 @@ const EmissionsChart = ({
294
464
  <Grid item>
295
465
  <LabeledEmissionsChart
296
466
  data={chartData}
297
- chartRef={chartRef}
298
467
  type={type}
299
468
  displayUnitLabel={
300
469
  isPercentageChart ? "%" : displayUnit || displayUnitLabel
@@ -302,6 +471,9 @@ const EmissionsChart = ({
302
471
  chartArray={chartArray.map((obj) => ({ ...obj, viewMode }))}
303
472
  aspect={aspect}
304
473
  showTooltip={showTooltip}
474
+ graphPeriod={graphPeriod}
475
+ netZeroPercentage={netZeroPercentage}
476
+ baseline={findFirstYearBaseline()}
305
477
  />
306
478
  </Grid>
307
479
  </Grid>