Skip to content

Commit

Permalink
Extract utility and data building functions, tool tip and constants
Browse files Browse the repository at this point in the history
  • Loading branch information
derekvmcintire committed Dec 20, 2024
1 parent 0b1ebd9 commit f57e751
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,171 +13,48 @@ import {
} from 'recharts'
import { SummaryOutputSchema } from '../../../../../../types/types'
import { Icon } from '../../../icon'

// Constants for chart styling
const COLOR_ORANGE = '#FF5733'
const COLOR_BLUE = '#8884d8'
const COLOR_GREY = '#999999'
const COLOR_GREY_LIGHT = '#f5f5f5'
const COLOR_WHITE = '#fff'

type ChartGridProps = {
setPoint: number
averageHeatLoad: number
maxHeatLoad: number
}
const ChartGrid = ({
setPoint,
averageHeatLoad,
maxHeatLoad,
}: ChartGridProps) => {
return (
<div className="container mx-auto p-4">
<div className="grid grid-cols-3 gap-4">
{/* Grid Item 1 */}
<div className="flex items-center justify-center border-r-2 border-gray-300 p-6">
<div className="flex flex-col items-center">
<div className="text-gray-500">Set Point</div>
<div className="font-semibold">{`${setPoint} °F`}</div>
</div>
</div>

{/* Grid Item 2 */}
<div className="flex items-center justify-center border-r-2 border-gray-300 p-6">
<div className="flex flex-col items-center">
<div className="text-gray-500">Max Heat Load</div>
<div className="font-semibold">{`${maxHeatLoad} BTU/h`}</div>
</div>
</div>

{/* Grid Item 3 */}
<div className="flex items-center justify-center p-6">
<div className="flex flex-col items-center">
<div className="text-gray-500">Average Heat Load</div>
<div className="font-semibold">{`${averageHeatLoad} BTU/h`}</div>
</div>
</div>
</div>
</div>
)
}
import { HeatLoadGrid } from '../HeatLoadGrid'
import {
COLOR_GREY_LIGHT,
COLOR_WHITE,
COLOR_ORANGE,
COLOR_BLUE,
} from '../constants'
import {
calculateAvgHeatLoad,
calculateMaxHeatLoad,
} from '../utility/heat-load-calculations'
import { buildHeatLoadGraphData } from '../utility/build-heat-load-graph-data'

type HeatLoadProps = {
heatLoadSummaryOutput: SummaryOutputSchema
}

/**
* HeatLoad component renders a chart that displays the heating system demand based on different temperature values.
* The chart includes two lines representing maximum and average heat load, along with scatter points at design temperatures.
* @param {HeatLoadProps} props - The props containing the heat load data to render the chart.
* 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) {
console.log('heatLoadSummaryOutput: ', heatLoadSummaryOutput)
const designSetPoint = 70 // Design set point is 70°F as described in this document: https://docs.google.com/document/d/16WlqY3ofq4xpalsfwRuYBWMbeUHfXRvbWU69xxVNCGM/edit?tab=t.0
const {
whole_home_heat_loss_rate,
average_indoor_temperature,
estimated_balance_point,
design_temperature,
} = heatLoadSummaryOutput

/**
* Calculates the maximum heat load for a given temperature.
* The formula used is based on the design set point, the provided temperature, and the whole home heat loss rate.
* If the result is negative, it returns 0 to avoid negative heat loads.
*
* @param {number} temperature - The outdoor temperature at which to calculate the heat load.
* @returns {number} - The calculated maximum heat load in BTU/h for the given temperature.
*/
const getMaxHeatLoadForTemperature = (temperature: number): number =>
Math.round(
Math.max(0, (designSetPoint - temperature) * whole_home_heat_loss_rate),
)
const designSetPoint = 70 // Design set point (70°F), defined in external documentation
const { design_temperature } = heatLoadSummaryOutput

/**
* Calculates the average heat load for a given temperature considering internal and solar gain.
* The formula incorporates the design set point, average indoor temperature, the estimated balance point,
* and the outdoor temperature to compute the average heat load.
* If the result is negative, it returns 0 to avoid negative heat loads.
* useMemo hook to calculate the heat load data for the maximum and average lines.
* This data is built from the heat load summary and design temperature.
*
* @param {number} temperature - The outdoor temperature at which to calculate the heat load.
* @returns {number} - The calculated average heat load in BTU/h for the given temperature.
*/
const getAvgHeatLoadForTemperature = (temperature: number): number =>
Math.round(
Math.max(
0,
(designSetPoint -
average_indoor_temperature +
estimated_balance_point -
temperature) *
whole_home_heat_loss_rate,
),
)

