Skip to content
This repository has been archived by the owner on Sep 4, 2024. It is now read-only.

Commit

Permalink
Feat: Added Sidebar for creating charts
Browse files Browse the repository at this point in the history
  • Loading branch information
MeisterSeSe committed Jan 26, 2024
1 parent b6c9f95 commit ac3cd10
Show file tree
Hide file tree
Showing 3 changed files with 393 additions and 0 deletions.
169 changes: 169 additions & 0 deletions frontendVue3/src/classes/ChartsSideMenuFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@

// Returns a Factlabel object with empty values
// The object itself contains metadata, metrics and analysis properties,
// each as an array of FactLabelEntries.
// Each entry a JSON and should look like:
// {
// "name": string,
// "description": string,
// "parent": string,
// "level": int,
// "value": string || [string],
// "size": int,
// "ratio": double
// }
export function getEmptyFactLabel() {
return facts;
}
const facts = {
"metrics": [
{
"name": "Features",
"description": "Set of features in the feature model.",
"parent": null,
"level": 0,
},
{
"name": "NumberOfFeatures",
"description": "Number of Features",
"parent": "Features",
"level": 1,
},
{
"name": "Number of Leaf Features",
"description": "Number of Leaf Features",
"parent": "Features",
"level": 1,
},
{
"name": "Number of Top Features",
"description": "Number of Top Features",
"parent": "Features",
"level": 1,
},
{
"name": "Tree relationships",
"description": "Number of relationships (edges) of the feature model.",
"parent": null,
"level": 0,
},
{
"name": "Ratio optional Features",
"description": "Ratio of optional features ",
"parent": "Tree relationships",
"level": 1,
},
{
"name": "Cyclomatic Complexity",
"description": "Cyclomatic Complexity",
"parent": null,
"level": 0,
},
{
"name": "Simple Cyclomatic Complexity",
"description": "Simple Cyclomatic Complexity",
"parent": "Cyclomatic Complexity",
"level": 1,
},
{
"name": "Independent Cyclomatic Complexity",
"description": "Independent Cyclomatic Complexity",
"parent": "Cyclomatic Complexity",
"level": 1,
},
{
"name": "Depth of tree",
"description": "Number of features of the longest path from the root to the leaf features.",
"parent": null,
"level": 0,
},
{
"name": "Tree Depth",
"description": "Number of features of the longest path from the root to the leaf features.",
"parent": "Depth of tree",
"level": 1,
},
{
"name": "Average Number of Children",
"description": "Average Number of Children per feature",
"parent": "Depth of tree",
"level": 1,
},
{
"name": "Clauses",
"description": "Metrics for clause representation per model",
"parent": null,
"level": 0,
},
{
"name": "Number of Clauses",
"description": "Number of Clauses for represantation",
"parent": "Clauses",
"level": 1,
},
{
"name": "Number of literals",
"description": "Number of Literals for represantation",
"parent": "Clauses",
"level": 1,
},
{
"name": "Clause Density",
"description": "Maximal number of children per feature.",
"parent": "Clauses",
"level": 1,
},
{
"name": "Cross-tree constraints",
"description": "Textual cross-tree constraints.",
"parent": null,
"level": 0,
},
{
"name": "Number of Constraints",
"description": "Number of Constraints per model.",
"parent": "Cross-tree constraints",
"level": 1,
},
{
"name": "Average Constraints Size",
"description": "Average number of features involved in a constraint",
"parent": "Cross-tree constraints",
"level": 1,
},
{
"name": "Cross-tree constraints density",
"description": "Cross Tree constraints per model",
"parent": "Cross-tree constraints",
"level": 1,
},
{
"name": "Features in constraints Density",
"description": "Features involved in cross-tree constraints. The ratio to the total number of features is called 'Extra constraint representativeness (ECR)'.",
"parent": "Cross-tree constraints",
"level": 1,
},
],
"analysis": [
{
"name": "Core features",
"description": "Features that are part of all the configurations (aka 'common features').",
"parent": null,
"level": 0,
},
{
"name": "Dead features",
"description": "Features that cannot appear in any configuration.",
"parent": null,
"level": 0,
},
{
"name": "Configurations",
"description": "Number of valid configurations represented by the feature model. If <= is shown, the number represents an upper estimation bound.",
"parent": null,
"level": 0,
}
]
}


220 changes: 220 additions & 0 deletions frontendVue3/src/components/ChartsSideMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
<template>
<v-card variant="outlined">

<v-divider class="border-opacity-100"></v-divider>
<v-divider class="border-opacity-100" thickness="10"></v-divider>
<!-- Data Table for Metrics: -->
<v-data-table v-model:expanded="expandedRoot" :headers="expandableHeaders" :items="showMetrics" item-value="name" item-key="name">
<template v-slot:headers>
</template>
<template v-slot:expanded-row="{ item, columns }">
<tr>
<td :colspan="columns.length">
<v-data-table v-model:expanded="expandedSubs" :headers="expandableHeaders" :items="item.raw.childs"
item-value="name" :expand-on-click="true">
<template v-slot:headers>
</template>
<template v-slot:item.data-table-expand="{ item }">
<td >
<template v-if="item.raw.childs && item.raw.childs.length > 0">
<v-icon icon="mdi-chevron-down"></v-icon>
</template>
</td>
<td>
<v-btn v-if="!item.raw.childs || !item.raw.childs.length > 0" @click="generateChart(item.value)" icon>
<v-icon>mdi-chart-bar</v-icon>
</v-btn>
</td>
</template>
<template v-slot:expanded-row="{ item, columns }">
<template v-if="item.raw.childs && item.raw.childs.length > 0">
<!-- only print when subitems exist-->
<tr>
<td :colspan="columns.length">
<v-data-table :headers="factHeaders" :items="item.raw.childs" item-value="name"
:show-expand="false">

