Ekaropolus 8ed81f89e8
All checks were successful
continuous-integration/drone/push Build is passing
Add virtual dashboard for vehicles
2025-05-21 20:25:59 -06:00

145 lines
4.6 KiB
HTML

{% extends "pxy_dashboard/partials/base.html" %}
{% load static %}
{% block title %}Urban Digital Twin{% endblock %}
{% block content %}
{% include "pxy_dashboard/partials/dashboard/kpi_row.html" %}
<div class="row mb-4">
<div class="col-md-4">
<label for="subdivisionSelect" class="form-label">Selecciona una subdivisión:</label>
<select id="subdivisionSelect" class="form-select">
{% for s in subdivisions %}
<option value="{{ s }}" {% if s == selected_subdivision %}selected{% endif %}>{{ s }}</option>
{% endfor %}
</select>
</div>
</div>
<!-- Column Chart -->
<div class="card mb-4">
<div class="card-header"><h5 class="mb-0">Column Chart: Cuartiles por Fecha y Vehículo</h5></div>
<div class="card-body">
<div id="chart-bar"></div>
</div>
</div>
<!-- Heatmap -->
<div class="card mb-4">
<div class="card-header"><h5 class="mb-0">Heatmap: Distribución de Cuartiles</h5></div>
<div class="card-body">
<div id="chart-heatmap"></div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
// Datos desde Django
const rawData = {{ scatter_data_json|safe }};
const vehicles = {{ vehicle_categories|safe }};
const subdivision = "{{ selected_subdivision|escapejs }}";
// Escala de color para z
const colorScale = {1:"#FF4136",2:"#FF851B",3:"#FFDC00",4:"#2ECC40",5:"#0074D9"};
// Fechas únicas y ordenadas
const dates = [...new Set(rawData.map(p => p.x))].sort();
// Función para extraer solo dígitos del string "Vehículo X"
function extractVehicleId(label) {
const m = label.match(/\d+/);
return m ? m[0] : label;
}
// --- Column Chart Series ---
const seriesBar = vehicles.map(veh => ({
name: veh,
data: dates.map(d => {
const rec = rawData.find(p => p.x === d && p.y === veh);
return rec ? rec.z : 0;
})
}));
// Render Column Chart con click
new ApexCharts(document.querySelector("#chart-bar"), {
chart: {
type: 'bar',
height: 350,
events: {
dataPointSelection: function(_, __, { seriesIndex, dataPointIndex }) {
const vehLabel = vehicles[seriesIndex];
const vehId = extractVehicleId(vehLabel);
const url = `/city/virtual/reality/digital/twin/waste/${encodeURIComponent(subdivision)}/${encodeURIComponent(vehId)}/`;
window.open(url, "_blank");
}
}
},
plotOptions: { bar: { horizontal: false, columnWidth: '50%' } },
dataLabels: { enabled: false },
series: seriesBar,
xaxis: { categories: dates, title: { text: 'Fecha' } },
yaxis: { title: { text: 'Cuartiles únicos' } },
legend: { position: 'bottom' },
title: { text: `Cuartiles únicos por Fecha en ${subdivision}` }
}).render();
// --- Heatmap Series ---
const seriesHeat = vehicles.map(veh => ({
name: veh,
data: dates.map(d => {
const rec = rawData.find(p => p.x === d && p.y === veh);
return { x: d, y: rec ? rec.z : 0 };
})
}));
// Render Heatmap con click
new ApexCharts(document.querySelector("#chart-heatmap"), {
chart: {
type: 'heatmap',
height: 350,
events: {
dataPointSelection: function(_, __, { seriesIndex, dataPointIndex }) {
const vehLabel = vehicles[seriesIndex];
const vehId = extractVehicleId(vehLabel);
const url = `/city/virtual/reality/digital/twin/waste/${encodeURIComponent(subdivision)}/${encodeURIComponent(vehId)}/`;
window.open(url, "_blank");
}
}
},
series: seriesHeat,
plotOptions: {
heatmap: {
shadeIntensity: 0.5,
colorScale: {
ranges: [
{ from: 0, to: 0, name: '0', color: '#f2f2f2' },
{ from: 1, to: 1, name: '1', color: '#FF4136' },
{ from: 2, to: 2, name: '2', color: '#FF851B' },
{ from: 3, to: 3, name: '3', color: '#FFDC00' },
{ from: 4, to: 4, name: '4', color: '#2ECC40' },
{ from: 5, to: 5, name: '5+', color: '#0074D9' }
]
}
}
},
dataLabels: { enabled: false },
xaxis: { type: 'category', categories: dates, title: { text: 'Fecha' } },
yaxis: { title: { text: 'Vehículo' } },
title: { text: `Heatmap de Cuartiles en ${subdivision}` }
}).render();
// Recarga al cambiar subdivisión
document.getElementById("subdivisionSelect")
.addEventListener("change", function(){
const q = new URLSearchParams({ subdivision: this.value }).toString();
location.search = q;
});
});
</script>
{% endblock %}