@aclymatepackages/modules 1.0.25 → 1.0.27

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,70 @@ 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
+ },
144
+ size: "small"
145
+ });
146
+ };
147
+ const ChartLineLabelSetter = _ref9 => {
148
+ let {
149
+ viewBox,
150
+ labels,
151
+ setLabels,
152
+ label
153
+ } = _ref9,
154
+ otherProps = _objectWithoutProperties(_ref9, _excluded2);
155
+ (0, _react.useEffect)(() => {
156
+ const {
157
+ y
158
+ } = viewBox;
159
+ if (!labels.find(existingLabel => existingLabel.label === label)) {
160
+ setLabels(existingLabels => [...existingLabels, _objectSpread({
161
+ y,
162
+ label
163
+ }, otherProps)]);
164
+ }
165
+ }, [viewBox, label, labels, setLabels, otherProps]);
166
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null);
167
+ };
168
+ const LabeledEmissionsChart = _ref10 => {
115
169
  var _data$, _data;
116
170
  let {
171
+ graphPeriod,
117
172
  data,
118
- chartRef,
119
173
  type,
120
174
  displayUnitLabel,
121
175
  chartArray,
122
176
  aspect = 3,
123
- showTooltip
124
- } = _ref8;
177
+ showTooltip,
178
+ netZeroPercentage,
179
+ baseline
180
+ } = _ref10;
125
181
  const {
126
182
  palette
127
183
  } = (0, _material.useTheme)();
@@ -133,17 +189,66 @@ const LabeledEmissionsChart = _ref8 => {
133
189
  warningField: "warning",
134
190
  barSumField: "totalEmissionsSumTons"
135
191
  });
192
+ const [chartWidth, setChartWidth] = (0, _react.useState)(0);
193
+ const [referenceLineLabels, setReferenceLineLabels] = (0, _react.useState)([]);
194
+ const chartContainerRef = (0, _react.useRef)();
195
+ (0, _react.useEffect)(() => {
196
+ if (chartContainerRef.current) {
197
+ setChartWidth(chartContainerRef.current.offsetWidth);
198
+ }
199
+ }, [chartContainerRef]);
136
200
  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
201
  const ChartElement = type === "bar" ? _recharts.Bar : _recharts.Area;
