FRONTEND: Add stacked bar chart integration with error bars using Chart.js in ReportChart; include Chart.js dependency and enhance ReportRoute with updated icons
This commit is contained in:
parent
abed6b82e5
commit
849d31bc8e
4 changed files with 240 additions and 11 deletions
19
src/frontend/package-lock.json
generated
19
src/frontend/package-lock.json
generated
|
|
@ -10,6 +10,7 @@
|
|||
"dependencies": {
|
||||
"@phosphor-icons/vue": "^2.2.1",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"loglevel": "^1.9.2",
|
||||
"pinia": "^3.0.3",
|
||||
"vue": "^3.5.18",
|
||||
|
|
@ -973,6 +974,12 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@kurkle/color": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@phosphor-icons/vue": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@phosphor-icons/vue/-/vue-2.2.1.tgz",
|
||||
|
|
@ -1719,6 +1726,18 @@
|
|||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
|
||||
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"dependencies": {
|
||||
"@phosphor-icons/vue": "^2.2.1",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"loglevel": "^1.9.2",
|
||||
"pinia": "^3.0.3",
|
||||
"vue": "^3.5.18",
|
||||
|
|
|
|||
|
|
@ -1,15 +1,216 @@
|
|||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
<template>
|
||||
<div class="chart-container">
|
||||
<canvas ref="stackedBarChart"></canvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReportChart"
|
||||
})
|
||||
<script>
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
BarController,
|
||||
ScatterController,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
PointElement
|
||||
} from 'chart.js';
|
||||
|
||||
// Register the required components
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
BarController,
|
||||
ScatterController,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
PointElement
|
||||
);
|
||||
|
||||
export default {
|
||||
name: "ReportChart",
|
||||
computed: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
data: {
|
||||
labels: ['Sample A'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'MEK A',
|
||||
data: [35],
|
||||
backgroundColor: '#6B869C', // Blue-gray like in your image
|
||||
borderColor: '#6B869C',
|
||||
borderWidth: 1,
|
||||
stack: 'stack1'
|
||||
},
|
||||
{
|
||||
label: 'Logistics costs',
|
||||
data: [15],
|
||||
backgroundColor: '#5AF0B4', // Mint green like in your image
|
||||
borderColor: '#5AF0B4',
|
||||
borderWidth: 1,
|
||||
stack: 'stack1'
|
||||
},
|
||||
{
|
||||
label: 'Error Bars',
|
||||
|
||||
// data: [50, 65, 58, 75], // Total height (used for error bar positioning)
|
||||
type: 'scatter',
|
||||
backgroundColor: 'transparent',
|
||||
borderColor: 'transparent',
|
||||
pointRadius: 0,
|
||||
showLine: false,
|
||||
stack: 'errorBars',
|
||||
errorBars: {
|
||||
plus: [5], // Upper error
|
||||
minus: [4] // Lower error
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const ctx = this.$refs.stackedBarChart.getContext('2d');
|
||||
|
||||
// Sample data
|
||||
const data = this.data;
|
||||
|
||||
const config = {
|
||||
type: 'bar',
|
||||
data: data,
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
display: false //this will remove only the label
|
||||
},
|
||||
border: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
y: {
|
||||
stacked: true, // Don't stack for error bars
|
||||
beginAtZero: true,
|
||||
max: 90,
|
||||
grid: {
|
||||
color: '#E3EDFF'
|
||||
},
|
||||
ticks: {
|
||||
display: false //this will remove only the label
|
||||
},
|
||||
border: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
labels: {
|
||||
filter: function (item, chart) {
|
||||
// Hide the error bars dataset from legend
|
||||
return item.text !== 'Error Bars';
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
filter: function (tooltipItem) {
|
||||
// Don't show tooltip for error bars dataset
|
||||
return tooltipItem.datasetIndex !== 2;
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
onComplete: function () {
|
||||
// Draw error bars after animation completes
|
||||
drawErrorBars(this);
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [{
|
||||
afterDraw: function (chart) {
|
||||
drawErrorBars(chart);
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
function drawErrorBars(chart) {
|
||||
const ctx = chart.ctx;
|
||||
const meta = chart.getDatasetMeta(0); // Get metadata for first dataset
|
||||
const errorData = chart.data.datasets[2].errorBars;
|
||||
|
||||
if (!errorData) return;
|
||||
|
||||
ctx.strokeStyle = '#002F54';
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
meta.data.forEach((bar, index) => {
|
||||
const x = bar.x;
|
||||
|
||||
// Calculate the top of the stacked bar (this is the center point for error bars)
|
||||
const topValue = chart.data.datasets[0].data[index] + chart.data.datasets[1].data[index];
|
||||
const yCenterPixel = chart.scales.y.getPixelForValue(topValue);
|
||||
|
||||
// Error bar measurements
|
||||
const errorUp = errorData.plus[index];
|
||||
const errorDown = errorData.minus[index];
|
||||
|
||||
// Convert error values to pixel distances
|
||||
const errorUpPixels = chart.scales.y.getPixelForValue(topValue) - chart.scales.y.getPixelForValue(topValue + errorUp);
|
||||
const errorDownPixels = chart.scales.y.getPixelForValue(topValue - errorDown) - chart.scales.y.getPixelForValue(topValue);
|
||||
|
||||
const yErrorTop = yCenterPixel - errorUpPixels;
|
||||
const yErrorBottom = yCenterPixel + errorDownPixels;
|
||||
|
||||
// Draw vertical line
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, yErrorTop);
|
||||
ctx.lineTo(x, yErrorBottom);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw top cap
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x - 8, yErrorTop);
|
||||
ctx.lineTo(x + 8, yErrorTop);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw bottom cap
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x - 8, yErrorBottom);
|
||||
ctx.lineTo(x + 8, yErrorBottom);
|
||||
ctx.stroke();
|
||||
});
|
||||
}
|
||||
|
||||
// Create the chart
|
||||
const stackedBarChart = new ChartJS(ctx, config);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
|
||||
.chart-container {
|
||||
position: relative;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
v-if="section.transport_type === 'ROAD' || section.transport_type === 'POST_RUN'"></ph-truck>
|
||||
|
||||
<div>
|
||||
{{ section.from_node.external_mapping_id ?? section.from_node.name }} >
|
||||
{{ section.from_node.external_mapping_id ?? section.from_node.name }} <PhArrowRight :size="12" weight="bold" />
|
||||
{{ section.to_node.external_mapping_id ?? section.to_node.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -40,11 +40,19 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {PhBoat, PhTrain, PhTruck, PhTruckTrailer} from "@phosphor-icons/vue";
|
||||
import {
|
||||
PhArrowRight,
|
||||
PhBoat,
|
||||
PhCaretDoubleRight,
|
||||
PhCaretRight,
|
||||
PhTrain,
|
||||
PhTruck,
|
||||
PhTruckTrailer
|
||||
} from "@phosphor-icons/vue";
|
||||
|
||||
export default {
|
||||
name: 'ReportRoute',
|
||||
components: {PhTruck, PhTruckTrailer, PhTrain, PhBoat},
|
||||
components: {PhArrowRight, PhCaretDoubleRight, PhCaretRight, PhTruck, PhTruckTrailer, PhTrain, PhBoat},
|
||||
props: {
|
||||
sections: {
|
||||
type: Array,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue