Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Historical Graph to Building Details Over Any Tracked Column #107

Merged
merged 9 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,21 @@ for data processing

Docker is the recommended approach to quickly getting started with local development. Docker helps create a version of the Electrify Chicago website on your computer so you can test out your code before submitting a pull request.

- The recommended installation method for your operating system can be found [here](https://docs.docker.com/install/).
- The recommended installation method for your operating system can be found [here](https://docs.docker.com/install/).
- [Get started with Docker](https://docs.docker.com/get-started/)

### **2. Start Docker**

This command starts server locally. To start it, `cd` into the project directory in your terminal then run the following command:
This command starts server locally. To start it, `cd` into the project directory in your terminal then run the following command:

```bash
docker-compose up
```

**Tip:** Added a new dependency? Once you've updated the `package.json` run
`docker-compose up --build` to rebuild the image, which will re-run the setup steps in the
`Dockerfile`.

Running the above command will result in the following output in your terminal

<details>
Expand Down Expand Up @@ -68,7 +72,7 @@ docker-compose run --rm electrify-chicago yarn lint-fix
### Run Data Processing

1. If you update the raw data CSVs or the data scripts that post-process them (like if you are adding
a new statistical analysis), you need to re-run the data processing.
a new statistical analysis), you need to re-run the data processing.

2. To then process a new CSV file (at `src/data/source/ChicagoEnergyBenchmarking.csv`), you need to run the following command:

Expand Down Expand Up @@ -146,4 +150,3 @@ const BuildingOwnerIds = [
## Deploys

This site deploys automatically via Netlify by running `gridsome build`.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"main": "index.js",
"repository": "[email protected]:vkoves/electrify-chicago.git",
"author": "Viktor Köves <[email protected]>",
"engines" : { "node" : ">=16.00 < 17.0.0" },
"engines": {
"node": ">=16.00 < 17.0.0"
},
"license": "MIT",
"scripts": {
"build": "gridsome build",
Expand All @@ -16,6 +18,7 @@
},
"dependencies": {
"csv-parse": "^5.3.6",
"d3": "^7.9.0",
"gridsome": "^0.7.0",
"leaflet": "~1.7.0",
"leaflet.gridlayer.googlemutant": "^0.14.0",
Expand All @@ -25,6 +28,7 @@
},
"devDependencies": {
"@microsoft/eslint-formatter-sarif": "^3.0.0",
"@types/d3": "^7.4.3",
"@types/leaflet": "^1.9.3",
"@types/sindresorhus__slugify": "^0.9.3",
"@typescript-eslint/eslint-plugin": "^5.56.0",
Expand Down
10 changes: 6 additions & 4 deletions src/common-functions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,16 @@ export interface IBuildingNode { node: IBuilding }
export interface IHistoricData {
ID: string;
DataYear: string;
GrossFloorArea: string;
// The actual data
ChicagoEnergyRating: string;
ENERGYSTARScore: string;
SourceEUI: string;
DistrictSteamUse: string;
ElectricityUse: string;
ENERGYSTARScore: string;
GHGIntensity: string;
GrossFloorArea: string;
NaturalGasUse: string;
DistrictSteamUse: string;
SourceEUI: string;
TotalGHGEmissions: string;
}

/**
Expand Down
132 changes: 132 additions & 0 deletions src/components/BarGraph.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<template>
<div class="bar-graph-cont">
<div
class="label"
v-html="graphTitle"

Check warning

Code scanning / ESLint

disallow use of v-html to prevent XSS attack Warning

'v-html' directive can lead to XSS attack.
/>

<svg><!-- D3 inserts here --></svg>
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import * as d3 from 'd3';

export interface IGraphPoint {
x: number | string;
y: number;
}

/**
* A component that can graph an arbitrary array of numeric data
*/
@Component({
components: {
},
})
export default class BarGraph extends Vue {
@Prop({required: true}) graphTitle!: string;

@Prop({required: true}) graphData!: Array<IGraphPoint>;

@Watch('graphData')
onDataChanged(): void {
this.renderGraph();
}

readonly width = 800;
readonly height = 400;

readonly graphMargins = { top: 30, right: 30, bottom: 50, left: 50 };
readonly barMargin = 0.2;

svg!: d3.Selection<SVGGElement, unknown, HTMLElement, any>;
Dismissed Show dismissed Hide dismissed

mounted(): void {
const outerWidth = this.width + this.graphMargins.left + this.graphMargins.right;
const outerHeight = this.height + this.graphMargins.top + this.graphMargins.bottom;

this.svg = d3
.select("svg")
.attr("width", outerWidth)
.attr("height", outerHeight)
.attr("viewBox", `0 0 ${outerWidth} ${outerHeight}`)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", `translate(${this.graphMargins.left},${this.graphMargins.top})`);

this.renderGraph();
}

renderGraph(): void {
// Empty the SVG
this.svg.html(null);

const g = this.svg.append("g");
Fixed Show fixed Hide fixed

const xVals: Array<string> = this.graphData.map((d) => d.x.toString());
const yVals: Array<number> = this.graphData.map((d) => d.y);

const x = d3
.scaleBand()
.range([0, this.width])
.domain(xVals)
.padding(this.barMargin);

const y = d3
.scaleLinear()
.domain([ 0, d3.max(yVals) as number])
.rangeRound([this.height, 0]);

// Render X axis
this.svg.append("g")
.attr("transform", `translate(0, ${this.height})`)
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");

// Render Y axis
this.svg.append("g")
.call(d3.axisLeft(y));

this.svg.selectAll("mybar")
.data(this.graphData)
.enter()
.append("rect")
.attr("x", (d) => {
return x(d.x.toString() as string) as number;
})
.attr("y", (d) => y(d.y))
.attr("width", x.bandwidth())
.attr("height", (d) => this.height - y(d.y))
.attr("fill", "#69b3a2");
}
}
</script>

<style lang="scss">
.bar-graph-cont {
margin: 1rem 0;

.label {
font-weight: bold;
font-size: 1.25rem;
margin-bottom: 0.5rem;
}

svg {
width: 100%;
border: solid $border-thin $grey-light;
aspect-ratio: 2;
height: auto;
max-width: 50rem;
}

.tick {
font-weight: bold;
font-size: 1rem;
}
}
</style>
8 changes: 4 additions & 4 deletions src/components/HistoricalBuildingDataTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,20 @@
GHG Emissions <span class="unit">metric tons CO<sub>2</sub>e</span>
</th>
<th scope="col">
Source EUI <span class="unit">kBtu / sqft</span>
Source EUI <span class="unit">kBTU / sqft</span>
</th>

<th scope="col">
Electricity Use <span class="unit">kBtu</span>
Electricity Use <span class="unit">kBTU</span>
</th>
<th scope="col">
Natural Gas Use <span class="unit">kBtu</span>
Natural Gas Use <span class="unit">kBTU</span>
</th>
<th
v-if="renderedColumns.includes('DistrictSteamUse')"
scope="col"
>
District Steam Use <span class="unit">kBtu</span>
District Steam Use <span class="unit">kBTU</span>
</th>
</tr>
</thead>
Expand Down
70 changes: 70 additions & 0 deletions src/templates/BuildingDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,40 @@

<HistoricalBuildingDataTable :historic-benchmarks="historicData" />

<form class="graph-controls">
<label for="col-to-graph">Column to Graph</label>
<select
id="col-to-graph"
v-model="colToGraph"
>
<!-- TODO: Make this based on the rendered graph columns -->
<option value="TotalGHGEmissions">
Total GHG Emissions
</option>
<option value="GHGIntensity">
GHG Intensity
</option>
<option value="ElectricityUse">
Electricity Use
</option>
<option value="NaturalGasUse">
Gas Use
</option>
</select>

<button
type="submit"
@click="updateGraph"
>
Update
</button>
</form>

<BarGraph
:graph-data="currGraphData"
:graph-title="currGraphTitle"
/>

<p class="constrained">
<strong>* Note on Rankings:</strong> Rankings and medians are among <em>included</em>
buildings, which are those who reported under the Chicago Energy Benchmarking Ordinance for
Expand Down Expand Up @@ -428,6 +462,7 @@
import { Component, Vue } from 'vue-property-decorator';

import { LatestDataYear } from '../constants/globals.vue';
import BarGraph from '~/components/BarGraph.vue';
import BuildingImage from '~/components/BuildingImage.vue';
import DataSourceFootnote from '~/components/DataSourceFootnote.vue';
import HistoricalBuildingDataTable from '~/components/HistoricalBuildingDataTable.vue';
Expand All @@ -446,6 +481,7 @@
UtilityCosts,
IBuildingBenchmarkStats,
} from '../common-functions.vue';
import { IGraphPoint } from '../components/BarGraph.vue';

@Component<any>({
metaInfo() {
Expand All @@ -454,6 +490,7 @@
};
},
components: {
BarGraph,
BuildingImage,
DataSourceFootnote,
NewTabIcon,
Expand All @@ -469,6 +506,14 @@
},
})
export default class BuildingDetails extends Vue {
// TODO: Move to constant
graphTitles = {
TotalGHGEmissions: 'Total GHG Emissions (metric tons CO<sub>2</sub>e)',
GHGIntensity: 'GHG Intensity (metric tons CO<sub>2</sub>e/sqft)',
ElectricityUse: 'Electricity Use (kBTU)',
NaturalGasUse: 'Natural Gas Use (kBTU)',
};

/** Expose stats to template */
readonly BuildingBenchmarkStats: IBuildingBenchmarkStats = BuildingBenchmarkStats;

Expand All @@ -487,6 +532,13 @@
/** All benchmarks (reported and not) for this building */
historicData!: Array<IHistoricData>;

/** The data we are currently rendering in the historic data graph */
currGraphData?: Array<IGraphPoint> = [];
currGraphTitle?: string = '';

/** The key from the historical data we are graphing */
colToGraph = 'TotalGHGEmissions';

/** A helper to get the current building, but with proper typing */
get building(): IBuilding {
return this.$page.building;
Expand Down Expand Up @@ -533,6 +585,19 @@
created(): void {
this.historicData = this.$page.allBenchmark.edges
.map((nodeObj: { node: IHistoricData }) => nodeObj.node) || [];

this.updateGraph();
}

updateGraph(event?: Event): void {
event?.preventDefault();

this.currGraphData = this.historicData.map((datum: IHistoricData) => ({
x: datum.DataYear,
y: parseFloat((datum as any)[this.colToGraph] as string),

Check warning

Code scanning / ESLint

Disallow the `any` type Warning

Unexpected any. Specify a different type.
}));

this.currGraphTitle = (this.graphTitles as any)[this.colToGraph];

Check warning

Code scanning / ESLint

Disallow the `any` type Warning

Unexpected any. Specify a different type.
}
}
</script>
Expand Down Expand Up @@ -677,6 +742,11 @@
li + li { margin-top: 0.25rem; }
}

.graph-controls {
label { display: block; font-weight: bold; }
select { margin-right: 1rem; }
}

@media (max-width: $mobile-max-width) {
.building-header {
.building-img-cont, .building-header-text { width: 100%; }
Expand Down
Loading
Loading