Skip to content

Commit

Permalink
Use correct data and make chart pretty
Browse files Browse the repository at this point in the history
  • Loading branch information
derekvmcintire committed Dec 18, 2024
1 parent f27f962 commit bccf96c
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 96 deletions.
Original file line number Diff line number Diff line change
@@ -1,45 +1,205 @@
import React from 'react'
import React, { useMemo } from 'react';
import {
ScatterChart,
Scatter,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from 'recharts'
import { HeatLoadGraphRecordSchema } from '../../../../../../types/types'
ComposedChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Scatter,
ResponsiveContainer,
Legend,
Label,
} from 'recharts';
import { SummaryOutputSchema } from '../../../../../../types/types';

const COLOR_ORANGE = "#FF5733 ";
const COLOR_BLUE = "#8884d8";
const COLOR_GREY_DARK = "#d5d5d5";
const COLOR_GREY_LIGHT = "#f5f5f5";
const COLOR_WHITE = "#fff";

interface ChartDataPoint {
temperature: number; // Temperature value for the X-axis
maxLine?: number; // Max heat load line value
avgLine?: number; // Average heat load line value
maxPoint?: number; // Max heat load at design temperature (scatter point)
avgPoint?: number; // Average heat load at design temperature (scatter point)
}

interface HeatLoadProps {
data: HeatLoadGraphRecordSchema[]
heatLoadSummaryOutput: SummaryOutputSchema;
}