202
+ const buildReferenceLinesArray = () => {
203
+ if (!baseline) {
204
+ return [];
205
+ }
206
+ const baselineReferenceLine = {
207
+ value: baseline,
208
+ label: "".concat((0, _formatters.ucFirstLetters)(graphPeriod), "ly Baseline"),
209
+ labelPosition: "top"
210
+ };
211
+ const ghgReductionLine = {
212
+ value: baseline * 0.5,
213
+ label: "GHG Reduction Mandate (50% by 2030)",
214
+ labelPosition: "top"
215
+ };
216
+ const companyPledgeLabel = "Company pledge to reduce emissions by ".concat(netZeroPercentage, "% by 2030");
217
+ const pledgeReductionValue = baseline * (1 - netZeroPercentage / 100);
218
+ if (!netZeroPercentage) {
219
+ return [baselineReferenceLine, ghgReductionLine];
220
+ }
221
+ if (netZeroPercentage >= 55) {
222
+ return [baselineReferenceLine, ghgReductionLine, {
223
+ value: pledgeReductionValue,
224
+ label: companyPledgeLabel,
225
+ labelPosition: "bottom"
226
+ }];
227
+ }
228
+ return [baselineReferenceLine, {
229
+ label: companyPledgeLabel,
230
+ value: pledgeReductionValue,
231
+ labelPosition: "bottom"
232
+ }];
233
+ };
234
+ const referenceLines = buildReferenceLinesArray();
235
+
236
+ //This weird hack is needed because for some reason every label shows up in referenceLineLabels twice.
237
+ const uniqueReferenceLineLabels = [...new Set(referenceLineLabels.map(_ref11 => {
238
+ let {
239
+ label
240
+ } = _ref11;
241
+ return label;
242
+ }))].map(label => referenceLineLabels.find(lineLabel => lineLabel.label === label));
138
243
  return /*#__PURE__*/_react.default.createElement(_material.Box, {
139
- position: "relative"
244
+ position: "relative",
245
+ ref: chartContainerRef
140
246
  }, /*#__PURE__*/_react.default.createElement(_recharts.ResponsiveContainer, {
141
247
  aspect: aspect
142
248
  }, /*#__PURE__*/_react.default.createElement(_recharts.ComposedChart, {
143
249
  width: 500,
144
250
  height: 300,
145
- data: data,
146
- ref: chartRef
251
+ data: data
147
252
  }, /*#__PURE__*/_react.default.createElement(_recharts.XAxis, {
148
253
  dataKey: "label",
149
254
  interval: "preserveStartEnd",
@@ -161,12 +266,12 @@ const LabeledEmissionsChart = _ref8 => {
161
266
  categoriesArray: chartArray,
162
267
  displayUnitLabel: displayUnitLabel
163
268
  })
164
- }), chartArray.map(_ref9 => {
269
+ }), chartArray.map(_ref12 => {
165
270
  let {
166
271
  subcategory,
167
272
  color
168
- } = _ref9,
169
- otherProps = _objectWithoutProperties(_ref9, _excluded2);
273
+ } = _ref12,
274
+ otherProps = _objectWithoutProperties(_ref12, _excluded3);
170
275
  return /*#__PURE__*/_react.default.createElement(ChartElement, _extends({
171
276
  key: "emissions-chart-element-".concat(subcategory),
172
277
  type: "monotone",
@@ -184,26 +289,45 @@ const LabeledEmissionsChart = _ref8 => {
184
289
  strokeWidth: 4,
185
290
  dot: false,
186
291
  strokeDasharray: "5 5"
187
- }))), warningLabels);
292
+ }), !!referenceLines.length && referenceLines.map((_ref13, idx) => {
293
+ let {
294
+ label,
295
+ value,
296
+ labelPosition
297
+ } = _ref13;
298
+ return /*#__PURE__*/_react.default.createElement(_recharts.ReferenceLine, {
299
+ key: "chart-reference-line-".concat(idx),
300
+ y: value,
301
+ strokeWidth: 2,
302
+ stroke: palette.backgroundGray.dark,
303
+ label: /*#__PURE__*/_react.default.createElement(ChartLineLabelSetter, {
304
+ label: label,
305
+ labels: referenceLineLabels,
306
+ setLabels: setReferenceLineLabels
307
+ })
308
+ });
309
+ }))), warningLabels, !!uniqueReferenceLineLabels.length && uniqueReferenceLineLabels.map((label, idx) => /*#__PURE__*/_react.default.createElement(ReferenceLineLabelChips, _extends({
310
+ key: "reference-line-label-chip-".concat(idx),
311
+ x: chartWidth / 2
312
+ }, label))));
188
313
  };