/**
* useMemo hook to 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
* using the calculations provided in this document: https://docs.google.com/document/d/16WlqY3ofq4xpalsfwRuYBWMbeUHfXRvbWU69xxVNCGM/edit?tab=t.0
* @returns {Array} - An array of data points for the lines and scatter points.
* @returns {Array} - Array of data points for the lines and scatter points.
*/
const data = useMemo(() => {
const points = []

// Calculate heat load at -10°F from the design temperature (start point)
const startTemperature = design_temperature - 10
const avgHeatLoadStart = getAvgHeatLoadForTemperature(startTemperature)
const maxHeatLoadStart = getMaxHeatLoadForTemperature(startTemperature)

// Calculate heat load at design temperature
const avgHeatLoad = getAvgHeatLoadForTemperature(design_temperature)
const maxHeatLoad = getMaxHeatLoadForTemperature(design_temperature)

// Calculate heat load at design set point (70°F)
const avgHeatLoadSetPoint = getAvgHeatLoadForTemperature(designSetPoint)
const maxHeatLoadSetPoint = getMaxHeatLoadForTemperature(designSetPoint)

// point for avg line at start
points.push({
temperature: startTemperature,
avgLine: avgHeatLoadStart,
})
// point for avg line at design temperature
points.push({
temperature: design_temperature,
avgLine: avgHeatLoad,
avgPoint: avgHeatLoad,
})
// point for avg line at design set point
points.push({
temperature: designSetPoint,
avgLine: avgHeatLoadSetPoint,
})

// Add the point for max line at start temperature
points.push({
temperature: startTemperature,
maxLine: maxHeatLoadStart,
})
// Add the point for max line at design temperature
points.push({
temperature: design_temperature,
maxLine: maxHeatLoad,
maxPoint: maxHeatLoad,
})
// Add the point for max line at design set point
points.push({
temperature: designSetPoint,
maxLine: maxHeatLoadSetPoint,
})

return points
return buildHeatLoadGraphData(heatLoadSummaryOutput, designSetPoint)
}, [heatLoadSummaryOutput])

/**
* useMemo hook to calculate the minimum Y-axis value based on the data.
* It includes a buffer to ensure the chart has some space below the minimum heat load value.
* useMemo hook to calculate the minimum Y-axis value.
* Includes a 20% buffer below the minimum heat load value to ensure some space in the chart.
*
* @returns {number} - The calculated minimum Y-axis value.
*/
const minYValue = useMemo(() => {
Expand All @@ -186,38 +63,40 @@ export function HeatLoad({ heatLoadSummaryOutput }: HeatLoadProps) {
Math.min(point.maxLine || Infinity, point.avgLine || Infinity),
),
)
return Math.max(0, Math.floor((minValue * 0.8) / 10000) * 10000) // Add 20% buffer below min Y value
return Math.max(0, Math.floor((minValue * 0.8) / 10000) * 10000) // 20% buffer
}, [data])

/**
* useMemo hook to calculate the maximum Y-axis value based on the data.
* It includes a 30% buffer to ensure the chart has some space above the maximum heat load value
* which is important so the line doesn't intersect with the legend
* useMemo hook to calculate the maximum Y-axis value.
* Includes a 30% buffer above the maximum heat load value to ensure space above the line.
*
* @returns {number} - The calculated maximum Y-axis value.
*/
const maxYValue = useMemo(() => {
const maxValue = Math.max(
...data.map((point) => Math.max(point.maxLine || 0, point.avgLine || 0)),
)
return Math.ceil((maxValue * 1.3) / 10000) * 10000
return Math.ceil((maxValue * 1.3) / 10000) * 10000 // 30% buffer
}, [data])

/**
* useMemo hook to calculate the minimum X-axis value.
* This is set to 10°F below the design temperature to allow some space before the design temperature.
* Set 10°F below the design temperature to provide some space before the design point.
*
* @returns {number} - The calculated minimum X-axis value.
*/
const minXValue = useMemo(() => design_temperature - 10, [design_temperature])

/**
* useMemo hook to calculate the maximum X-axis value.
* This is set to the design set point (70°F).
* Set to the design set point (70°F) to end the chart at the design temperature.
*
* @returns {number} - The calculated maximum X-axis value.
*/
const maxXValue = useMemo(() => designSetPoint, [designSetPoint]) // End at the design set point
const maxXValue = useMemo(() => designSetPoint, [designSetPoint])

