diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/Graphs/HeatLoad.tsx b/heat-stack/app/components/ui/heat/CaseSummaryComponents/Graphs/HeatLoad.tsx
index f5223fd3..fc77291a 100644
--- a/heat-stack/app/components/ui/heat/CaseSummaryComponents/Graphs/HeatLoad.tsx
+++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/Graphs/HeatLoad.tsx
@@ -1,48 +1,189 @@
+import React, { useMemo } from 'react'
import {
- ScatterChart,
- Scatter,
+ ComposedChart,
+ Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
+ Label,
+ Scatter,
} from 'recharts'
+import { SummaryOutputSchema } from '../../../../../../types/types'
+import { Icon } from '../../../icon'
+import { HeatLoadGrid } from '../HeatLoadGrid'
+import {
+ COLOR_GREY_LIGHT,
+ COLOR_ORANGE,
+ COLOR_BLUE,
+} from '../constants'
+import {
+ calculateAvgHeatLoad,
+ calculateMaxHeatLoad,
+} from '../utility/heat-load-calculations'
+import { buildHeatLoadGraphData } from '../utility/build-heat-load-graph-data'
+import { HeatLoadGraphToolTip } from './HeatLoadGraphToolTip'
+import { CustomLegend } from './HeatLoadGraphLegend'
+import { DESIGN_SET_POINT } from '../../../../../global_constants'
+
+const X_AXIS_BUFFER_PERCENTAGE_MAX = 1.3; // 30% buffer
+const Y_AXIS_ROUNDING_UNIT = 10000; // Rounding unit for minY and maxY
+const Y_AXIS_MIN_VALUE = 0; // Always start the Y axis at 0
+
+const roundDownToNearestTen = (n: number) => Math.floor(n / 10) * 10; // Used for determining the start temperature on the X axis
+
+type HeatLoadProps = {
+ heatLoadSummaryOutput: SummaryOutputSchema
+}
+
+/**
+ * HeatLoad component renders a chart displaying the heating system demand based on different outdoor temperatures.
+ * It includes two lines for the maximum and average heat loads, with scatter points at the design temperature.
+ *
+ * @param {HeatLoadProps} props - The props containing heat load data to render the chart.
+ * @returns {JSX.Element} - The rendered chart component.
+ */
+export function HeatLoad({
+ heatLoadSummaryOutput,
+}: HeatLoadProps): JSX.Element {
+ const { design_temperature, whole_home_heat_loss_rate } = heatLoadSummaryOutput
+ const minTemperature = roundDownToNearestTen(design_temperature - 10) // Start temperature rounded down from design temperature for visual clarity
+ const maxTemperature = DESIGN_SET_POINT + 2 // end the X axis at the DESIGN_SET_POINT plus 2f for visual clarity
+
+ /**
+ * useMemo to build the HeatLoad graph data.
+ */
+ const data = useMemo(() => {
+ return buildHeatLoadGraphData(
+ heatLoadSummaryOutput,
+ minTemperature,
+ DESIGN_SET_POINT,
+ maxTemperature,
+ )
+ }, [heatLoadSummaryOutput])
+
+ /**
+ * useMemo to iterate through the data and calculate the min and max values for the Y axis.
+ */
+ const { minYValue, maxYValue } = useMemo(() => {
+ let minValue = Infinity
+ let maxValue = 0
+
+ for (const point of data) {
+ const maxLine = point.maxLine || 0
+ const avgLine = point.avgLine || 0
+
+ minValue = Math.min(minValue, maxLine || Infinity, avgLine || Infinity)
+ maxValue = Math.max(maxValue, maxLine, avgLine)
+ }
-// data from Karle Heat Load Analysis Beta 7 2023-07-11
-const data = [
- { x: 0, y: 74015 },
- { x: 60.5, y: 10045 },
- { x: 67, y: 3172 },
- { x: 70, y: 0 },
- { x: 8.4, y: 65133 },
-]
+ // seet min and max Y axis values
+ const minY = Y_AXIS_MIN_VALUE
+ const adjustedMaxYValue = maxValue * X_AXIS_BUFFER_PERCENTAGE_MAX;
+ const maxY = Math.ceil(adjustedMaxYValue / Y_AXIS_ROUNDING_UNIT) * Y_AXIS_ROUNDING_UNIT
+
+ return { minYValue: minY, maxYValue: maxY }
+ }, [data])
-export function HeatLoad() {
return (
-
-
Heating System Demand
+
+
+ Heating System Demand
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+ } />
+
+ {/* Line for maximum heat load */}
+
+
+ {/* Line for average heat load */}
+
+
+ {/* Scatter point for maximum heat load at design temperature */}
+
-
-
-
-
+
+ {/* Scatter point for average heat load at design temperature */}
+
+
+
+
+ );
+};
diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/Graphs/HeatLoadGraphToolTip.tsx b/heat-stack/app/components/ui/heat/CaseSummaryComponents/Graphs/HeatLoadGraphToolTip.tsx
new file mode 100644
index 00000000..925ae62c
--- /dev/null
+++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/Graphs/HeatLoadGraphToolTip.tsx
@@ -0,0 +1,35 @@
+type HeatLoadGraphToolTipProps = {
+ payload?: Array<{ payload: { temperature?: number }; value?: number; name?: string }>
+}
+
+/**
+ * CustomTooltip renders a tooltip for the heat load chart.
+ * @param {object} props - The props containing data for the tooltip.
+ * @returns {JSX.Element} - The rendered tooltip element.
+ */
+export const HeatLoadGraphToolTip = (
+ props: HeatLoadGraphToolTipProps,
+): JSX.Element => {
+ const { payload } = props
+ const temperature = payload?.[0]?.payload?.temperature ?? null
+ const value = payload?.[0]?.value ?? null
+ const name = payload?.[0]?.name ?? ''
+
+ if (temperature !== null) {
+ return (
+
+
{`${Number(value).toLocaleString()} BTU/h`}
+
{`${temperature}°F ${name.replace('Line', ' Heat Load').replace('Point', ' at Design Temperature')}`}
+
+ )
+ }
+
+ return (
+
+
{`${Number(value).toLocaleString()} BTU/h`}
+
+ {name.replace('Line', ' Heat Load').replace('Point', ' at Design Temperature')}
+
+
+ )
+}
diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/HeatLoadAnalysis.tsx b/heat-stack/app/components/ui/heat/CaseSummaryComponents/HeatLoadAnalysis.tsx
index c0393dbb..059f7ad2 100644
--- a/heat-stack/app/components/ui/heat/CaseSummaryComponents/HeatLoadAnalysis.tsx
+++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/HeatLoadAnalysis.tsx
@@ -1,8 +1,13 @@
// import { AnalysisHeader } from './AnalysisHeader.tsx'
+import React from 'react'
import { HeatLoad } from './Graphs/HeatLoad.tsx'
import { WholeHomeUAComparison } from './Graphs/WholeHomeUAComparison.tsx'
-export function Graphs() {
+interface GraphsProps {
+ heatLoadSummaryOutput: any;
+}
+
+export function Graphs({ heatLoadSummaryOutput }: GraphsProps) {
const fuel_type = 'Natural Gas'
const titleClassTailwind = 'text-5xl font-extrabold tracking-wide'
const componentMargin = 'mt-10'
@@ -14,7 +19,7 @@ export function Graphs() {
Fuel Type
{fuel_type}
{/* */}
-
+
)
diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/HeatLoadGrid.tsx b/heat-stack/app/components/ui/heat/CaseSummaryComponents/HeatLoadGrid.tsx
new file mode 100644
index 00000000..f56cde06
--- /dev/null
+++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/HeatLoadGrid.tsx
@@ -0,0 +1,55 @@
+import React from 'react'
+
+type HeatLoadGridProps = {
+ setPoint: number
+ averageHeatLoad: number
+ maxHeatLoad: number
+}
+
+/**
+ * HeatLoadGrid is a stateless functional component that displays key summary data
+ * in a grid format. The grid includes the set point temperature, maximum heat load,
+ * and average heat load values.
+ *
+ * @component
+ * @param {HeatLoadGridProps} props - The props for the HeatLoadGrid component.
+ * @param {number} props.setPoint - The set point temperature in degrees Fahrenheit.
+ * @param {number} props.averageHeatLoad - The average heat load in BTU/h.
+ * @param {number} props.maxHeatLoad - The maximum heat load in BTU/h.
+ * @returns {JSX.Element} - A styled grid displaying the set point, max heat load, and average heat load.
+ */
+export const HeatLoadGrid = ({
+ setPoint,
+ averageHeatLoad,
+ maxHeatLoad,
+}: HeatLoadGridProps) => {
+ return (
+
+
+ {/* Grid Item 1 */}
+
+
+
Set Point
+
{`${setPoint} °F`}
+
+
+
+ {/* Grid Item 2 */}
+
+
+
Max Heat Load
+
{`${maxHeatLoad} BTU/h`}
+
+
+
+ {/* Grid Item 3 */}
+
+
+
Average Heat Load
+
{`${averageHeatLoad} BTU/h`}
+
+
+
+
+ )
+}
diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/constants.ts b/heat-stack/app/components/ui/heat/CaseSummaryComponents/constants.ts
new file mode 100644
index 00000000..d9820d20
--- /dev/null
+++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/constants.ts
@@ -0,0 +1,6 @@
+// Constants for chart styling
+export const COLOR_ORANGE = '#FF5733'
+export const COLOR_BLUE = '#8884d8'
+export const COLOR_GREY = '#999999'
+export const COLOR_GREY_LIGHT = '#cccccc'
+export const COLOR_WHITE = '#fff'
diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/utility/build-heat-load-graph-data.ts b/heat-stack/app/components/ui/heat/CaseSummaryComponents/utility/build-heat-load-graph-data.ts
new file mode 100644
index 00000000..dd09b2ff
--- /dev/null
+++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/utility/build-heat-load-graph-data.ts
@@ -0,0 +1,99 @@
+import { SummaryOutputSchema } from '../../../../../../types/types';
+import {
+ calculateAvgHeatLoad,
+ calculateAvgHeatLoadEndPoint,
+ calculateMaxHeatLoad,
+} from './heat-load-calculations';
+
+type HeatLoadGraphPoint = {
+ temperature: number
+ avgLine?: number
+ avgPoint?: number
+ maxLine?: number
+ maxPoint?: number
+};
+
+/**
+ * Calculate the heat load data points for max and avg lines.
+ * The data is computed based on the provided heat load summary and the design temperature.
+ * The returned data points are used for graphing the average and maximum heat load over a range of temperatures.
+ *
+ * @param {SummaryOutputSchema} heatLoadSummaryOutput - The heat load summary data.
+ * @param {number} designSetPoint - The design temperature set point, typically 70°F.
+ * @returns {HeatLoadGraphPoint[]} - An array of data points for the lines and scatter points, each containing a temperature and associated heat load values.
+ */
+export const buildHeatLoadGraphData = (
+ heatLoadSummaryOutput: SummaryOutputSchema,
+ startTemperature: number,
+ designSetPoint: number,
+ endTemperature: number,
+): HeatLoadGraphPoint[] => {
+ const { design_temperature, whole_home_heat_loss_rate, average_indoor_temperature, estimated_balance_point } = heatLoadSummaryOutput;
+
+ const avgHeatLoad = calculateAvgHeatLoad(
+ heatLoadSummaryOutput,
+ design_temperature,
+ designSetPoint,
+ );
+
+ const maxHeatLoad = calculateMaxHeatLoad(
+ whole_home_heat_loss_rate,
+ design_temperature,
+ designSetPoint,
+ );
+
+ // Points for Avg line
+ const avgLineStartPoint = {
+ temperature: startTemperature,
+ avgLine: calculateAvgHeatLoad(
+ heatLoadSummaryOutput,
+ startTemperature,
+ designSetPoint,
+ ),
+ };
+
+ const avgLineDesignTemperaturePoint = {
+ temperature: design_temperature,
+ avgLine: avgHeatLoad,
+ avgPoint: avgHeatLoad,
+ };
+
+ const avgLineEndPoint = {
+ temperature: calculateAvgHeatLoadEndPoint(estimated_balance_point, designSetPoint, average_indoor_temperature),
+ avgLine: 0,
+ };
+
+ // Points for Max line
+ const maxLineStartPoint = {
+ temperature: startTemperature,
+ maxLine: calculateMaxHeatLoad(
+ whole_home_heat_loss_rate,
+ startTemperature,
+ designSetPoint,
+ ),
+ };
+
+ const maxLineDesignTemperaturePoint = {
+ temperature: design_temperature,
+ maxLine: maxHeatLoad,
+ maxPoint: maxHeatLoad,
+ };
+
+ const maxLineEndPoint = {
+ temperature: designSetPoint,
+ maxLine: calculateMaxHeatLoad(
+ whole_home_heat_loss_rate,
+ endTemperature,
+ designSetPoint,
+ ),
+ };
+
+ return [
+ avgLineStartPoint,
+ avgLineDesignTemperaturePoint,
+ avgLineEndPoint,
+ maxLineStartPoint,
+ maxLineDesignTemperaturePoint,
+ maxLineEndPoint
+ ]
+}
diff --git a/heat-stack/app/components/ui/heat/CaseSummaryComponents/utility/heat-load-calculations.ts b/heat-stack/app/components/ui/heat/CaseSummaryComponents/utility/heat-load-calculations.ts
new file mode 100644
index 00000000..0f5d7c31
--- /dev/null
+++ b/heat-stack/app/components/ui/heat/CaseSummaryComponents/utility/heat-load-calculations.ts
@@ -0,0 +1,69 @@
+import { SummaryOutputSchema } from '../../../../../../types/types'
+// Utility file for helper functions related to calculating heat load
+// calculations are based on documentation found here: https://docs.google.com/document/d/16WlqY3ofq4xpalsfwRuYBWMbeUHfXRvbWU69xxVNCGM/edit?tab=t.0#heading=h.tl7o1hwvhavz
+
+/**
+ * Calculates the maximum heat load based on the given temperature and heat load summary.
+ * The result is rounded to the nearest integer.
+ *
+ * @param {number} whole_home_heat_loss_rate - The heat loss rate for the whole home.
+ * @param {number} temperature - The current temperature to use in the calculation.
+ * @param {number} [designSetPoint] - The design set point temperature.
+ * @returns {number} - The calculated maximum heat load.
+ */
+export function calculateMaxHeatLoad(
+ whole_home_heat_loss_rate: number,
+ temperature: number,
+ designSetPoint: number,
+): number {
+ return Math.max(0, (designSetPoint - temperature) * whole_home_heat_loss_rate)
+}
+
+/**
+ * Calculates the average heat load based on the given temperature and heat load summary.
+ * The result is rounded to the nearest integer.
+ *
+ * @param {SummaryOutputSchema} heatLoadSummary - The summary data that includes heat loss rates and indoor temperature details.
+ * @param {number} temperature - The current temperature to use in the calculation.
+ * @param {number} [designSetPoint] - The design set point temperature.
+ * @returns {number} - The calculated average heat load.
+ */
+export function calculateAvgHeatLoad(
+ heatLoadSummary: SummaryOutputSchema,
+ temperature: number,
+ designSetPoint: number,
+): number {
+ const {
+ whole_home_heat_loss_rate,
+ average_indoor_temperature,
+ estimated_balance_point,
+ } = heatLoadSummary
+ return Math.max(
+ 0,
+ (designSetPoint -
+ average_indoor_temperature +
+ estimated_balance_point -
+ temperature) *
+ whole_home_heat_loss_rate,
+ )
+}
+
+/**
+ * Calculates the average heat load endpoint based on the balance point temperature,
+ * design set point, and average indoor temperature.
+ *
+ * This function computes the endpoint of the average heat load calculation, which can
+ * be used as part of broader heat load analyses.
+ *
+ * @param {number} balancePointTemperature - The balance point temperature used in the calculation.
+ * @param {number} designSetPoint - The design set point temperature.
+ * @param {number} averageIndoorTemperature - The average indoor temperature.
+ * @returns {number} - The calculated average heat load endpoint.
+ */
+export function calculateAvgHeatLoadEndPoint(
+ balancePointTemperature: number,
+ designSetPoint: number,
+ averageIndoorTemperature: number,
+): number {
+ return balancePointTemperature + designSetPoint - averageIndoorTemperature;
+}
diff --git a/heat-stack/app/global_constants.ts b/heat-stack/app/global_constants.ts
new file mode 100644
index 00000000..7ee94942
--- /dev/null
+++ b/heat-stack/app/global_constants.ts
@@ -0,0 +1 @@
+export const DESIGN_SET_POINT = 70 // Design set point (70°F), defined in external documentation - https://docs.google.com/document/d/16WlqY3ofq4xpalsfwRuYBWMbeUHfXRvbWU69xxVNCGM/edit?tab=t.0
diff --git a/heat-stack/app/routes/_heat+/heatloadanalysis.tsx b/heat-stack/app/routes/_heat+/heatloadanalysis.tsx
index 5360e271..9a603d1c 100644
--- a/heat-stack/app/routes/_heat+/heatloadanalysis.tsx
+++ b/heat-stack/app/routes/_heat+/heatloadanalysis.tsx
@@ -1,5 +1,12 @@
+import React from 'react'
import { Graphs } from '../../components/ui/heat/CaseSummaryComponents/HeatLoadAnalysis.tsx'
-export default function HeatLoadAnalysis() {
- return
+interface HeatLoadAnalysisProps {
+ heatLoadSummaryOutput: any;
+}
+
+export default function HeatLoadAnalysis({
+ heatLoadSummaryOutput,
+}: HeatLoadAnalysisProps) {
+ return
}
diff --git a/heat-stack/app/routes/_heat+/single.tsx b/heat-stack/app/routes/_heat+/single.tsx
index 25be9d32..cd027b5c 100644
--- a/heat-stack/app/routes/_heat+/single.tsx
+++ b/heat-stack/app/routes/_heat+/single.tsx
@@ -58,6 +58,7 @@ import { CurrentHeatingSystem } from '../../components/ui/heat/CaseSummaryCompon
import { EnergyUseHistory } from '../../components/ui/heat/CaseSummaryComponents/EnergyUseHistory.tsx'
import { HomeInformation } from '../../components/ui/heat/CaseSummaryComponents/HomeInformation.tsx'
import HeatLoadAnalysis from './heatloadanalysis.tsx'
+import React from 'react'
/** Modeled off the conform example at
* https://github.com/epicweb-dev/web-forms/blob/b69e441f5577b91e7df116eba415d4714daacb9d/exercises/03.schema-validation/03.solution.conform-form/app/routes/users%2B/%24username_%2B/notes.%24noteId_.edit.tsx#L48 */
@@ -399,10 +400,10 @@ Traceback (most recent call last): File "", line 32,
const gasBillDataWithUserAdjustments = foo; /* processed_energy_bills is untested here */
- const billingRecords = foo.get('processed_energy_bills')
- billingRecords.forEach((record: any) => {
- record.set('inclusion_override', true);
- });
+ // const billingRecords = foo.get('processed_energy_bills')
+ // billingRecords.forEach((record: any) => {
+ // record.set('inclusion_override', true);
+ // });
// foo.set('processed_energy_bills', null)
// foo.set('processed_energy_bills', billingRecords)
//console.log("(after customization) gasBillDataWithUserAdjustments billing records[0]", gasBillDataWithUserAdjustments.get('processed_energy_bills')[0])
@@ -482,7 +483,10 @@ export default function Inputs() {
// const location = useLocation();
// console.log(`location:`, location); // `.state` is `null`
const lastResult = useActionData()
-
+ const parsedLastResult = hasDataProperty(lastResult)
+ ? JSON.parse(lastResult.data, reviver) as Map: undefined;
+
+ const heatLoadSummaryOutput = parsedLastResult ? Object.fromEntries(parsedLastResult?.get('heat_load_output')) : undefined;
/* @ts-ignore */
// console.log("lastResult (all Rules Engine data)", lastResult !== undefined ? JSON.parse(lastResult.data, reviver): undefined)
@@ -516,8 +520,7 @@ export default function Inputs() {
temp1.get('balance_point_graph').get('records')[0].get('heat_loss_rate')
*//* @ts-ignore */
- // console.log("HeatLoad chart", lastResult !== undefined ? JSON.parse(lastResult.data, reviver)?.get('balance_point_graph')?.get('records'): undefined)
-
+ // console.log("HeatLoad chart", lastResult !== undefined ? JSON.parse(lastResult.data, reviver)?.get('balance_point_graph')?.get('records'): undefined)
type ActionResult =
| SubmissionResult
| { data: string }
@@ -588,7 +591,7 @@ export default function Inputs() {
- {show_usage_data && }
+ {show_usage_data && }
>
)
}