189
- const EmissionsChart = _ref10 => {
314
+ const EmissionsChart = _ref14 => {
190
315
  let {
191
316
  dataArray: emissions,
192
317
  type,
193
318
  viewMode = "subcategories",
194
319
  graphPeriod,
195
- chartRef,
196
320
  showTrendline,
197
321
  displayUnit,
198
- unitConverter,
199
322
  aspect,
200
323
  isPercentageChart,
201
324
  showTooltip = true,
202
325
  startDate,
203
- convertCarbonUnits,
326
+ convertCarbonUnits = tons => tons,
204
327
  displayUnitLabel,
205
- branding
206
- } = _ref10;
328
+ branding,
329
+ netZeroPercentage
330
+ } = _ref14;
207
331
  const {
208
332
  chartLabelsArray,
209
333
  scopesArray,
@@ -217,8 +341,8 @@ const EmissionsChart = _ref10 => {
217
341
  buildRealDataObj,
218
342
  viewMode
219
343
  });
220
- const convertChartDataObject = (chartDataObj, converter) => Object.fromEntries(Object.entries(chartDataObj).map(_ref11 => {
221
- let [key, value] = _ref11;
344
+ const convertChartDataObject = (chartDataObj, converter) => Object.fromEntries(Object.entries(chartDataObj).map(_ref15 => {
345
+ let [key, value] = _ref15;
222
346
  if (typeof value !== "number") {
223
347
  return [key, value];
224
348
  }
@@ -229,35 +353,35 @@ const EmissionsChart = _ref10 => {
229
353
  }));
230
354
  if (isPercentageChart) {
231
355
  const percentageConvertedChartData = preliminaryChartData.map(chartDataObj => {
232
- const objectSubcategoryProperties = Object.keys(chartDataObj).filter(key => _subcategories.subcategories.find(_ref12 => {
356
+ const objectSubcategoryProperties = Object.keys(chartDataObj).filter(key => _subcategories.subcategories.find(_ref16 => {
233
357
  let {
234
358
  subcategory
235
- } = _ref12;
359
+ } = _ref16;
236
360
  return subcategory === key;
237
361
  }));
238
362
  const objectSubcategoryValues = objectSubcategoryProperties.map(subcategory => ({
239
363
  key: subcategory,
240
364
  value: chartDataObj[subcategory]
241
365
  }));
242
- const periodEmissionsSum = objectSubcategoryValues.reduce((sum, _ref13) => {
366
+ const periodEmissionsSum = objectSubcategoryValues.reduce((sum, _ref17) => {
243
367
  let {
244
368
  value
245
- } = _ref13;
369
+ } = _ref17;
246
370
  return value + sum;
247
371
  }, 0);
248
372
  const percentageConverter = value => value / periodEmissionsSum * 100;
249
- const newObject = Object.fromEntries(objectSubcategoryValues.map(_ref14 => {
373
+ const newObject = Object.fromEntries(objectSubcategoryValues.map(_ref18 => {
250
374
  let {
251
375
  key,
252
376
  value
253
- } = _ref14;
377
+ } = _ref18;
254
378
  return [key, value];
255
379
  }));
256
380
  return convertChartDataObject(newObject, percentageConverter);
257
381
  });
258
382
  return labelChartData(percentageConvertedChartData);
259
383
  }
260
- const unitConvertedChartData = preliminaryChartData.map(chartDataObj => convertChartDataObject(chartDataObj, value => unitConverter ? unitConverter(value) : convertCarbonUnits(value)));
384
+ const unitConvertedChartData = preliminaryChartData.map(chartDataObj => convertChartDataObject(chartDataObj, convertCarbonUnits));
261
385
  const labeledChartData = labelChartData(unitConvertedChartData);
262
386
  if (showTrendline) {
263
387
  return addTrendlineToChartData(labeledChartData);
@@ -265,6 +389,26 @@ const EmissionsChart = _ref10 => {
265
389
  return labeledChartData;
266
390
  };
267
391
  const chartData = buildChartData();
392
+ const findFirstYearBaseline = () => {
393
+ if ((0, _dayjs.default)().diff((0, _dayjs.default)(startDate), "month") < 12) {
394
+ return false;
395
+ }
396
+ const {
397
+ groupedEmissions: firstYearBaselineMonthlyEmissions
398
+ } = (0, _chartHelpers.buildEmissionGroupData)(emissions, "month", startDate);
399
+ const firstYearMonthlyGroupedEmissions = [...new Array(11)].map((_, idx) => firstYearBaselineMonthlyEmissions[idx]);
400
+ const firstYearMonthlyEmissionsSum = firstYearMonthlyGroupedEmissions.map(emissions => ({
401
+ tonsCo2e: (0, _otherHelpers.sumTonsCo2e)(emissions)
402
+ }));
403
+ const monthlyBaselineTons = (0, _otherHelpers.sumTonsCo2e)(firstYearMonthlyEmissionsSum) / 12;
404
+ if (graphPeriod === "year") {
405
+ return convertCarbonUnits(monthlyBaselineTons * 12);
406
+ }
407
+ if (graphPeriod === "quarter") {
408
+ return convertCarbonUnits(monthlyBaselineTons * 3);
409
+ }
410
+ return convertCarbonUnits(monthlyBaselineTons);
411
+ };
268
412
  const chartArray = viewMode === "scopes" ? scopesArray : subcategoriesArray;
269
413
  return /*#__PURE__*/_react.default.createElement(_material.Grid, {
270
414
  container: true,
@@ -273,14 +417,16 @@ const EmissionsChart = _ref10 => {
273
417
  item: true
274
418
  }, /*#__PURE__*/_react.default.createElement(LabeledEmissionsChart, {
275
419
  data: chartData,
276
- chartRef: chartRef,
277
420
  type: type,
278
421
  displayUnitLabel: isPercentageChart ? "%" : displayUnit || displayUnitLabel,
279
422
  chartArray: chartArray.map(obj => _objectSpread(_objectSpread({}, obj), {}, {
280
423
  viewMode
281
424
  })),
282
425
  aspect: aspect,
283
- showTooltip: showTooltip
426
+ showTooltip: showTooltip,
427
+ graphPeriod: graphPeriod,
428
+ netZeroPercentage: netZeroPercentage,
429
+ baseline: findFirstYearBaseline()
284
430
  })));
285
431
  };
286
432
  var _default = exports.default = EmissionsChart;
@@ -52,7 +52,6 @@ const ReportGraphContentLayout = _ref => {
52
52
  viewMode: chartViewMode,
53
53
  graphPeriod: interval,
54
54
  displayUnit: "tons",
55
- unitConverter: tons => tons,
56
55
  aspect: 2,
57
56
  isPercentageChart: isPercentageChart,
58
57
  showTooltip: showTooltip,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aclymatepackages/modules",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
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,67 @@ 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
+ }}
136
+ size="small"
137
+ />
138
+ );
139
+ };
140
+
141
+ const ChartLineLabelSetter = ({
142
+ viewBox,
143
+ labels,
144
+ setLabels,
145
+ label,
146
+ ...otherProps
147
+ }) => {
148
+ useEffect(() => {
149
+ const { y } = viewBox;
150
+
151
+ if (!labels.find((existingLabel) => existingLabel.label === label)) {
152
+ setLabels((existingLabels) => [
153
+ ...existingLabels,
154
+ { y, label, ...otherProps },
155
+ ]);
156
+ }
157
+ }, [viewBox, label, labels, setLabels, otherProps]);
158
+
159
+ return <></>;
160
+ };
161
+
113
162
  const LabeledEmissionsChart = ({
163
+ graphPeriod,
114
164
  data,
115
- chartRef,
116
165
  type,
117
166
  displayUnitLabel,
118
167
  chartArray,
119
168
  aspect = 3,
120
169
  showTooltip,
170
+ netZeroPercentage,
171
+ baseline,
121
172
  }) => {
122
173
  const { palette } = useTheme();
123
174
 
@@ -127,14 +178,80 @@ const LabeledEmissionsChart = ({
127
178
  barSumField: "totalEmissionsSumTons",
128
179
  });
129
180
 
181
+ const [chartWidth, setChartWidth] = useState(0);
182
+ const [referenceLineLabels, setReferenceLineLabels] = useState([]);
183
+
184
+ const chartContainerRef = useRef();
185
+
186
+ useEffect(() => {
187
+ if (chartContainerRef.current) {
188
+ setChartWidth(chartContainerRef.current.offsetWidth);
189
+ }
190
+ }, [chartContainerRef]);
191
+
130
192
  const isTrendLineGood = data[0]?.trendLine > data[data.length - 1]?.trendLine;
131
193
 
132
194
  const ChartElement = type === "bar" ? Bar : Area;
133
195
 
196
+ const buildReferenceLinesArray = () => {
197
+ if (!baseline) {
198
+ return [];
199
+ }
200
+
201
+ const baselineReferenceLine = {
202
+ value: baseline,
203
+ label: `${ucFirstLetters(graphPeriod)}ly Baseline`,
204
+ labelPosition: "top",
205
+ };
206
+
207
+ const ghgReductionLine = {
208
+ value: baseline * 0.5,
209
+ label: "GHG Reduction Mandate (50% by 2030)",
210
+ labelPosition: "top",
211
+ };
212
+
213
+ const companyPledgeLabel = `Company pledge to reduce emissions by ${netZeroPercentage}% by 2030`;
214
+ const pledgeReductionValue = baseline * (1 - netZeroPercentage / 100);
215
+
216
+ if (!netZeroPercentage) {
217
+ return [baselineReferenceLine, ghgReductionLine];
218
+ }
219
+
220
+ if (netZeroPercentage >= 55) {
221
+ return [
222
+ baselineReferenceLine,
223
+ ghgReductionLine,
224
+ {
225
+ value: pledgeReductionValue,
226
+ label: companyPledgeLabel,
227
+ labelPosition: "bottom",
228
+ },
229
+ ];
230
+ }
231
+
232
+ return [
233
+ baselineReferenceLine,
234
+ {
235
+ label: companyPledgeLabel,
236
+ value: pledgeReductionValue,
237
+ labelPosition: "bottom",
238
+ },
239
+ ];
240
+ };
241
+
242
+ const referenceLines = buildReferenceLinesArray();
243
+
244
+ //This weird hack is needed because for some reason every label shows up in referenceLineLabels twice.
245
+ const uniqueReferenceLineLabels = [
246
+ ...new Set(referenceLineLabels.map(({ label }) => label)),
247
+ ].map((label) =>
248
+ referenceLineLabels.find((lineLabel) => lineLabel.label === label)
249
+ );
250
+
134
251
  return (
135
- <Box position="relative">
252
+ <Box position="relative" ref={chartContainerRef}>
136
253
  <ResponsiveContainer aspect={aspect}>
137
- <ComposedChart width={500} height={300} data={data} ref={chartRef}>
254
+ <ComposedChart width={500} height={300} data={data}>
138
255
  <XAxis dataKey="label" interval="preserveStartEnd" height={20} />
139
256
  <YAxis
140
257
  tickFormatter={(tick) =>
@@ -179,9 +296,33 @@ const LabeledEmissionsChart = ({
179
296
  dot={false}
180
297
  strokeDasharray="5 5"
181
298
  />
299
+ {!!referenceLines.length &&
300
+ referenceLines.map(({ label, value, labelPosition }, idx) => (
301
+ <ReferenceLine
302
+ key={`chart-reference-line-${idx}`}
303
+ y={value}
304
+ strokeWidth={2}
305
+ stroke={palette.backgroundGray.dark}
306
+ label={
307
+ <ChartLineLabelSetter
308
+ label={label}
309
+ labels={referenceLineLabels}
310
+ setLabels={setReferenceLineLabels}
311
+ />
312
+ }
313
+ />
314
+ ))}
182
315
  </ComposedChart>
183
316
  </ResponsiveContainer>
184
317
  {warningLabels}
318
+ {!!uniqueReferenceLineLabels.length &&
319
+ uniqueReferenceLineLabels.map((label, idx) => (
320
+ <ReferenceLineLabelChips
321
+ key={`reference-line-label-chip-${idx}`}
322
+ x={chartWidth / 2}
323
+ {...label}
324
+ />
325
+ ))}
185
326
  </Box>
186
327
  );
187
328
  };
@@ -191,17 +332,16 @@ const EmissionsChart = ({
191
332
  type,
192
333
  viewMode = "subcategories",
193
334
  graphPeriod,
194
- chartRef,
195
335
  showTrendline,
196
336
  displayUnit,
197
- unitConverter,
198
337
  aspect,
199
338
  isPercentageChart,
200
339
  showTooltip = true,
201
340
  startDate,
202
- convertCarbonUnits,
341
+ convertCarbonUnits = (tons) => tons,
203
342
  displayUnitLabel,
204
343
  branding,
344
+ netZeroPercentage,
205
345
  }) => {
206
346
  const {
207
347
  chartLabelsArray,
@@ -271,9 +411,7 @@ const EmissionsChart = ({
271
411
  }
272
412
 
273
413
  const unitConvertedChartData = preliminaryChartData.map((chartDataObj) =>
274
- convertChartDataObject(chartDataObj, (value) =>
275
- unitConverter ? unitConverter(value) : convertCarbonUnits(value)
276
- )
414
+ convertChartDataObject(chartDataObj, convertCarbonUnits)
277
415
  );
278
416
 
279
417
  const labeledChartData = labelChartData(unitConvertedChartData);
@@ -287,6 +425,33 @@ const EmissionsChart = ({
287
425
 
288
426
  const chartData = buildChartData();
289
427
 
428
+ const findFirstYearBaseline = () => {
429
+ if (dayjs().diff(dayjs(startDate), "month") < 12) {
430
+ return false;
431
+ }
432
+
433
+ const { groupedEmissions: firstYearBaselineMonthlyEmissions } =
434
+ buildEmissionGroupData(emissions, "month", startDate);
435
+
436
+ const firstYearMonthlyGroupedEmissions = [...new Array(11)].map(
437
+ (_, idx) => firstYearBaselineMonthlyEmissions[idx]
438
+ );
439
+ const firstYearMonthlyEmissionsSum = firstYearMonthlyGroupedEmissions.map(
440
+ (emissions) => ({ tonsCo2e: sumTonsCo2e(emissions) })
441
+ );
442
+ const monthlyBaselineTons = sumTonsCo2e(firstYearMonthlyEmissionsSum) / 12;
443
+
444
+ if (graphPeriod === "year") {
445
+ return convertCarbonUnits(monthlyBaselineTons * 12);
446
+ }
447
+
448
+ if (graphPeriod === "quarter") {
449
+ return convertCarbonUnits(monthlyBaselineTons * 3);
450
+ }
451
+
452
+ return convertCarbonUnits(monthlyBaselineTons);
453
+ };
454
+
290
455
  const chartArray = viewMode === "scopes" ? scopesArray : subcategoriesArray;
291
456
 
292
457
  return (
@@ -294,7 +459,6 @@ const EmissionsChart = ({
294
459
  <Grid item>
295
460
  <LabeledEmissionsChart
296
461
  data={chartData}
297
- chartRef={chartRef}
298
462
  type={type}
299
463
  displayUnitLabel={
300
464
  isPercentageChart ? "%" : displayUnit || displayUnitLabel
@@ -302,6 +466,9 @@ const EmissionsChart = ({
302
466
  chartArray={chartArray.map((obj) => ({ ...obj, viewMode }))}
303
467
  aspect={aspect}
304
468
  showTooltip={showTooltip}
469
+ graphPeriod={graphPeriod}
470
+ netZeroPercentage={netZeroPercentage}
471
+ baseline={findFirstYearBaseline()}
305
472
  />
306
473
  </Grid>
307
474
  </Grid>
@@ -36,7 +36,6 @@ const ReportGraphContentLayout = ({
36
36
  viewMode={chartViewMode}
37
37
  graphPeriod={interval}
38
38
  displayUnit="tons"
39
- unitConverter={(tons) => tons}
40
39
  aspect={2}
41
40
  isPercentageChart={isPercentageChart}
42
41
  showTooltip={showTooltip}