return (
<div className="rounded-lg shadow-lg min-w-[625px]">
<div className="min-w-[625px] rounded-lg shadow-lg">
<span className="mb-4 text-lg font-semibold">
Heating System Demand <Icon name="question-mark-circled" size="md" />{' '}
</span>
Expand All @@ -239,7 +118,7 @@ export function HeatLoad({ heatLoadSummaryOutput }: HeatLoadProps) {
dataKey="temperature"
name="Outdoor Temperature"
domain={[minXValue, maxXValue]}
tickCount={maxXValue - minXValue + 1} // Ensure whole numbers
tickCount={maxXValue - minXValue + 1} // Ensure whole number ticks
>
<Label
value="Outdoor Temperature (°F)"
Expand All @@ -248,11 +127,7 @@ export function HeatLoad({ heatLoadSummaryOutput }: HeatLoadProps) {
/>
</XAxis>

<YAxis
type="number"
name="Heat Load"
domain={[minYValue, maxYValue]}
>
<YAxis type="number" name="Heat Load" domain={[minYValue, maxYValue]}>
<Label
value="Heat Load (BTU/h)"
position="left"
Expand All @@ -268,7 +143,7 @@ export function HeatLoad({ heatLoadSummaryOutput }: HeatLoadProps) {
: null

if (temperature !== null) {
// Return formatted output, ensuring the temperature is shown in color below the heat load value
// Format tooltip with temperature and heat load values
return [
`${Number(value).toLocaleString()} BTU/h`, // Heat load in BTU/h
`${temperature}°F ${name.replace('Line', ' Heat Load').replace('Point', ' at Design Temperature')}`, // Temperature in °F below the heat load value
Expand Down Expand Up @@ -334,10 +209,18 @@ export function HeatLoad({ heatLoadSummaryOutput }: HeatLoadProps) {
/>
</ComposedChart>
</ResponsiveContainer>
<ChartGrid
<HeatLoadGrid
setPoint={designSetPoint}
averageHeatLoad={getAvgHeatLoadForTemperature(design_temperature)}
maxHeatLoad={getMaxHeatLoadForTemperature(design_temperature)}
averageHeatLoad={calculateAvgHeatLoad(
heatLoadSummaryOutput,
design_temperature,
designSetPoint,
)}
maxHeatLoad={calculateMaxHeatLoad(
heatLoadSummaryOutput,
design_temperature,
designSetPoint,
)}
/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react'

/**
* 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: any): JSX.Element => {
const { payload } = props
const temperature = payload ? payload?.temperature : null
const value = payload ? payload?.value : null
const name = payload ? payload?.name : ''

if (temperature !== null) {
// Return formatted output, ensuring the temperature is shown in color below the heat load value
return (
<div className="tooltip-content">
<div>{`${Number(value).toLocaleString()} BTU/h`}</div>
<div>{`${temperature}°F ${name.replace('Line', ' Heat Load').replace('Point', ' at Design Temperature')}`}</div>
</div>
)
}

// Fallback in case the temperature is not available
return (
<div className="tooltip-content">
<div>{`${Number(value).toLocaleString()} BTU/h`}</div>
<div>
{name
.replace('Line', ' Heat Load')
.replace('Point', ' at Design Temperature')}
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -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 {ChartGridProps} 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,
}: ChartGridProps) => {
return (
<div className="container mx-auto p-4">
<div className="grid grid-cols-3 gap-4">
{/* Grid Item 1 */}
<div className="flex items-center justify-center border-r-2 border-gray-300 p-6">
<div className="flex flex-col items-center">
<div className="text-gray-500">Set Point</div>
<div className="font-semibold">{`${setPoint} °F`}</div>
</div>
</div>

{/* Grid Item 2 */}
<div className="flex items-center justify-center border-r-2 border-gray-300 p-6">
<div className="flex flex-col items-center">
<div className="text-gray-500">Max Heat Load</div>
<div className="font-semibold">{`${maxHeatLoad} BTU/h`}</div>
</div>
</div>

{/* Grid Item 3 */}
<div className="flex items-center justify-center p-6">
<div className="flex flex-col items-center">
<div className="text-gray-500">Average Heat Load</div>
<div className="font-semibold">{`${averageHeatLoad} BTU/h`}</div>
</div>
</div>
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -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 = '#f5f5f5'
export const COLOR_WHITE = '#fff'
Loading

0 comments on commit f57e751

Please sign in to comment.