Skip to content

Commit

Permalink
Benchmarking (#97)
Browse files Browse the repository at this point in the history
* Updated results, now resultList with precalculated categories and nesting

* Benchmarking added uses new result structure to read result quickly

* Minor usability fixes

* Updated Github actions to be more streamlined in building live page

---------

Co-authored-by: sultanahmed-7bb <[email protected]>
  • Loading branch information
fabianlinkflink and sultanahmed-7bb authored Dec 10, 2024
1 parent eaf800b commit 1af18ad
Show file tree
Hide file tree
Showing 53 changed files with 4,351 additions and 621 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/todo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: TODO Issue
on:
workflow_dispatch:
inputs:
importAll:
default: false
required: false
type: boolean
description: Enable, if you want to import all TODOs. Runs on checked out branch! Only use if you're sure what you are doing.
push:
branches: # do not set multiple branches, todos might be added and then get referenced by themselves in case of a merge
- main

permissions:
issues: write
repository-projects: read
contents: read

jobs:
todos:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Run Issue Bot
uses: juulsn/todo-issue@main
with:
excludePattern: '^(node_modules/)'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
204 changes: 204 additions & 0 deletions src/components/Benchmark/BenchmarkGrid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
<template>
<div class="flex-1 p-6">
<div class="bg-white p-4 shadow-md rounded-md">
<Dropdown
:items="graphParameters"
name="graphParameter"
:dropdownName="dropdownName"
@selectedItem="handleResultListSelection"
class="pb-6"
/>
<ul
role="list"
class="max-h-screen grid grid-cols-1 gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 items-start overflow-auto"
>
<li
v-for="(resultLog, index) in resultLogs"
:key="resultLog.name"
class="col-span-1 flex flex-col divide-y divide-gray-200 rounded-lg bg-white text-center shadow"
>
<!-- Project Iterator. -->
<div class="flex flex-1 flex-col p-8">
<dd class="text-m text text-gray-500">{{ resultLog.name }}</dd>
<div class="h-[20vh]">
<GraphContainer
:resultItem="displayResultList[index]"
/>
</div>
<dd class="mt-3">
<span
class="inline-flex items-center rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20"
>
{{ displayResultList[index].displayName }}
</span>
</dd>
</div>

<!-- Stats. -->
<div>
<div class="-mt-px flex divide-x divide-gray-200">
<div class="flex w-0 flex-1">
<a
class="relative -mr-px inline-flex w-0 flex-1 items-center justify-center gap-x-3 rounded-bl-lg border border-transparent py-4 text-sm font-semibold text-green-900 text-center"
>
{{ aggregatedEmission[index] }} kg-co² / m²
</a>
</div>

<div class="-ml-px flex w-0 flex-1">
<a
class="relative inline-flex w-0 flex-1 items-center justify-center gap-x-3 rounded-br-lg border border-transparent py-4 text-sm font-semibold text-gray-800 text-center"
>
<td
:class="{'text-red-600' : emissionPercentage[index] >= 100, 'text-green-600' : emissionPercentage[index] < 100}"
>
{{ emissionPercentage[index] }}%
</td>
</a>
</div>
</div>
</div>


<!-- Expand Button -->
<button
class="mt-4 w-full rounded bg-green-500 text-white py-2 text-sm font-medium hover:bg-green-600"
@click="toggleExpansion(index)"
>
{{ expanded[index] ? 'Hide Details' : 'Show Details' }}
</button>

<!-- Expandable Data Table -->
<div v-if="expanded[index]" class="overflow-auto max-h-80">
<DataTable :resultItem="displayResultList[index]" />
</div>
</li>
</ul>
</div>
</div>
</template>

<script lang="ts">
import Dropdown from '@/components/Misc/Dropdown.vue'
import GraphContainer from '@/components/Graphs/GraphContainer.vue'
import DataTable from '@/components/Graphs/DataTable.vue'
import { useResultStore } from '@/stores/result'
import { useFirebaseStore } from '@/stores/firebase'
import { useProjectStore } from '@/stores/main'
import { useSettingsStore } from '@/stores/settings'
import { defineComponent, ref, reactive, computed, watch } from 'vue'
import type { ResultItem } from '@/models/result'
import type { ResultsLog } from '@/models/firebase'
import type { dropdownItem } from '@/components/Misc/DropdownMenuItem.vue'
import { getResultLogEmissions, emissionToNumber } from '@/utils/resultUtils'
export default defineComponent({
name: 'BenchmarkGrid',
components: {
Dropdown,
GraphContainer,
DataTable,
},
setup() {
const resultStore = useResultStore()
const firebaseStore = useFirebaseStore()
const projectStore = useProjectStore()
const settingsStore = useSettingsStore()
const resultLogs = ref<ResultsLog[]>([])
const benchmarkParameter = ref<string>('parameters.speckle_type')
// Create a boolean array to track expansion state
const expanded = reactive(resultLogs.value.map(() => false))
// Fetches results from firebase
firebaseStore.fetchResults(projectStore.currProject.id).then((logs) => {
resultLogs.value = logs
})
// Dropdown items from resultList
const graphParameters = ref<dropdownItem[]>(
resultStore.resultList.map((result) => ({
name: result.displayName,
data: JSON.stringify(result), // Passing the entire result as JSON for flexibility
}))
)
// Selected result
const selectedResult = ref<ResultItem | null>(null)
const dropdownName = ref(selectedResult.value ? selectedResult.value.displayName : 'Select a result')
// Handler for selecting a dropdown item
const handleResultListSelection = (selectedItem: dropdownItem) => {
resultStore.setReloadData(true)
projectStore.clearSelectedObjects()
const parsedResult = JSON.parse(selectedItem.data) as ResultItem
benchmarkParameter.value = parsedResult.parameter
}
const aggregatedEmission = computed(() => {
const resultLogEmission = resultLogs.value.map((log: ResultsLog) => {
const emission = getResultLogEmissions(log, benchmarkParameter.value)
return Math.round(emissionToNumber(emission) / (settingsStore.appSettings.area ?? 100))
})
return resultLogEmission
})
const emissionPercentage = computed(() => {
const maxEmission = Math.max(...aggregatedEmission.value)
return aggregatedEmission.value.map((emission) => {
return Math.round((emission / maxEmission) * 100)
})
})
const displayResultList = computed(() => {
return resultLogs.value.map((resultLog) => {
return resultLog.resultList.find((item: ResultItem) => item.parameter === benchmarkParameter.value)
})
})
function toggleExpansion(index) {
expanded[index] = !expanded[index]
}
watch(graphParameters, (newGraphParameters) => {
if (newGraphParameters.length > 0) {
// If selectedResult is null or no longer matches an item in graphParameters, update it
if (
!selectedResult.value ||
!newGraphParameters.some(
(item) => item.name === selectedResult.value?.displayName
)
) {
const parsedResult = JSON.parse(newGraphParameters[0].data) as ResultItem
selectedResult.value = parsedResult
// Update the dropdownName to reflect the new selection
dropdownName.value = parsedResult.displayName
}
} else {
// If graphParameters is empty, reset selectedResult and dropdownName
selectedResult.value = null
dropdownName.value = 'Select a result'
}
})
return {
handleResultListSelection,
toggleExpansion,
expanded,
resultLogs,
aggregatedEmission,
graphParameters,
dropdownName,
displayResultList,
emissionPercentage,
}
},
})
</script>
25 changes: 16 additions & 9 deletions src/components/DetailBar/DetailBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@
import { defineComponent, computed, watch, ref } from 'vue'
import { useProjectStore } from '@/stores/main'
import { useNavigationStore } from '@/stores/navigation'
import { useResultStore } from '@/stores/result'
import OverviewBar from '@/components/DetailBar/OverviewBar.vue'
import MaterialBar from '@/components/DetailBar/MaterialBar.vue'
import ResultsBar from '@/components/DetailBar/ResultsBar.vue'
import StackedBarChart from '@/components/Graphs/StackedBarChart.vue'
import { geometryToLCSChartData } from '@/utils/resultUtils'
import { geometryToChartData } from '@/utils/resultUtils'
import type { GeometryObject } from '@/models/geometryObject'
import type { ChartData } from '@/models/chartModels'
export default defineComponent({
name: 'DetailBar',
Expand All @@ -33,7 +35,8 @@ export default defineComponent({
},
setup() {
const navStore = useNavigationStore()
const projStore = useProjectStore()
const projectStore = useProjectStore()
const resultStore = useResultStore()
// Total value to be shown for each group
const currDetailbar = computed(() => {
if (navStore.activePage === 'Overview') return OverviewBar
Expand All @@ -48,26 +51,30 @@ export default defineComponent({
// Pass props to the current detail bar component
const updateComponentProps = () => {
if (currDetailbar.value === StackedBarChart) {
let geos: GeometryObject[]
if (projStore.selectedObjects.length > 0) {
geos = projStore.selectedObjects
let data: ChartData[] = []
if (projectStore.selectedObjects.length > 0) {
data = geometryToChartData(projectStore.selectedObjects, resultStore.activeParameter, true)
} else {
geos = projStore.currProject.geometry
data = geometryToChartData(projectStore.currProject.geometry, resultStore.activeParameter, true)
}
componentProps.value = {
data: geometryToLCSChartData(geos),
data: data,
}
}
else {
componentProps.value = {}
}
}
watch(() => projStore.selectedObjects, () => {
watch(() => projectStore.selectedObjects, () => {
updateComponentProps()
})
watch(() => resultStore.activeParameter, () => {
updateComponentProps()
})
updateComponentProps()
return {
Expand Down
87 changes: 87 additions & 0 deletions src/components/Graphs/DataTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<template>
<!-- Table -->
<table class="divide-y divide-gray-200 max-w-full block table-fixed">
<thead class="w-full block">
<tr class="w-full flex bg-gray-200 text-gray-700 text-left text-xs leading-4 font-medium uppercase tracking-wider whitespace-nowrap">
<th class="m-3 w-3/6">
{{ resultItem.displayName }}
</th>
<th class="m-3 w-1/6">
Amount
</th>
<th class="m-3 w-2/6">
Emission
</th>
</tr>
</thead>
<tr
v-for="(groupedResult) in resultItem.data"
:key="groupedResult.parameter"
class="text-xs whitespace-no-wrap w-full flex hover:bg-gray-200"
>
<td scope="row" class="m-2 w-3/6 line-clamp-3">
{{ groupedResult.parameter }}
</td>
<td class="m-2 w-1/6">
{{ getAmountWithUnit(groupedResult) }}
</td>
<!-- Arbitrary numbers for red vs green, this needs to be threshold or dynamic and-->
<td
:class="{'text-red-600' : getRoundedEmissions(groupedResult) >= 100, 'text-green-600' : getRoundedEmissions(groupedResult) < 100 }"
class="m-2 w-2/6"
>
{{ getRoundedEmissions(groupedResult) }}
<br>
kg/CO<sub>2</sub>
</td>
</tr>
</table>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { emissionToNumber } from '@/utils/resultUtils'
import type { PropType } from 'vue'
import type { ResultItem, GroupedResults } from '@/models/result'
export default defineComponent({
name: 'ResultTable',
components: {
},
props: {
resultItem: {
type: Object as PropType<ResultItem>,
required: true,
},
},
methods: {
/**
* Return formated and rounded amount of the grouped result
* Order is m3 -> m2 -> m -> pcs
* @param groupedResult
*/
getAmountWithUnit(groupedResult: GroupedResults) {
const quantity = groupedResult.quantity;
if (quantity?.m3) return `${Math.round(quantity.m3 * 1e2) / 1e2 } m3`
if (quantity?.m2) return `${Math.round(quantity.m2 * 1e2) / 1e2 } m2`
if (quantity?.m) return `${Math.round(quantity.m * 1e2) / 1e2 } m`
if (quantity?.pcs) return `${quantity.pcs} pcs`
return '0'
},
/**
* Gets rounded emission for a grouped result
* TODO: This should be a threshold or dynamic and atleast noramlized to sqm
* @param groupedResult
*/
getRoundedEmissions(groupedResult: GroupedResults) {
return Math.round(emissionToNumber(groupedResult.data.emission) * 1e2) / 1e2
},
},
setup() {
return {
}
},
})
</script>
Loading

0 comments on commit 1af18ad

Please sign in to comment.