export function HeatLoad({ data }: HeatLoadProps) {
return (
<div>
<div className="item-title">Heating System Demand</div>

<ResponsiveContainer width="100%" height={400}>
<ScatterChart
margin={{
top: 20,
right: 20,
bottom: 20,
left: 100,
}}
>
<CartesianGrid />
<XAxis
type="number"
dataKey="balance_point"
name=" Outdoor Temperature"
unit="°F"
/>
<YAxis type="number" dataKey="heat_loss_rate" name=" Heat Load" unit=" BTU/h" />
<Tooltip cursor={{ strokeDasharray: '3 3' }} />
<Scatter name="Heat Load" data={data} fill="#8884d8" />
</ScatterChart>
</ResponsiveContainer>
</div>
)
export function HeatLoad({ heatLoadSummaryOutput }: HeatLoadProps) {
// Generate the data points for the lines and scatter points
const data = useMemo(() => {
const points: ChartDataPoint[] = [];

for (let temp = heatLoadSummaryOutput.design_temperature; temp <= heatLoadSummaryOutput.estimated_balance_point; temp += 2) {
// Calculate the maximum heat load (without internal/solar gain)
const maxHeatLoad = heatLoadSummaryOutput.whole_home_heat_loss_rate * (heatLoadSummaryOutput.average_indoor_temperature - temp);

// Calculate the average heat load (with internal/solar gain)
const avgHeatLoad = heatLoadSummaryOutput.whole_home_heat_loss_rate *
(heatLoadSummaryOutput.average_indoor_temperature - (temp + heatLoadSummaryOutput.difference_between_ti_and_tbp));

// Add the calculated points to the array
points.push({
temperature: temp,
maxLine: Math.max(0, maxHeatLoad),
avgLine: Math.max(0, avgHeatLoad),
});
}

// Add the design temperature points (for the scatter points)
points.push({
temperature: heatLoadSummaryOutput.design_temperature,
maxPoint: heatLoadSummaryOutput.maximum_heat_load,
avgPoint: heatLoadSummaryOutput.average_heat_load,
});

return points;
}, [heatLoadSummaryOutput]);

// Calculate the minimum Y value, ensuring it doesn't go below 0
const minYValue = useMemo(() => {
const minValue = Math.min(
...data.map(point => Math.min(
point.maxLine || Infinity,
point.avgLine || Infinity,
point.maxPoint || Infinity,
point.avgPoint || Infinity
))
);
return Math.max(0, Math.floor(minValue / 10000) * 10000); // Round down to the nearest 10,000
}, [data]);

// Calculate the maximum Y value with extra headroom (to add space above the maximum value)
const maxYValue = useMemo(() => {
const maxValue = Math.max(
...data.map(point => Math.max(
point.maxLine || 0,
point.avgLine || 0,
point.maxPoint || 0,
point.avgPoint || 0
))
);

// Add 10% headroom to the max value (rounded up to the nearest 10,000)
const headroom = Math.ceil(maxValue * 0.1 / 10000) * 10000;

return (Math.ceil(maxValue / 10000) * 10000) + headroom;
}, [data]);

return (
<div>
<div className="text-lg font-semibold mb-4">Heating System Demand</div>

<ResponsiveContainer width="100%" height={400}>
<ComposedChart
margin={{
top: 20,
right: 20,
bottom: 50,
left: 100,
}}
data={data}
>
{/* Grid lines for background */}
<CartesianGrid stroke={COLOR_GREY_LIGHT} />

{/* X-Axis: Outdoor Temperature */}
<XAxis
type="number"
dataKey="temperature"
name="Outdoor Temperature"
unit="°F"
domain={['dataMin - 2', 'dataMax']}
>
<Label
value="Outdoor Temperature (°F)"
position="bottom"
offset={20}
/>
</XAxis>

{/* Y-Axis: Heat Load (with dynamic range) */}
<YAxis
type="number"
name="Heat Load"
unit=" BTU/h"
domain={[() => minYValue, () => maxYValue]}
>
<Label
value="Heat Load (BTU/h)"
position="left"
angle={-90}
offset={30}
/>
</YAxis>

{/* Tooltip for displaying data on hover */}
<Tooltip
formatter={(value: any, name: string) => [
`${Number(value).toLocaleString()} BTU/h`,
name.replace('Line', ' Heat Load').replace('Point', ' at Design Temperature')
]}
/>

{/* Legend for chart */}
<Legend
wrapperStyle={{
backgroundColor: COLOR_WHITE,
border: `1px solid ${COLOR_GREY_DARK}`,
borderRadius: '3px',
padding: '15px'
}}
align="right"
verticalAlign="top"
layout="middle"
formatter={(value: string) => value.replace('Line', ' Heat Load').replace('Point', ' at Design Temperature')}
/>

{/* Line for maximum heat load */}
<Line
type="monotone"
dataKey="maxLine"
stroke={COLOR_ORANGE}
dot={false}
name="Maximum, no internal or solar gain"
/>

{/* Line for average heat load */}
<Line
type="monotone"
dataKey="avgLine"
stroke={COLOR_BLUE}
dot={false}
name="Average, with internal & solar gain"
/>

{/* Scatter point for maximum heat load at design temperature */}
<Scatter
dataKey="maxPoint"
fill={COLOR_ORANGE}
name="Maximum at design temperature"
shape="diamond"
legendType="diamond"
/>

{/* Scatter point for average heat load at design temperature */}
<Scatter
dataKey="avgPoint"
fill={COLOR_BLUE}
name="Average at design temperature"
shape="diamond"
legendType="diamond"
/>
</ComposedChart>
</ResponsiveContainer>
</div>
);
}

export default HeatLoad;
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
import React from 'react'
import { HeatLoad } from './Graphs/HeatLoad.tsx'
import { WholeHomeUAComparison } from './Graphs/WholeHomeUAComparison.tsx'
import { HeatLoadGraphRecordSchema } from '../../../../../types/types.ts'

interface GraphsProps {
heatLoadData: HeatLoadGraphRecordSchema[]
heatLoadSummaryOutput: any;
}

export function Graphs({ heatLoadData }: GraphsProps) {
export function Graphs({ heatLoadSummaryOutput }: GraphsProps) {
const fuel_type = 'Natural Gas'
const titleClassTailwind = 'text-5xl font-extrabold tracking-wide'
const componentMargin = 'mt-10'
Expand All @@ -20,7 +19,7 @@ export function Graphs({ heatLoadData }: GraphsProps) {
Fuel Type
{fuel_type}
{/* <AnalysisHeader /> */}
<HeatLoad data={heatLoadData} />
<HeatLoad heatLoadSummaryOutput={heatLoadSummaryOutput} />
<WholeHomeUAComparison />
</div>
)
Expand Down
44 changes: 3 additions & 41 deletions heat-stack/app/routes/_heat+/heatloadanalysis.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,12 @@
import React from 'react'
import { Graphs } from '../../components/ui/heat/CaseSummaryComponents/HeatLoadAnalysis.tsx'
import { HeatLoadGraphRecordSchema } from '../../../types/types.ts'

type HeatLoadRecord = Map<
| 'balance_point'
| 'heat_loss_rate'
| 'change_in_heat_loss_rate'
| 'percent_change_in_heat_loss_rate'
| 'standard_deviation',
number
>

interface HeatLoadAnalysisProps {
heatLoadData: HeatLoadRecord[]
}

/**
* Transforms raw heat load data into a schema-compliant format suitable for graphing.
* Each record is mapped to an object adhering to the HeatLoadGraphRecordSchema.
*
* @param {HeatLoadRecord[]} data - Array of heat load records as Maps.
* @returns {HeatLoadGraphRecordSchema[]} Transformed data with `balance_point` and `heat_loss_rate`.
*/
function transformHeatLoadData(
data: HeatLoadRecord[],
): HeatLoadGraphRecordSchema[] {
return data
.map((record) => {
const balancePoint = record.get('balance_point')
const heatLossRate = record.get('heat_loss_rate')
if (
typeof balancePoint === 'number' &&
typeof heatLossRate === 'number'
) {
return {
balance_point: balancePoint,
heat_loss_rate: heatLossRate,
}
}
return null
})
.filter((item): item is HeatLoadGraphRecordSchema => item !== null)
heatLoadSummaryOutput: any;
}

export default function HeatLoadAnalysis({
heatLoadData,
heatLoadSummaryOutput,
}: HeatLoadAnalysisProps) {
return <Graphs heatLoadData={transformHeatLoadData(heatLoadData)} />
return <Graphs heatLoadSummaryOutput={heatLoadSummaryOutput}/>
}
8 changes: 4 additions & 4 deletions heat-stack/app/routes/_heat+/single.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -483,10 +483,10 @@ export default function Inputs() {
// const location = useLocation();
// console.log(`location:`, location); // `.state` is `null`
const lastResult = useActionData<typeof action>()
const heatLoadData = hasDataProperty(lastResult)
? JSON.parse(lastResult.data, reviver)?.get('balance_point_graph')?.get('records')
: undefined;
const parsedLastResult = hasDataProperty(lastResult)
? JSON.parse(lastResult.data, reviver) : 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)
Expand Down Expand Up @@ -591,7 +591,7 @@ export default function Inputs() {
<ErrorList id={form.errorId} errors={form.errors} />
<Button type="submit">Submit</Button>
</Form>
{show_usage_data && <HeatLoadAnalysis heatLoadData={heatLoadData}/> }
{show_usage_data && <HeatLoadAnalysis heatLoadSummaryOutput={heatLoadSummaryOutput} /> }
</>
)
}
6 changes: 0 additions & 6 deletions heat-stack/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,6 @@ export const LocationSchema = z.object({
// inclusionOverride: z.enum(['Include', 'Do not include', 'Include in other analysis']),
// });

// Define the schema for heat load records
export const heatLoadGraphRecordSchema = z.object({
balance_point: z.number(),
heat_loss_rate: z.number(),
})

// Define the schema for balance records
export const balancePointGraphRecordSchema = z.object({
balance_point: z.number(),
Expand Down
2 changes: 0 additions & 2 deletions heat-stack/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
type allProcessedEnergyBillsSchema,
type usageDataSchema,
type naturalGasUsageSchema,
heatLoadGraphRecordSchema
} from './index.ts'


Expand All @@ -18,4 +17,3 @@ export type SummaryOutputSchema = z.infer<typeof summaryOutputSchema>;
export type BillingRecordSchema = z.infer<typeof oneProcessedEnergyBillSchema>;
export type BillingRecordsSchema = z.infer<typeof allProcessedEnergyBillsSchema>;
export type UsageDataSchema = z.infer<typeof usageDataSchema>;
export type HeatLoadGraphRecordSchema = z.infer<typeof heatLoadGraphRecordSchema>;

0 comments on commit bccf96c

Please sign in to comment.