Compare commits
2 Commits
894dd1d92d
...
4e4f6defde
Author | SHA1 | Date | |
---|---|---|---|
4e4f6defde | |||
8a95ff9ad3 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -29,3 +29,4 @@ db.sqlite3
|
|||||||
pxy_city_digital_twins/__backup__/
|
pxy_city_digital_twins/__backup__/
|
||||||
Dockerfile.dev
|
Dockerfile.dev
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
|
docker-compose.override.yml
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile.dev
|
|
||||||
command: >
|
|
||||||
sh -c "python manage.py migrate &&
|
|
||||||
python manage.py runserver 0.0.0.0:8000"
|
|
||||||
ports:
|
|
||||||
- "8011:8000"
|
|
||||||
volumes:
|
|
||||||
- .:/app
|
|
||||||
- ./staticfiles:/app/staticfiles
|
|
21159
mediafiles/geo_scenarios/combined_with_volumes.csv
Normal file
21159
mediafiles/geo_scenarios/combined_with_volumes.csv
Normal file
File diff suppressed because it is too large
Load Diff
12123
mediafiles/opt_scenarios/all_steps_GEN_ORG_urbana_individual_B.csv
Normal file
12123
mediafiles/opt_scenarios/all_steps_GEN_ORG_urbana_individual_B.csv
Normal file
File diff suppressed because it is too large
Load Diff
@ -15,12 +15,8 @@ sys.path.append(str(BASE_DIR))
|
|||||||
# Core security settings
|
# Core security settings
|
||||||
SECRET_KEY = os.getenv("SECRET_KEY")
|
SECRET_KEY = os.getenv("SECRET_KEY")
|
||||||
DEBUG = os.getenv("DEBUG", "False") == "True"
|
DEBUG = os.getenv("DEBUG", "False") == "True"
|
||||||
#ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split(",")
|
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split(",")
|
||||||
ALLOWED_HOSTS = [
|
|
||||||
'localhost',
|
|
||||||
'127.0.0.1',
|
|
||||||
'.ngrok-free.app',
|
|
||||||
]
|
|
||||||
# Application definition
|
# Application definition
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
# Django built-in apps
|
# Django built-in apps
|
||||||
@ -64,7 +60,7 @@ INSTALLED_APPS = [
|
|||||||
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
||||||
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = os.getenv("SITE_ID", 1)
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
"django.contrib.auth.backends.ModelBackend", # default
|
"django.contrib.auth.backends.ModelBackend", # default
|
||||||
|
@ -1,3 +1,48 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from .models import Country, GeoScenario, OptScenario
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
|
@admin.register(Country)
|
||||||
|
class CountryAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('code', 'name')
|
||||||
|
search_fields = ('code', 'name')
|
||||||
|
ordering = ('name',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(GeoScenario)
|
||||||
|
class GeoScenarioAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'country', 'upload_date', 'geographic_field_name')
|
||||||
|
list_filter = ('country', 'upload_date')
|
||||||
|
search_fields = ('name', 'country__name')
|
||||||
|
readonly_fields = ('upload_date',)
|
||||||
|
fieldsets = (
|
||||||
|
(None, {
|
||||||
|
'fields': (
|
||||||
|
'name',
|
||||||
|
'country',
|
||||||
|
'geographic_field_name',
|
||||||
|
'csv_file',
|
||||||
|
'upload_date',
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(OptScenario)
|
||||||
|
class OptScenarioAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'geo_scenario', 'type_of_waste', 'strategy', 'upload_date')
|
||||||
|
list_filter = ('type_of_waste', 'upload_date')
|
||||||
|
search_fields = ('name', 'geo_scenario__name', 'strategy')
|
||||||
|
readonly_fields = ('upload_date',)
|
||||||
|
fieldsets = (
|
||||||
|
(None, {
|
||||||
|
'fields': (
|
||||||
|
'geo_scenario',
|
||||||
|
'name',
|
||||||
|
'type_of_waste',
|
||||||
|
'strategy',
|
||||||
|
'optimized_csv',
|
||||||
|
'upload_date',
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
58
pxy_dashboard/apps/migrations/0001_initial.py
Normal file
58
pxy_dashboard/apps/migrations/0001_initial.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Generated by Django 5.0.3 on 2025-05-19 00:37
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Country',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('code', models.CharField(max_length=3, unique=True)),
|
||||||
|
('name', models.CharField(max_length=100)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'País',
|
||||||
|
'verbose_name_plural': 'Países',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='GeoScenario',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(help_text='Nombre del escenario geográfico base', max_length=255)),
|
||||||
|
('upload_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('geographic_field_name', models.CharField(help_text='Columna con el identificador geográfico (ej. N_URBANO, PUEBLO)', max_length=100)),
|
||||||
|
('csv_file', models.FileField(upload_to='geo_scenarios/')),
|
||||||
|
('country', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='apps.country')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Escenario Geográfico',
|
||||||
|
'verbose_name_plural': 'Escenarios Geográficos',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OptScenario',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(help_text='Nombre del escenario de optimización', max_length=255)),
|
||||||
|
('type_of_waste', models.CharField(help_text='Tipo de residuo (ej. orgánico, reciclable)', max_length=100)),
|
||||||
|
('strategy', models.CharField(help_text='Método de optimización utilizado', max_length=100)),
|
||||||
|
('upload_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('optimized_csv', models.FileField(upload_to='opt_scenarios/')),
|
||||||
|
('geo_scenario', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opt_scenarios', to='apps.geoscenario')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Escenario de Optimización',
|
||||||
|
'verbose_name_plural': 'Escenarios de Optimización',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
pxy_dashboard/apps/migrations/__init__.py
Normal file
0
pxy_dashboard/apps/migrations/__init__.py
Normal file
@ -1,3 +1,43 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# Create your models here.
|
class Country(models.Model):
|
||||||
|
code = models.CharField(max_length=3, unique=True)
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'País'
|
||||||
|
verbose_name_plural = 'Países'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class GeoScenario(models.Model):
|
||||||
|
name = models.CharField(max_length=255, help_text='Nombre del escenario geográfico base')
|
||||||
|
upload_date = models.DateTimeField(auto_now_add=True)
|
||||||
|
country = models.ForeignKey(Country, on_delete=models.SET_NULL, null=True)
|
||||||
|
geographic_field_name = models.CharField(max_length=100, help_text='Columna con el identificador geográfico (ej. N_URBANO, PUEBLO)')
|
||||||
|
csv_file = models.FileField(upload_to='geo_scenarios/')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Escenario Geográfico'
|
||||||
|
verbose_name_plural = 'Escenarios Geográficos'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} ({self.country})"
|
||||||
|
|
||||||
|
|
||||||
|
class OptScenario(models.Model):
|
||||||
|
geo_scenario = models.ForeignKey(GeoScenario, on_delete=models.CASCADE, related_name='opt_scenarios')
|
||||||
|
name = models.CharField(max_length=255, help_text='Nombre del escenario de optimización')
|
||||||
|
type_of_waste = models.CharField(max_length=100, help_text='Tipo de residuo (ej. orgánico, reciclable)')
|
||||||
|
strategy = models.CharField(max_length=100, help_text='Método de optimización utilizado')
|
||||||
|
upload_date = models.DateTimeField(auto_now_add=True)
|
||||||
|
optimized_csv = models.FileField(upload_to='opt_scenarios/')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Escenario de Optimización'
|
||||||
|
verbose_name_plural = 'Escenarios de Optimización'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} ({self.type_of_waste})"
|
||||||
|
@ -11,7 +11,8 @@ from pxy_dashboard.apps.views import (
|
|||||||
apps_file_manager,
|
apps_file_manager,
|
||||||
|
|
||||||
# New – Waste Collection (Pre-Operation)
|
# New – Waste Collection (Pre-Operation)
|
||||||
apps_zone_definition,
|
#apps_zone_definition,
|
||||||
|
zone_definition_view,
|
||||||
apps_route_optimization,
|
apps_route_optimization,
|
||||||
apps_dispatch_plan,
|
apps_dispatch_plan,
|
||||||
|
|
||||||
@ -51,7 +52,8 @@ urlpatterns = [
|
|||||||
path("file-manager", apps_file_manager, name="file-manager"),
|
path("file-manager", apps_file_manager, name="file-manager"),
|
||||||
|
|
||||||
# Pre-Operation
|
# Pre-Operation
|
||||||
path("zone-definition", apps_zone_definition, name="zone-definition"),
|
#path("zone-definition", apps_zone_definition, name="zone-definition"),
|
||||||
|
path("zone-definition", zone_definition_view, name="zone-definition"),
|
||||||
path("route-optimization", apps_route_optimization, name="route-optimization"),
|
path("route-optimization", apps_route_optimization, name="route-optimization"),
|
||||||
path("dispatch-plan", apps_dispatch_plan, name="dispatch-plan"),
|
path("dispatch-plan", apps_dispatch_plan, name="dispatch-plan"),
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ apps_file_manager = AppsView.as_view(template_name="pxy_dashboard/apps/apps-file
|
|||||||
# ───── Waste Collection Intelligence ─────────────────────────────────────────
|
# ───── Waste Collection Intelligence ─────────────────────────────────────────
|
||||||
|
|
||||||
# Pre-Operation
|
# Pre-Operation
|
||||||
apps_zone_definition = AppsView.as_view(template_name="pxy_dashboard/apps/apps-zone-definition.html")
|
#apps_zone_definition = AppsView.as_view(template_name="pxy_dashboard/apps/apps-zone-definition.html")
|
||||||
apps_route_optimization = AppsView.as_view(template_name="pxy_dashboard/apps/apps-route-optimization.html")
|
apps_route_optimization = AppsView.as_view(template_name="pxy_dashboard/apps/apps-route-optimization.html")
|
||||||
apps_dispatch_plan = AppsView.as_view(template_name="pxy_dashboard/apps/apps-dispatch-plan.html")
|
apps_dispatch_plan = AppsView.as_view(template_name="pxy_dashboard/apps/apps-dispatch-plan.html")
|
||||||
|
|
||||||
@ -49,3 +49,87 @@ apps_logs_limits = AppsView.as_view(template_name="pxy_dashboard/apps/apps-logs-
|
|||||||
apps_config_api = AppsView.as_view(template_name="pxy_dashboard/apps/apps-config-api.html")
|
apps_config_api = AppsView.as_view(template_name="pxy_dashboard/apps/apps-config-api.html")
|
||||||
apps_config_map = AppsView.as_view(template_name="pxy_dashboard/apps/apps-config-map.html")
|
apps_config_map = AppsView.as_view(template_name="pxy_dashboard/apps/apps-config-map.html")
|
||||||
apps_config_collection = AppsView.as_view(template_name="pxy_dashboard/apps/apps-config-collection.html")
|
apps_config_collection = AppsView.as_view(template_name="pxy_dashboard/apps/apps-config-collection.html")
|
||||||
|
|
||||||
|
|
||||||
|
from django.shortcuts import render
|
||||||
|
from .models import GeoScenario
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
def zone_definition_view(request):
|
||||||
|
scenario = GeoScenario.objects.last()
|
||||||
|
chart_data = {}
|
||||||
|
viviendas_data = {}
|
||||||
|
city_options = []
|
||||||
|
scatter_series = {}
|
||||||
|
|
||||||
|
if scenario and scenario.csv_file:
|
||||||
|
df = pd.read_csv(scenario.csv_file.path)
|
||||||
|
df = df.fillna(0)
|
||||||
|
|
||||||
|
# Solo conservar zonas con generación total > 0
|
||||||
|
df = df[df["GEN_TOT"] > 0]
|
||||||
|
|
||||||
|
# Opciones de ciudad
|
||||||
|
city_options = sorted(df["N_URBANO"].dropna().unique())
|
||||||
|
selected_city = request.GET.get("city") or (city_options[0] if city_options else None)
|
||||||
|
|
||||||
|
# Barras por zona (residuos)
|
||||||
|
if selected_city:
|
||||||
|
city_df = df[df["N_URBANO"] == selected_city]
|
||||||
|
grouped = (
|
||||||
|
city_df.groupby("COD_ZONA")[["GEN_ORG", "GEN_INVA", "GEN_RESTO"]]
|
||||||
|
.sum()
|
||||||
|
.reset_index()
|
||||||
|
.sort_values(by="GEN_ORG", ascending=False)
|
||||||
|
)
|
||||||
|
chart_data = {
|
||||||
|
"zones": grouped["COD_ZONA"].astype(str).tolist(),
|
||||||
|
"gen_org": grouped["GEN_ORG"].round(2).tolist(),
|
||||||
|
"gen_inva": grouped["GEN_INVA"].round(2).tolist(),
|
||||||
|
"gen_resto": grouped["GEN_RESTO"].round(2).tolist(),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Barras por zona (viviendas)
|
||||||
|
viviendas_grouped = (
|
||||||
|
city_df.groupby("COD_ZONA")["num_viviendas"]
|
||||||
|
.sum()
|
||||||
|
.reset_index()
|
||||||
|
.sort_values(by="num_viviendas", ascending=False)
|
||||||
|
)
|
||||||
|
viviendas_data = {
|
||||||
|
"zones": viviendas_grouped["COD_ZONA"].astype(str).tolist(),
|
||||||
|
"viviendas": viviendas_grouped["num_viviendas"].astype(int).tolist(),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Dispersión por ciudad
|
||||||
|
scatter_series = {
|
||||||
|
"GEN_ORG": [],
|
||||||
|
"GEN_INVA": [],
|
||||||
|
"GEN_RESTO": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
city_grouped = (
|
||||||
|
df.groupby("N_URBANO")[["num_viviendas", "GEN_ORG", "GEN_INVA", "GEN_RESTO"]]
|
||||||
|
.sum()
|
||||||
|
.reset_index()
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, row in city_grouped.iterrows():
|
||||||
|
viviendas = float(row["num_viviendas"])
|
||||||
|
if viviendas == 0:
|
||||||
|
continue
|
||||||
|
city = row["N_URBANO"]
|
||||||
|
if row["GEN_ORG"] > 0:
|
||||||
|
scatter_series["GEN_ORG"].append({"x": viviendas, "y": float(row["GEN_ORG"]), "city": city})
|
||||||
|
if row["GEN_INVA"] > 0:
|
||||||
|
scatter_series["GEN_INVA"].append({"x": viviendas, "y": float(row["GEN_INVA"]), "city": city})
|
||||||
|
if row["GEN_RESTO"] > 0:
|
||||||
|
scatter_series["GEN_RESTO"].append({"x": viviendas, "y": float(row["GEN_RESTO"]), "city": city})
|
||||||
|
|
||||||
|
return render(request, "pxy_dashboard/apps/apps-zone-definition.html", {
|
||||||
|
"chart_data": chart_data,
|
||||||
|
"viviendas_data": viviendas_data,
|
||||||
|
"scatter_series": scatter_series,
|
||||||
|
"cities": city_options,
|
||||||
|
"selected_city": selected_city,
|
||||||
|
})
|
||||||
|
@ -1,5 +1,122 @@
|
|||||||
{% extends "pxy_dashboard/partials/base.html" %}
|
{% extends "pxy_dashboard/partials/base.html" %}
|
||||||
{% block content %}
|
{% load static %}
|
||||||
<h2>Zone Definition</h2>
|
|
||||||
<p>This is a placeholder page for <code>apps-zone-definition.html</code></p>
|
{% block title %}Zone Definition{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'dashboard/vendor/apexcharts/apexcharts.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% include "pxy_dashboard/partials/dashboard/kpi_row.html" %}
|
||||||
|
<div class="container mt-4">
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h4 class="fw-bold">Pre-Operation · Zone Definition</h4>
|
||||||
|
<form method="get" class="d-flex align-items-center">
|
||||||
|
<label for="city-select" class="me-2 mb-0">Select City:</label>
|
||||||
|
<select name="city" id="city-select" class="form-select" onchange="this.form.submit()">
|
||||||
|
{% for city in cities %}
|
||||||
|
<option value="{{ city }}" {% if city == selected_city %}selected{% endif %}>{{ city }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if chart_data.zones %}
|
||||||
|
<!-- Residuals by Zone -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header"><h5 class="mb-0">Waste Generation by Zone</h5></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="chart-residues"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Housing by Zone -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header"><h5 class="mb-0">Number of Households by Zone</h5></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="chart-housing"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if scatter_series %}
|
||||||
|
<!-- Scatter Chart -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header"><h5 class="mb-0">Scatter: Waste vs Households (All Cities)</h5></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="scatter-plot"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script src="{% static 'dashboard/vendor/apexcharts/apexcharts.min.js' %}"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
{% if chart_data.zones %}
|
||||||
|
// Residue Bar Chart
|
||||||
|
var options1 = {
|
||||||
|
chart: { type: 'bar', stacked: true, height: 350 },
|
||||||
|
series: [
|
||||||
|
{ name: 'Organic', data: {{ chart_data.gen_org|safe }} },
|
||||||
|
{ name: 'Inorganic', data: {{ chart_data.gen_inva|safe }} },
|
||||||
|
{ name: 'Other', data: {{ chart_data.gen_resto|safe }} }
|
||||||
|
],
|
||||||
|
xaxis: { categories: {{ chart_data.zones|safe }} },
|
||||||
|
title: { text: "Waste Generation per Zone" }
|
||||||
|
};
|
||||||
|
new ApexCharts(document.querySelector("#chart-residues"), options1).render();
|
||||||
|
|
||||||
|
// Housing Chart
|
||||||
|
var options2 = {
|
||||||
|
chart: { type: 'bar', height: 350 },
|
||||||
|
series: [{ name: 'Households', data: {{ viviendas_data.viviendas|safe }} }],
|
||||||
|
xaxis: { categories: {{ viviendas_data.zones|safe }} },
|
||||||
|
title: { text: "Number of Households per Zone" }
|
||||||
|
};
|
||||||
|
new ApexCharts(document.querySelector("#chart-housing"), options2).render();
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if scatter_series %}
|
||||||
|
// Scatter Plot
|
||||||
|
var options3 = {
|
||||||
|
chart: { type: 'scatter', height: 400, zoom: { enabled: true } },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Organic',
|
||||||
|
data: {{ scatter_series.GEN_ORG|safe }},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Inorganic',
|
||||||
|
data: {{ scatter_series.GEN_INVA|safe }},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Other',
|
||||||
|
data: {{ scatter_series.GEN_RESTO|safe }},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
xaxis: {
|
||||||
|
title: { text: "Number of Households" }
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
title: { text: "Waste Generated (kg/day)" }
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
custom: function({ series, seriesIndex, dataPointIndex, w }) {
|
||||||
|
var d = w.config.series[seriesIndex].data[dataPointIndex];
|
||||||
|
return '<div class="p-2"><strong>' + d.city + '</strong><br/>Waste: ' + d.y + '<br/>Homes: ' + d.x + '</div>';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: "Waste Generation vs Households by City"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
new ApexCharts(document.querySelector("#scatter-plot"), options3).render();
|
||||||
|
{% endif %}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user