<template v-slot:headers>
</template>
<template v-slot:item="{ item }">
<tr>
<v-btn @click="generateChart(item.value)" icon>
<v-icon>mdi-chart-bar</v-icon>
</v-btn>
<td>
{{item.value}}
</td>
</tr>
</template>
<template v-slot:bottom>
</template>
</v-data-table>

</td>
</tr>
</template>
</template>
<template v-slot:bottom>
</template>
</v-data-table>

</td>


</tr>
</template>
<template v-slot:no-data>
No Metrics available
</template>
<template v-slot:bottom>
</template>
</v-data-table>

<v-divider class="border-opacity-100" thickness="10"></v-divider>
<!-- Data Table for Analysis: -->
<v-data-table :headers="factHeaders" :items="showAnalysis" item-value="name">
<template v-slot:headers>
Core Metrics
</template>
<template v-slot:no-data>
No Analysis available
</template>
<template v-slot:bottom>
</template>
</v-data-table>
<v-divider class="border-opacity-100" thickness="10"></v-divider>
<v-checkbox-btn v-model="hideMissing" label="Hide Missing">
</v-checkbox-btn>
</v-card>
</template>

<script setup>
import {computed, ref, toRefs} from 'vue';
import * as ChartsSideMenuFactory from "@/classes/ChartsSideMenuFactory";
const FM_CHAR_NAME_DESC = "Name";
const FM_CHAR_HREF_DESC = "Reference";
const FM_CHAR_DESC_DESC = "Description";
const EMPTY_VALUE = "";
const facts = ChartsSideMenuFactory.getEmptyFactLabel()
const analysis = ref(facts.analysis)
const metrics = ref(facts.metrics)
const name = ref("");
const fmHref = ref("");
const desc = ref("");
const expandedRoot = ref([]);
const expandedSubs = ref([]);
const expandedSubSubs = ref([]);
const factHeaders = ref([{ key: "name", sortable: false }, { key: "value", sortable: false }]);
const expandableHeaders = ref([{ key: 'data-table-expand' }, { key: "name", sortable: false }, ]);
const hideMissing = ref(false);
const emit = defineEmits(['createChart'])
function generateChart(name){
emit('createChart', name);
}
const showMetrics = computed(() => {
if (hideMissing.value) {
return updateMetrics().filter((entry) => checkEntryIfValueSet(entry));
} else {
return updateMetrics();
}
});
const showAnalysis = computed(() => {
if (hideMissing.value) {
return updateAnalysis().filter((entry) => entry.value !== EMPTY_VALUE);
} else {
return updateAnalysis();
}
});
const updateMetrics = () => {
if (getMaxLevel() > 2) {
console.error("incompatible Metrics, format only supported until depth of 3");
}
let root_entries = getEntriesOnLevel(metrics.value, 0);
let sub_entries = getEntriesOnLevel(metrics.value, 1);
let sub_sub_entries = getEntriesOnLevel(metrics.value, 2);
sortInParent(sub_entries, sub_sub_entries);
sortInParent(root_entries, sub_entries);
return root_entries;
};
const updateAnalysis = () => {
return analysis.value.map((entry) => {
var obj = {};
obj["name"] = entry.name;
obj["value"] = getDisplayValue(entry);
return obj;
});
};
const checkEntryIfValueSet = (entry) => {
if (entry.value !== EMPTY_VALUE) {
return true;
} else if (entry.childs.length === 0) {
return false;
} else {
let someSubValueSet = false;
entry.childs.forEach((child) => {
someSubValueSet = someSubValueSet || checkEntryIfValueSet(child);
});
return someSubValueSet;
}
};
const sortInParent = (parentArray, childArray) => {
childArray.forEach((child) => {
parentArray.forEach((parent) => {
if (child.parent === parent.name) {
parent.childs.push(child);
}
});
});
};
const getEntriesOnLevel = (arr, level) => {
let entries = arr.filter((entry) => entry.level === level);
return entries.map((entry) => {
var obj = {};
obj["name"] = entry.name;
obj["value"] = getDisplayValue(entry);
obj["parent"] = entry.parent;
obj["childs"] = [];
return obj;
});
};
const getMaxLevel = () => {
var maxLevel = 0;
metrics.value.forEach(function (entry, index) {
if (entry.level >= maxLevel) {
maxLevel = entry.level;
}
});
return maxLevel;
};
const getDisplayValue = (entry) => {
try {
var value_str = "";
if (entry.size !== null) {
value_str = "" + entry.size;
if (entry.ratio !== null) {
var rounded = Math.round(100 * entry.ratio);
value_str = value_str + " (" + rounded + ")";
}
} else if (entry.value !== null) {
value_str = entry.value;
}
return value_str
} catch (e) {
console.error(e);
return "Failed to determine which value to display";
}
};
</script>

<style scoped></style>
Loading

0 comments on commit ac3cd10

Please sign in to comment.