From 674fdfdd2ede403fb865c25ee1d152f5b8663675 Mon Sep 17 00:00:00 2001 From: Paolo Pasquali Date: Fri, 30 Jun 2017 12:21:34 +0200 Subject: [PATCH 01/11] Add charts app --- charts-prj/__init__.py | 0 charts-prj/settings.py | 58 ++ charts-prj/static/README | 8 + charts-prj/static/css/site_base.css | 0 charts-prj/static/img/README | 0 charts-prj/static/js/README | 0 charts-prj/templates/_actions.html | 24 + charts-prj/templates/geonode_base.html | 4 + charts-prj/templates/site_base.html | 12 + charts-prj/templates/site_index.html | 15 + charts-prj/testing_settings.py | 109 +++ charts-prj/urls.py | 13 + charts-prj/wsgi.py | 31 + charts_app/__init__.py | 0 charts_app/admin.py | 10 + charts_app/migrations/0001_initial.py | 27 + charts_app/migrations/__init__.py | 0 charts_app/models.py | 53 ++ charts_app/templates/donut_chart_v1.html | 68 ++ charts_app/templates/donut_chart_v2.html | 169 +++++ charts_app/templates/pie_chart_v1.html | 65 ++ charts_app/templates/pie_chart_v2.html | 67 ++ charts_app/tests.py | 3 + charts_app/urls.py | 15 + charts_app/views.py | 123 +++ haitidata/settings.py | 4 +- haitidata/static/css/haitidata.css | 31 +- haitidata/static/css/index.css | 45 ++ haitidata/static/js/bar.js | 184 +++++ haitidata/static/js/csv.js | 151 ++++ haitidata/static/js/donut.js | 415 ++++++++++ haitidata/static/js/line.js | 179 +++++ haitidata/static/js/pie.js | 416 ++++++++++ haitidata/static/less/haitidata.less | 37 +- .../templates/charts_app/chart_base.html | 12 + .../charts_app/chart_confirm_delete.html | 5 + .../templates/charts_app/chart_detail.html | 106 +++ .../templates/charts_app/chart_form.html | 87 +++ .../charts_app/chart_main_script.html | 24 + .../charts_app/chart_update_form.html | 94 +++ .../{layers => layers-paolo}/layer_base.html | 0 .../layer_change_poc.html | 0 .../templates/layers-paolo/layer_detail.html | 708 ++++++++++++++++++ .../layers-paolo/layer_detail.html.save | 601 +++++++++++++++ .../layer_geoext_map.html | 0 .../layer_granule_remove.html | 0 .../layer_leaflet_map.html | 0 .../{layers => layers-paolo}/layer_list.html | 0 .../{layers => layers-paolo}/layer_map.html | 0 .../layer_metadata.html | 0 .../layer_metadata_detail.html | 0 .../layer_ol3_map.html | 0 .../layer_remove.html | 0 .../layer_replace.html | 0 .../layer_style_manage.html | 0 .../wps_execute_gs_aggregate.xml | 0 .../wps_execute_gs_unique.xml | 0 haitidata/templates/layers/layer_detail.html | 258 ++++++- haitidata/templates/site_base.html | 148 ++-- haitidata/urls.py | 5 +- wfs_harvest/__init__.py | 0 wfs_harvest/admin.py | 3 + wfs_harvest/migrations/__init__.py | 0 wfs_harvest/models.py | 3 + .../templates/wfs-harvest/field_choice.html | 41 + wfs_harvest/tests.py | 20 + wfs_harvest/urls.py | 8 + wfs_harvest/utils.py | 56 ++ wfs_harvest/views.py | 69 ++ 69 files changed, 4472 insertions(+), 112 deletions(-) create mode 100644 charts-prj/__init__.py create mode 100644 charts-prj/settings.py create mode 100644 charts-prj/static/README create mode 100644 charts-prj/static/css/site_base.css create mode 100644 charts-prj/static/img/README create mode 100644 charts-prj/static/js/README create mode 100644 charts-prj/templates/_actions.html create mode 100644 charts-prj/templates/geonode_base.html create mode 100644 charts-prj/templates/site_base.html create mode 100644 charts-prj/templates/site_index.html create mode 100644 charts-prj/testing_settings.py create mode 100644 charts-prj/urls.py create mode 100644 charts-prj/wsgi.py create mode 100644 charts_app/__init__.py create mode 100644 charts_app/admin.py create mode 100644 charts_app/migrations/0001_initial.py create mode 100644 charts_app/migrations/__init__.py create mode 100644 charts_app/models.py create mode 100644 charts_app/templates/donut_chart_v1.html create mode 100644 charts_app/templates/donut_chart_v2.html create mode 100644 charts_app/templates/pie_chart_v1.html create mode 100644 charts_app/templates/pie_chart_v2.html create mode 100644 charts_app/tests.py create mode 100644 charts_app/urls.py create mode 100644 charts_app/views.py create mode 100644 haitidata/static/css/index.css create mode 100644 haitidata/static/js/bar.js create mode 100644 haitidata/static/js/csv.js create mode 100644 haitidata/static/js/donut.js create mode 100644 haitidata/static/js/line.js create mode 100644 haitidata/static/js/pie.js create mode 100644 haitidata/templates/charts_app/chart_base.html create mode 100644 haitidata/templates/charts_app/chart_confirm_delete.html create mode 100644 haitidata/templates/charts_app/chart_detail.html create mode 100644 haitidata/templates/charts_app/chart_form.html create mode 100644 haitidata/templates/charts_app/chart_main_script.html create mode 100644 haitidata/templates/charts_app/chart_update_form.html rename haitidata/templates/{layers => layers-paolo}/layer_base.html (100%) rename haitidata/templates/{layers => layers-paolo}/layer_change_poc.html (100%) create mode 100644 haitidata/templates/layers-paolo/layer_detail.html create mode 100644 haitidata/templates/layers-paolo/layer_detail.html.save rename haitidata/templates/{layers => layers-paolo}/layer_geoext_map.html (100%) rename haitidata/templates/{layers => layers-paolo}/layer_granule_remove.html (100%) rename haitidata/templates/{layers => layers-paolo}/layer_leaflet_map.html (100%) rename haitidata/templates/{layers => layers-paolo}/layer_list.html (100%) rename haitidata/templates/{layers => layers-paolo}/layer_map.html (100%) rename haitidata/templates/{layers => layers-paolo}/layer_metadata.html (100%) rename haitidata/templates/{layers => layers-paolo}/layer_metadata_detail.html (100%) rename haitidata/templates/{layers => layers-paolo}/layer_ol3_map.html (100%) rename haitidata/templates/{layers => layers-paolo}/layer_remove.html (100%) rename haitidata/templates/{layers => layers-paolo}/layer_replace.html (100%) rename haitidata/templates/{layers => layers-paolo}/layer_style_manage.html (100%) rename haitidata/templates/{layers => layers-paolo}/wps_execute_gs_aggregate.xml (100%) rename haitidata/templates/{layers => layers-paolo}/wps_execute_gs_unique.xml (100%) create mode 100644 wfs_harvest/__init__.py create mode 100644 wfs_harvest/admin.py create mode 100644 wfs_harvest/migrations/__init__.py create mode 100644 wfs_harvest/models.py create mode 100644 wfs_harvest/templates/wfs-harvest/field_choice.html create mode 100644 wfs_harvest/tests.py create mode 100644 wfs_harvest/urls.py create mode 100644 wfs_harvest/utils.py create mode 100644 wfs_harvest/views.py diff --git a/charts-prj/__init__.py b/charts-prj/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/charts-prj/settings.py b/charts-prj/settings.py new file mode 100644 index 0000000..19b5894 --- /dev/null +++ b/charts-prj/settings.py @@ -0,0 +1,58 @@ +""" +Django settings for charts project. + +Generated by 'django-admin startproject' using Django 1.8.7. + +For more information on this file, see +https://docs.djangoproject.com/en/1.8/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.8/ref/settings/ +""" + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import os +from geonode.settings import * + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SITENAME = 'charts-prj' + +LOCAL_ROOT = os.path.abspath(os.path.dirname(__file__)) + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '*u(5zzp_wjj!^!w4$h@e#jv!*y02z17lb8gu-l)j+*79p^r8%(' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +# Application definition + +INSTALLED_APPS = INSTALLED_APPS + ( + 'charts_app', + 'wfs_harvest' +) + +WSGI_APPLICATION = 'charts-prj.wsgi.application' + +# Additional directories which hold static files +STATICFILES_DIRS.append( + os.path.join(LOCAL_ROOT, "static"), +) + +# Location of url mappings +ROOT_URLCONF = 'charts-prj.urls' + +# Location of locale files +LOCALE_PATHS = ( + os.path.join(LOCAL_ROOT, 'locale'), + ) + LOCALE_PATHS + +TEMPLATES[0]['DIRS'].insert(0, os.path.join(LOCAL_ROOT, "templates")) + +MAX_CSV_RECORDS = 20000 + +ALLOWED_HOSTS = ['*'] +PROXY_ALLOWED_HOSTS = ['*'] diff --git a/charts-prj/static/README b/charts-prj/static/README new file mode 100644 index 0000000..a0fd00c --- /dev/null +++ b/charts-prj/static/README @@ -0,0 +1,8 @@ +This directory is used to store static assets for your project. User media files +(FileFields/ImageFields) are not stored here. + +The convention for this directory is: + + * css/ — stores CSS files + * js/ — stores Javascript files + * img/ — stores image files diff --git a/charts-prj/static/css/site_base.css b/charts-prj/static/css/site_base.css new file mode 100644 index 0000000..e69de29 diff --git a/charts-prj/static/img/README b/charts-prj/static/img/README new file mode 100644 index 0000000..e69de29 diff --git a/charts-prj/static/js/README b/charts-prj/static/js/README new file mode 100644 index 0000000..e69de29 diff --git a/charts-prj/templates/_actions.html b/charts-prj/templates/_actions.html new file mode 100644 index 0000000..7e96144 --- /dev/null +++ b/charts-prj/templates/_actions.html @@ -0,0 +1,24 @@ +{% load i18n %} + diff --git a/charts-prj/templates/geonode_base.html b/charts-prj/templates/geonode_base.html new file mode 100644 index 0000000..80d77ad --- /dev/null +++ b/charts-prj/templates/geonode_base.html @@ -0,0 +1,4 @@ +{% extends "site_base.html" %} + +{% load i18n %} +{% load staticfiles %} diff --git a/charts-prj/templates/site_base.html b/charts-prj/templates/site_base.html new file mode 100644 index 0000000..e6eab01 --- /dev/null +++ b/charts-prj/templates/site_base.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} +{% block extra_head %} + +{% endblock %} +{% block extra_tab %} +{% comment %} +Add Tab for Third Party Apps +
  • + App +
  • +{% endcomment %} +{% endblock %} diff --git a/charts-prj/templates/site_index.html b/charts-prj/templates/site_index.html new file mode 100644 index 0000000..b0d3f4e --- /dev/null +++ b/charts-prj/templates/site_index.html @@ -0,0 +1,15 @@ +{% extends 'index.html' %} +{% load i18n %} +{% comment %} +This is where you can override the hero area block. You can simply modify the content below or replace it wholesale to meet your own needs. +{% endcomment %} + {% block hero %} +
    +
    +

    {% trans "Welcome to the charts GeoNode" %}

    +

    {% blocktrans %}GeoNode is an open source platform for sharing geospatial data and maps.{% endblocktrans %}

    +

    {% blocktrans %}If you have any questions about the software or service, join our mailing list.{% endblocktrans %}

    + {% trans 'Need help?' %} {% trans 'Getting Started' %} +
    +
    + {% endblock %} diff --git a/charts-prj/testing_settings.py b/charts-prj/testing_settings.py new file mode 100644 index 0000000..cb93e74 --- /dev/null +++ b/charts-prj/testing_settings.py @@ -0,0 +1,109 @@ +""" +Django settings for pippo project. + +Generated by 'django-admin startproject' using Django 1.8.7. + +For more information on this file, see +https://docs.djangoproject.com/en/1.8/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.8/ref/settings/ +""" + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import os + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '5i81&t!#h$4ie!-kg$&@5b-1j*)d9@(6)8g8o4kg*xw3i2e=@t' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +) + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', +) + +ROOT_URLCONF = 'charts-prj.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'charts-prj.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.8/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Internationalization +# https://docs.djangoproject.com/en/1.8/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.8/howto/static-files/ + +STATIC_URL = '/static/' + +ANONYMOUS_USER_ID = 1 + +LOCAL_MEDIA_URL = '' +API_INCLUDE_REGIONS_COUNT = '' +HAYSTACK_SEARCH = '' +RESOURCE_PUBLISHING = '' \ No newline at end of file diff --git a/charts-prj/urls.py b/charts-prj/urls.py new file mode 100644 index 0000000..ee7151d --- /dev/null +++ b/charts-prj/urls.py @@ -0,0 +1,13 @@ +from django.conf.urls import patterns, url +from django.views.generic import TemplateView + +from geonode.urls import * + +urlpatterns = patterns('', + url(r'^/?$', + TemplateView.as_view(template_name='site_index.html'), + name='home'), + ) + urlpatterns + [ + url(r'^chart/', include('charts_app.urls')), + url(r'^table/', include('wfs_harvest.urls')), + ] diff --git a/charts-prj/wsgi.py b/charts-prj/wsgi.py new file mode 100644 index 0000000..6147efb --- /dev/null +++ b/charts-prj/wsgi.py @@ -0,0 +1,31 @@ +""" +WSGI config for charts project. + +This module contains the WSGI application used by Django's development server +and any production WSGI deployments. It should expose a module-level variable +named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover +this application via the ``WSGI_APPLICATION`` setting. + +Usually you will have the standard Django WSGI application here, but it also +might make sense to replace the whole Django WSGI application with a custom one +that later delegates to the Django one. For example, you could introduce WSGI +middleware here, or combine a Django application with an application of another +framework. + +""" +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "charts-prj.settings") + +# This application object is used by any WSGI server configured to use this +# file. This includes Django's development server, if the WSGI_APPLICATION +# setting points here. +from django.core.wsgi import get_wsgi_application +application = get_wsgi_application() + +# Apply WSGI middleware here. +# from helloworld.wsgi import HelloWorldApplication +# application = HelloWorldApplication(application) diff --git a/charts_app/__init__.py b/charts_app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/charts_app/admin.py b/charts_app/admin.py new file mode 100644 index 0000000..05e7ad9 --- /dev/null +++ b/charts_app/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +from .models import Chart + + +class ChartAdmin(admin.ModelAdmin): + list_display = ['id', 'title'] + + +admin.site.register(Chart, ChartAdmin) diff --git a/charts_app/migrations/0001_initial.py b/charts_app/migrations/0001_initial.py new file mode 100644 index 0000000..04f3248 --- /dev/null +++ b/charts_app/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('layers', '24_to_26'), + ] + + operations = [ + migrations.CreateModel( + name='Chart', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('title', models.CharField(max_length=128, blank=True)), + ('category', models.CharField(max_length=200)), + ('quantity', models.CharField(max_length=200)), + ('type', models.SmallIntegerField(default=0, choices=[(0, b'Bar chart'), (1, b'Pie chart'), (2, b'Donut chart')])), + ('aggr_type', models.SmallIntegerField(default=3, choices=[(0, b'Sum'), (1, b'Mean'), (2, b'Category count'), (3, b'Max'), (4, b'Min')])), + ('abstract', models.TextField(blank=True)), + ('layer', models.ForeignKey(to='layers.Layer')), + ], + ), + ] diff --git a/charts_app/migrations/__init__.py b/charts_app/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/charts_app/models.py b/charts_app/models.py new file mode 100644 index 0000000..298ff2b --- /dev/null +++ b/charts_app/models.py @@ -0,0 +1,53 @@ +from django.db import models +from django.core.urlresolvers import reverse +from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings + +from geonode.layers.models import Layer + +from wfs_harvest.utils import get_featno + + +def validate_wfs(value): + lyr = Layer.objects.get(pk=value) + if lyr.service_type != 'WFS': + raise ValidationError(_('%(lyr_title)s is not a WFS layer'), + code='not_a_wfs', + params={'lyr_title': lyr.title}) + + feat_no = get_featno(value) + if feat_no > settings.MAX_CSV_RECORDS: + raise ValidationError(_('%(lyr_title)s has too many features; ' + 'chart functionality is not available ' + 'for very large datasets'), + code='too_many_records', + params={'lyr_title': lyr.title}) + + +class Chart(models.Model): + CHART_TYPES = ( + (0, 'Bar chart'), + (1, 'Pie chart'), + (2, 'Donut chart'), + (3, 'Line chart') + ) + + AGGREGATION_TYPES = ( + (0, 'Sum'), + (1, 'Mean'), + (2, 'Category count'), + (3, 'Max'), + (4, 'Min'), + ) + + layer = models.ForeignKey(Layer, validators=[validate_wfs]) + title = models.CharField(max_length=128, blank=True) + category = models.CharField(max_length=200) + quantity = models.CharField(max_length=200) + type = models.SmallIntegerField(choices=CHART_TYPES, default=0) + aggr_type = models.SmallIntegerField(choices=AGGREGATION_TYPES, default=3) + abstract = models.TextField(blank=True) + + def get_absolute_url(self): + return reverse('chart_detail', kwargs={'pk': self.pk}) diff --git a/charts_app/templates/donut_chart_v1.html b/charts_app/templates/donut_chart_v1.html new file mode 100644 index 0000000..286f532 --- /dev/null +++ b/charts_app/templates/donut_chart_v1.html @@ -0,0 +1,68 @@ + + +Donut Chart v1 +

    Simple Donut Chart v1

    +

    Zero values excluded

    +

    D3js version 4, use canvas

    + + + + \ No newline at end of file diff --git a/charts_app/templates/donut_chart_v2.html b/charts_app/templates/donut_chart_v2.html new file mode 100644 index 0000000..c371272 --- /dev/null +++ b/charts_app/templates/donut_chart_v2.html @@ -0,0 +1,169 @@ + + +Donut Chart v2 +

    Simple Donut Chart v2

    +

    Zero values excluded, label with ticks

    +

    D3js version 3, use svg

    + + + + + \ No newline at end of file diff --git a/charts_app/templates/pie_chart_v1.html b/charts_app/templates/pie_chart_v1.html new file mode 100644 index 0000000..10e08c5 --- /dev/null +++ b/charts_app/templates/pie_chart_v1.html @@ -0,0 +1,65 @@ + + +Pie Chart v1 +

    Simple Pie Chart v1

    +

    All data

    +

    D3js version 4, use svg

    + + + + diff --git a/charts_app/templates/pie_chart_v2.html b/charts_app/templates/pie_chart_v2.html new file mode 100644 index 0000000..7a93002 --- /dev/null +++ b/charts_app/templates/pie_chart_v2.html @@ -0,0 +1,67 @@ + + +Pie Chart v2 +

    Simple Pie Chart v2

    +

    Zero values excluded

    +

    D3js version 4, use svg

    + + + + diff --git a/charts_app/tests.py b/charts_app/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/charts_app/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/charts_app/urls.py b/charts_app/urls.py new file mode 100644 index 0000000..9d7ab99 --- /dev/null +++ b/charts_app/urls.py @@ -0,0 +1,15 @@ +from django.conf.urls import url + +from . import views + +urlpatterns = [url(r'^sample_csv$', views.get_csv, name='get_csv'), + url(r'^sample_csv2$', views.get_csv_aggr, name='get_csv2'), + url(r'^pie_chart_v1$', views.pie_chart_v1), + url(r'^donut_chart_v1$', views.donut_chart_v1), + url(r'^pie_chart_v2$', views.pie_chart_v2), + url(r'^donut_chart_v2$', views.donut_chart_v2), + url(r'^(?P[0-9]+)/$', views.ChartDetailView.as_view(), name='chart_detail'), + url(r'^add/(?P[0-9]+)/$', views.ChartCreate.as_view(), name='chart_add'), + url(r'^(?P[0-9]+)/update/$', views.ChartUpdate.as_view(), name='chart_update'), + url(r'^(?P[0-9]+)/delete/$', views.ChartDelete.as_view(), name='chart_delete') + ] diff --git a/charts_app/views.py b/charts_app/views.py new file mode 100644 index 0000000..d646bc6 --- /dev/null +++ b/charts_app/views.py @@ -0,0 +1,123 @@ +import csv + +from django.http import HttpResponse +from django.shortcuts import render +from django.views.generic.detail import DetailView +from django.views.generic.edit import CreateView, UpdateView, DeleteView + +from geonode.layers.models import Layer + +from .models import Chart +from wfs_harvest.utils import get_fields + + +class ChartDetailView(DetailView): + model = Chart + + +class ChartCreate(CreateView): + model = Chart + fields = '__all__' + + def get_initial(self): + layer_id = self.kwargs['layer_id'] + initial = self.initial.copy() + initial['layer'] = layer_id + return initial + + def get_context_data(self, **kwargs): + layer_id = self.kwargs['layer_id'] + layer = Layer.objects.get(pk=layer_id) + fieldnames, num_fieldnames = get_fields(layer_id) + ctx = super(ChartCreate, self).get_context_data(**kwargs) + ctx['fieldnames'] = fieldnames + ctx['num_fieldnames'] = num_fieldnames + ctx['layer'] = layer + return ctx + + +class ChartUpdate(UpdateView): + model = Chart + fields = '__all__' + template_name_suffix = '_update_form' + + +class ChartDelete(DeleteView): + model = Chart + success_url = '/' + + +def get_csv(request): + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="table.csv"' + + writer = csv.writer(response) + writer.writerow(['name', 'area']) + writer.writerow(['Af', 3.51]) + writer.writerow(['Ems / ems', 0.00]) + writer.writerow(['Ep / ep', 0.00]) + writer.writerow(['Lmst', 4.88]) + writer.writerow(['Mpb', 18.93]) + writer.writerow(['Pf', 30.53]) + writer.writerow(['Ppf', 6.84]) + writer.writerow(['Qa', 0.49]) + writer.writerow(['Qhac', 5.91]) + writer.writerow(['Qhad', 0.00]) + writer.writerow(['Qham', 11.01]) + writer.writerow(['Qht1', 1.10]) + writer.writerow(['Qht2', 27.60]) + writer.writerow(['Qpf', 34.40]) + writer.writerow(['Qphf', 42.30]) + + return response + +def get_csv_aggr(request): + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="table.csv"' + + writer = csv.writer(response) + writer.writerow(['name', 'area']) + writer.writerow(['Af', 3.51]) + writer.writerow(['Ems / ems', 0.00]) + writer.writerow(['Ep / ep', 0.00]) + writer.writerow(['Lmst', 4.88]) + writer.writerow(['Mpb', 18.93]) + writer.writerow(['Pf', 30.53]) + writer.writerow(['Ppf', 6.84]) + writer.writerow(['Qa', 0.49]) + writer.writerow(['Qhac', 5.91]) + writer.writerow(['Qhad', 0.00]) + writer.writerow(['Qham', 11.01]) + writer.writerow(['Qht1', 1.10]) + writer.writerow(['Qht2', 27.60]) + writer.writerow(['Qphf', 42.30]) + writer.writerow(['Af', 6.58]) + writer.writerow(['Qa', 30.78]) + writer.writerow(['Af', 16.58]) + + return response + + +def pie_chart_v1(request): + return render(request, 'pie_chart_v1.html') + + +def donut_chart_v1(request): + return render(request, 'donut_chart_v1.html') + + +def pie_chart_v2(request): + qdict = request.GET + typename = qdict['lyrname'] + category_field = qdict['category'] + quantity_field = qdict['quantity'] + context = { + 'lyrname': typename, + 'category': category_field, + 'quantity': quantity_field + } + return render(request, 'pie_chart_v2.html', context) + + +def donut_chart_v2(request): + return render(request, 'donut_chart_v2.html') diff --git a/haitidata/settings.py b/haitidata/settings.py index f747c50..e1470c1 100644 --- a/haitidata/settings.py +++ b/haitidata/settings.py @@ -53,7 +53,7 @@ LOCALE_PATHS = ( os.path.join(LOCAL_ROOT, 'locale'), ) + LOCALE_PATHS -INSTALLED_APPS = INSTALLED_APPS + ('haitidata', 'osgeo_importer', 'geonode-client') +INSTALLED_APPS = INSTALLED_APPS + ('haitidata', 'osgeo_importer', 'geonode-client', 'charts_app', 'wfs_harvest') LAYER_PREVIEW_LIBRARY = 'react' @@ -111,6 +111,8 @@ BROKER_URL = "amqp://guest@localhost:5672" CELERY_ALWAYS_EAGER = True IMPORT_TASK_SOFT_TIME_LIMIT = 90 +MAX_CSV_RECORDS = 20000 + SOCIAL_ORIGINS = [{ "label":"paper-plane-o", diff --git a/haitidata/static/css/haitidata.css b/haitidata/static/css/haitidata.css index f8b16d4..6427289 100644 --- a/haitidata/static/css/haitidata.css +++ b/haitidata/static/css/haitidata.css @@ -6882,7 +6882,7 @@ form input { max-height: none; } .navbar-header i { - color: white; + color: #ff5722; font-size: 2em; } .navbar-header button { @@ -7327,6 +7327,35 @@ footer select { .grid-spacer { margin-top: 30px; } +#accordion .panel { + border-radius: 0; + border-top: 2px solid #b0bec5; + line-height: 1em; +} +#accordion .panel-heading { + background: none; + border: 0; +} +#accordion .panel-body img { + margin-bottom: 1em; +} +#accordion .panel-body small { + color: #78909c; + font-size: 0.825em; +} +#accordion .panel-body form { + font-size: 0.825em; +} +#accordion .panel-body form .form-control { + background: none; + color: #546e7a; + font-size: 0.825em; + height: 24px; + line-height: 1; + -webkit-appearance: none; + -webkit-box-shadow: none; + box-shadow: none; +} /* ------ OSGeo Importer ------------------------------ */ body.importer h4 { diff --git a/haitidata/static/css/index.css b/haitidata/static/css/index.css new file mode 100644 index 0000000..ef34317 --- /dev/null +++ b/haitidata/static/css/index.css @@ -0,0 +1,45 @@ +body { + font-family: 'Roboto', sans-serif; + color: #333333; +} + +/* Add shadow effect to chart. If you don't like it, get rid of it. */ +svg { + -webkit-filter: drop-shadow( 0px 3px 3px rgba(0,0,0,.3) ); + filter: drop-shadow( 0px 3px 3px rgba(0,0,0,.25) ); +} + +/*Styling for the lines connecting the labels to the slices*/ +polyline{ + opacity: .3; + stroke: black; + stroke-width: 2px; + fill: none; +} + +/* Make the percentage on the text labels bold*/ +.labelName tspan { + font-style: normal; + font-weight: 700; +} + +/* In biology we generally italicise species names. */ +.labelName { + font-size: 0.9em; + font-style: italic; +} + +/* Tooltip for line chart */ + +div.tooltip { + position: absolute; + text-align: center; + width: 60px; + height: 28px; + padding: 2px; + font: 12px sans-serif; + background: lightsteelblue; + border: 0px; + border-radius: 8px; + pointer-events: none; +} \ No newline at end of file diff --git a/haitidata/static/js/bar.js b/haitidata/static/js/bar.js new file mode 100644 index 0000000..fa88539 --- /dev/null +++ b/haitidata/static/js/bar.js @@ -0,0 +1,184 @@ +function barChart(cat, qnt, title, abs){ + var width = 900, + height = 500, + padding = 5, + xScale = d3.scaleBand(), + yScale = d3.scaleLinear(), + colour = d3.scaleOrdinal(d3.schemeCategory20), + x, + y, + margin = { top: 30, bottom: 30, left: 30, right: 150 }, + floatFormat = d3.format("." + d3.precisionFixed(0.5) + "f"), + xAxis = d3.axisBottom(xScale), + yAxis = d3.axisLeft(yScale); + + function my(selection){ + + selection.each(function(data) { + + var svg = selection.append('svg') + .attr("id", "chart_svg") + .attr("width", width) + .attr("height", height); + + var g = svg.selectAll("g") + .data([1]); + g = g.enter().append("g") + .merge(g) + .attr("transform", + "translate(" + margin.left + "," + margin.top +")"); + + + var innerWidth = width - margin.left - margin.right ; + var innerHeight = function(){ return height - margin.top - margin.bottom} ; + + + xScale + .domain(data.map(function (d){ return d[x]; })) + .range([0, (innerWidth - margin.right)]); + + + // X axis + var xAxisG = g.selectAll(".x-axis").data([1]); + xAxisG.enter().append("g") + .attr("class", "x-axis") + .merge(xAxisG) + .attr("transform", "translate(0," + innerHeight() +")") + .call(xAxis); + + // labels X axis + // rotate text if is longer than... + + d3.select(this).select(".x-axis").selectAll("text").each(function(){ + if (this.getBBox().width > (xScale.bandwidth() - (padding*2))) + d3.selectAll("text").attr("transform", "rotate(-90)") + .attr("y", 0) + .attr("x", -10) + .attr("dy", ".35em") + .style("text-anchor", "end"); + }); + + // adjust margin and x axis title + var maxh = 0; + d3.select(this).select(".x-axis").selectAll("text").each(function(){ + if (this.getBBox().width > maxh) + maxh = this.getBBox().width; + }); + margin.bottom = margin.bottom + maxh; + d3.select(".x-axis").attr("transform", "translate(0," + (innerHeight()) + ")"); + + + g.append("text") + .attr("transform", "translate(" + (innerWidth/2) + ", " + (innerHeight() + margin.bottom - 5) + ")") + .style("font-size", "12px") + .style("text-anchor", "middle") + .text(cat); + + yScale + .domain([0, d3.max(data, function (d){ return d[y] ;})]) + .range([innerHeight(), 0]); + + // Y axis + var yAxisG = g.selectAll(".y-axis").data([1]); + yAxisG.enter().append("g") + .attr("class", "y-axis") + .merge(yAxisG) + .call(yAxis); + + //labels Y axis + var maxw = 0; + d3.select(this).select(".y-axis").selectAll("text").each(function(){ + if (this.getBBox().width > maxw) { + maxw = this.getBBox().width; + } + }); + margin.left = margin.left + maxw; + g.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + var yLabel = function(){ + if (title == "Count"){ + return "Count"; + } else { + return qnt; + }}; + + g.append("text") + .attr("transform", "rotate(-90)") + .attr("x", 0 - (innerHeight()/2)) + .attr("y", 0 - (margin.left)) + .attr("dy", "1em") + .style("font-size", "12px") + .style("text-anchor", "middle") + .text(yLabel); + + // bars + var rects = g.selectAll("rect") + .data(data); + rects.exit().remove(); + rects.enter().append("rect") + .merge(rects) + .attr("x", function (d){ return xScale(d[x]) + padding; }) + .attr("y", function (d){ return yScale(d[y]); }) + .attr("fill", function(d) { return colour(xScale(d[x])); }) + .attr("width", xScale.bandwidth() - (padding*2)) + .attr("height", function (d){ + return innerHeight() - yScale(d.value); + }); + + //chart legend + var legend = svg.selectAll(".legend") + .data(data) + .enter().append("g") + .attr("class", "legend") + .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); + + legend.append("rect") + .attr("x", width - 18) + .attr("width", 18) + .attr("height", 18) + .style("fill", function(d) { return colour(xScale(d[x]))} ); + + legend.append("text") + .attr("x", width - 24) + .attr("y", 9) + .attr("dy", ".35em") + .style("text-anchor", "end") + .text(function (d){ return d[x] + ": " + floatFormat(d[y]); }); + +/* //chart abstract + var abstract = svg.selectAll(".abstract").data(data).enter().append("g") + .attr("class", "abstract") + .attr("transform", "translate(0," + 450 + ")"); + + abstract.append("text") + .attr("x", width - 24) + .attr("y", 9) + .attr("dy", ".35em") + .style("text-anchor", "end") + .text(abs);*/ + + }); + } + + my.x = function (_){ + return arguments.length ? (x = _, my) : x; + }; + + my.y = function (_){ + return arguments.length ? (y = _, my) : y; + }; + + my.width = function (_){ + return arguments.length ? (width = _, my) : width; + }; + + my.height = function (_){ + return arguments.length ? (height = _, my) : height; + }; + + my.padding = function (_){ + return arguments.length ? (xScale.padding(_), my) : xScale.padding(); + }; + + return my; + } diff --git a/haitidata/static/js/csv.js b/haitidata/static/js/csv.js new file mode 100644 index 0000000..97de904 --- /dev/null +++ b/haitidata/static/js/csv.js @@ -0,0 +1,151 @@ +var getCsv = function(csv_link, category, quantity, agg, chartType, abs){ + var data = []; + d3.csv(csv_link, function(error, csv) { + if (error) throw error; + csv.forEach(function(row){ + var qnty_v; var ctgy_v; var i = 0; + while (i < Object.keys(row).length){ + if (Object.keys(row)[i] == quantity){ + qnty_v = +Object.values(row)[i] + } else if (Object.keys(row)[i] == category){ + if (Object.values(row)[i].length >= 20){ + + ctgy_v = Object.values(row)[i].substring(0, 17) + "..."; + } else { + ctgy_v = Object.values(row)[i]; + }; + } + i++; + }; + data.push({qnty : qnty_v , ctgy : ctgy_v }); + }) + + + var dropZeros = function(dato){ + return dato.filter(function(d){ + if (d.qnty !== 0){ + return d.qnty; + }}) + }; + + function groupData_sum(dato){ + return d3.nest().key(function(d) {return d.ctgy;}) + .rollup(function(v) {return d3.sum(v, function(d) {return d.qnty;});}) + .entries(dato); + }; + + function groupData_mean(dato){ + return d3.nest().key(function(d) {return d.ctgy;}) + .rollup(function(v) {return d3.mean(v, function(d) {return d.qnty;});}) + .entries(dato); + }; + + function groupData_min(dato){ + return d3.nest().key(function(d) {return d.ctgy;}) + .rollup(function(v) {return d3.min(v, function(d) {return d.qnty;});}) + .entries(dato); + }; + + function groupData_max(dato){ + return d3.nest().key(function(d) {return d.ctgy;}) + .rollup(function(v) {return d3.max(v, function(d) {return d.qnty;});}) + .entries(dato); + }; + + function groupData_count(dato){ + return d3.nest().key(function(d) {return d.ctgy;}) + .rollup(function(v) { return v.length;}) + .entries(dato); + }; + + function descendent(a,b){ + if (a.value < b.value) return 1; + if (a.value > b.value) return -1; + return 0; + } + + + var title; + var dati; + function aggregateData(data, agg){ + + if (agg == 0){ + dati = groupData_sum(dropZeros(data)).sort(descendent); + title = "Sum"; + } else if (agg == 1){ + dati = groupData_mean(data).sort(descendent); + title = "Mean"; + } else if (agg == 2){ + dati = groupData_count(data).sort(descendent); + title = "Count"; + } else if (agg == 3){ + dati = groupData_max(dropZeros(data)).sort(descendent); + title = "Maximum"; + } else if (agg == 4){ + dati = groupData_min(dropZeros(data)).sort(descendent);; + title = "Minimum"; + }; + return dati; + }; + + function create(chartType){ + if (chartType == 0){ + aggregateData(data, agg); + if (dati.length > 20){ + while (dati.length >= 21) { dati.pop(); }; + window.alert("Output limited to 20 categories"); + }; + var bar = barChart(category, quantity, title, abs) + .x('key') + .y('value') + d3.select("#chart_area") + .datum(dati) + .call(bar); + } else if (chartType == 1){ + aggregateData(data, agg) + if (dati.length > 10){ + while (dati.length >= 11) { dati.pop(); }; + window.alert("Output limited to 10 categories"); + }; + var pie = pieChart(category, quantity, title, abs) + .variable('value') + .category('key') + d3.select('#chart_area') + .datum(dati) + .call(pie); + } else if (chartType == 2){ + aggregateData(data, agg) + if (dati.length > 10){ + while (dati.length >= 11) { dati.pop(); }; + window.alert("Output limited to 10 categories"); + }; + var donut = donutChart(category, quantity, title, abs) + .variable('value') + .category('key') + d3.select('#chart_area') + .datum(dati) + .call(donut); + } else if (chartType == 3){ + aggregateData(data, agg) + if (dati.length > 20){ + while (dati.length >= 21) { dati.pop(); }; + window.alert("Output limited to 20 categories"); + }; + var line = lineChart(category, quantity, title, abs) + .x('key') + .y('value') + d3.select('#chart_area') + .datum(dati) + .call(line); + } + }; + + create(chartType); + }); +}; + + + + + + diff --git a/haitidata/static/js/donut.js b/haitidata/static/js/donut.js new file mode 100644 index 0000000..bac5c5d --- /dev/null +++ b/haitidata/static/js/donut.js @@ -0,0 +1,415 @@ +function donutChart(cat, qnt, title){ + var width = 900, + height = 500, + margin = {top: 70, right: 10, bottom: 10, left: 10}, + colour = d3.scaleOrdinal(d3.schemeCategory20c), // colour scheme + variable, // value in data that will dictate proportions on chart + category, // compare data by + padAngle = 0.015, // effectively dictates the gap between slices + transTime, // transition time + updateData, + floatFormat = d3.format("." + d3.precisionFixed(0.5) + "f"), + cornerRadius = 3; // sets how rounded the corners are on each slice + + function chart(selection){ + selection.each(function() { + // generate chart + // =========================================================================================== + // Set up constructors for making donut. See https://github.com/d3/d3-shape/blob/master/README.md + var radius = Math.min(width, (height - margin.top)) / 2; + + // creates a new pie generator + var pie = d3.pie() + .value(function(d) { return floatFormat(d[variable]); }) + .sort(null); + + // contructs and arc generator. This will be used for the donut. The difference between outer and inner + // radius will dictate the thickness of the donut + var arc = d3.arc() + .outerRadius(radius * 0.8) + .innerRadius(radius * 0.6) + .cornerRadius(cornerRadius) + .padAngle(padAngle); + + // this arc is used for aligning the text labels + var outerArc = d3.arc() + .outerRadius(radius * 0.83) + .innerRadius(radius * 0.83); + // =========================================================================================== + + // =========================================================================================== + // append the svg object to the selection + // var svg = selection.append('svg') + var svg = selection.append('svg') + .attr("id", "chart_svg") + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); + // =========================================================================================== + + // =========================================================================================== + // g elements to keep elements within svg modular + svg.append('g').attr('class', 'slices'); + svg.append('g').attr('class', 'labelName'); + svg.append('g').attr('class', 'lines'); + // =========================================================================================== + + // =========================================================================================== + // add and colour the donut slices + var path = svg.select('.slices') + .selectAll('path') + .data(pie) + .enter().append('path') + .attr('fill', function(d) { return colour(d.data[category]); }) + .attr('d', arc); + // =========================================================================================== + + // =========================================================================================== + // add text labels + var occupedLabel = []; + var label = svg.select('.labelName').selectAll('text') + .data(pie) + .enter().append('text') + .attr('dy', '.35em') + .html(updateLabelText) + .attr('transform', labelTransform) + .style('text-anchor', function(d) { + // if slice centre is on the left, anchor text to start, otherwise anchor to end + return (midAngle(d)) < Math.PI ? 'start' : 'end'; + }); + // =========================================================================================== + + // =========================================================================================== + // add lines connecting labels to slice. A polyline creates straight lines connecting several points + var occupedPoint = []; + var polyline = svg.select('.lines') + .selectAll('polyline') + .data(pie) + .enter().append('polyline') + .attr('points', calculatePoints); + // =========================================================================================== + + // =========================================================================================== + // add tooltip to mouse events on slices and labels + d3.selectAll('.labelName text, .slices path').call(toolTip); + // =========================================================================================== + + // =========================================================================================== + // FUNCTION TO UPDATE CHART + updateData = function() { + + var updatePath = d3.select('.slices').selectAll('path'); + var updateLines = d3.select('.lines').selectAll('polyline'); + var updateLabels = d3.select('.labelName').selectAll('text'); + + var data0 = path.data(), // store the current data before updating to the new + data1 = pie(data); + + // update data attached to the slices, labels, and polylines. the key function assigns the data to + // the correct element, rather than in order of how the data appears. This means that if a category + // already exists in the chart, it will have its data updated rather than removed and re-added. + updatePath = updatePath.data(data1, key); + updateLines = updateLines.data(data1, key); + updateLabels = updateLabels.data(data1, key); + + // adds new slices/lines/labels + updatePath.enter().append('path') + .each(function(d, i) { this._current = findNeighborArc(i, data0, data1, key) || d; }) + .attr('fill', function(d) { return colour(d.data[category]); }) + .attr('d', arc); + + updateLines.enter().append('polyline') + .each(function(d, i) { this._current = findNeighborArc(i, data0, data1, key) || d; }) + .attr('points', calculatePoints); + + updateLabels.enter().append('text') + .each(function(d, i) { this._current = findNeighborArc(i, data0, data1, key) || d; }) + .html(updateLabelText) + .attr('transform', labelTransform) + .style('text-anchor', function(d) { return (midAngle(d)) < Math.PI ? 'start' : 'end'; }); + + // removes slices/labels/lines that are not in the current dataset + updatePath.exit() + .transition() + .duration(transTime) + .attrTween("d", arcTween) + .remove(); + + updateLines.exit() + .transition() + .duration(transTime) + .attrTween("points", pointTween) + .remove(); + + updateLabels.exit() + .remove(); + + // animates the transition from old angle to new angle for slices/lines/labels + updatePath.transition().duration(transTime) + .attrTween('d', arcTween); + + updateLines.transition().duration(transTime) + .attrTween('points', pointTween); + + updateLabels.transition().duration(transTime) + .attrTween('transform', labelTween) + .styleTween('text-anchor', labelStyleTween); + + updateLabels.html(updateLabelText); // update the label text + + // add tooltip to mouse events on slices and labels + d3.selectAll('.labelName text, .slices path').call(toolTip); + + }; + // =========================================================================================== + // Functions + // calculates the angle for the middle of a slice + function midAngle(d) { return d.startAngle + (d.endAngle - d.startAngle) / 2; } + + // function that creates and adds the tool tip to a selected element + function toolTip(selection) { + + // add tooltip (svg circle element) when mouse enters label or slice + selection.on('mouseenter', function (data) { + + svg.append('text') + .attr('class', 'toolCircle') + .attr('dy', -15) // hard-coded. can adjust this to adjust text vertical alignment in tooltip + .html(toolTipHTML(data)) // add text to the circle. + .style('font-size', '.9em') + .style('text-anchor', 'middle'); // centres text in tooltip + + svg.append('circle') + .attr('class', 'toolCircle') + .attr('r', radius * 0.55) // radius of tooltip circle + .style('fill', colour(data.data[category])) // colour based on category mouse is over + .style('fill-opacity', 0.35); + + }); + + // remove the tooltip when mouse leaves the slice/label + selection.on('mouseout', function () { + d3.selectAll('.toolCircle').remove(); + }); + } + + // function to create the HTML string for the tool tip. Loops through each key in data object + // and returns the html string key: value + function toolTipHTML(data) { + + var tip = '', + i = 0; + + for (var key in data.data) { + + // if value is a number, format it as float + var value = (!isNaN(parseFloat(data.data[key]))) ? parseFloat(data.data[key]) : data.data[key]; + qnt = (title == "Count" ? "Count" : qnt); + + // leave off 'dy' attr for first tspan so the 'dy' attr on text element works. The 'dy' attr on + // tspan effectively imitates a line break. + if (i === 0) tip += '' + cat + ': ' + value + ''; + else tip += '' + qnt + ': ' + value + ''; + i++; + } + + return tip; + } + + // calculate the points for the polyline to pass through + function calculatePoints(d) { + // see label transform function for explanations of these three lines. + var pos = outerArc.centroid(d); + var rev_pos = outerArc.centroid(d); + pos[0] = radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1); + // verify label conflict: if overlaps move up of 10 + if (pos[0] < 0 && pos[1] < 0) { + var i = 0; + if (occupedPoint.length >= 1){ + for (i = 0; i < occupedPoint.length; i++){ + if (pos[1] <= (occupedPoint[i] + 10) && (pos[1] >= (occupedPoint[i] - 10))){ + pos[1] = pos[1] - 20; + rev_pos[1] = rev_pos[1] - 20; + } + } + } occupedPoint.push(pos[1]); + } + return [arc.centroid(d), rev_pos, pos] + } + + function labelTransform(d) { + // effectively computes the centre of the slice. + // see https://github.com/d3/d3-shape/blob/master/README.md#arc_centroid + var pos = outerArc.centroid(d); + // verify label conflict: if overlaps move up of 10 + if (pos[0] < 0 && pos[1] < 0) { + var i = 0; + if (occupedLabel.length >= 1){ + for (i = 0; i < occupedLabel.length; i++){ + if (pos[1] <= (occupedLabel[i] + 10) && (pos[1] >= (occupedLabel[i] - 10))){ + pos[1] = pos[1] - 20; + } + } + } occupedLabel.push(pos[1]); + } + // changes the point to be on left or right depending on where label is. + pos[0] = radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1); + return 'translate(' + pos + ')'; + } + + function updateLabelText(d) { + return d.data[category] + ': ' + floatFormat(d.data[variable]) + ''; + } + + // function that calculates transition path for label and also it's text anchoring + function labelStyleTween(d) { + this._current = this._current || d; + var interpolate = d3.interpolate(this._current, d); + this._current = interpolate(0); + return function(t){ + var d2 = interpolate(t); + return midAngle(d2) < Math.PI ? 'start':'end'; + }; + } + + function labelTween(d) { + this._current = this._current || d; + var interpolate = d3.interpolate(this._current, d); + this._current = interpolate(0); + return function(t){ + var d2 = interpolate(t), + pos = outerArc.centroid(d2); // computes the midpoint [x,y] of the centre line that would be + // generated by the given arguments. It is defined as startangle + endangle/2 and innerR + outerR/2 + pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1); // aligns the labels on the sides + return 'translate(' + pos + ')'; + }; + } + + function pointTween(d) { + this._current = this._current || d; + var interpolate = d3.interpolate(this._current, d); + this._current = interpolate(0); + return function(t){ + var d2 = interpolate(t), + pos = outerArc.centroid(d2); + pos[0] = radius * 0.95 * (midAngle(d2) < Math.PI ? 1 : -1); + return [arc.centroid(d2), outerArc.centroid(d2), pos]; + }; + } + + // function to calculate the tween for an arc's transition. + // see http://bl.ocks.org/mbostock/5100636 for a thorough explanation. + function arcTween(d) { + var i = d3.interpolate(this._current, d); + this._current = i(0); + return function(t) { return arc(i(t)); }; + } + + function findNeighborArc(i, data0, data1, key) { + var d; + return (d = findPreceding(i, data0, data1, key)) ? {startAngle: d.endAngle, endAngle: d.endAngle} + : (d = findFollowing(i, data0, data1, key)) ? {startAngle: d.startAngle, endAngle: d.startAngle} + : null; + } + // Find the element in data0 that joins the highest preceding element in data1. + function findPreceding(i, data0, data1, key) { + var m = data0.length; + while (--i >= 0) { + var k = key(data1[i]); + for (var j = 0; j < m; ++j) { + if (key(data0[j]) === k) return data0[j]; + } + } + } + + function key(d) { + return d.data[category]; + } + + // Find the element in data0 that joins the lowest following element in data1. + function findFollowing(i, data0, data1, key) { + var n = data1.length, m = data0.length; + while (++i < n) { + var k = key(data1[i]); + for (var j = 0; j < m; ++j) { + if (key(data0[j]) === k) return data0[j]; + } + } + } + + // =========================================================================================== + + }); + } + + // getter and setter functions. See Mike Bostocks post "Towards Reusable Charts" for a tutorial on how this works. + chart.width = function(value) { + if (!arguments.length) return width; + width = value; + return chart; + }; + + chart.height = function(value) { + if (!arguments.length) return height; + height = value; + return chart; + }; + + chart.margin = function(value) { + if (!arguments.length) return margin; + margin = value; + return chart; + }; + + chart.radius = function(value) { + if (!arguments.length) return radius; + radius = value; + return chart; + }; + + chart.padAngle = function(value) { + if (!arguments.length) return padAngle; + padAngle = value; + return chart; + }; + + chart.cornerRadius = function(value) { + if (!arguments.length) return cornerRadius; + cornerRadius = value; + return chart; + }; + + chart.colour = function(value) { + if (!arguments.length) return colour; + colour = value; + return chart; + }; + + chart.variable = function(value) { + if (!arguments.length) return variable; + variable = value; + return chart; + }; + + chart.category = function(value) { + if (!arguments.length) return category; + category = value; + return chart; + }; + + chart.transTime = function(value) { + if (!arguments.length) return transTime; + transTime = value; + return chart; + }; + + chart.data = function(value) { + if (!arguments.length) return data; + data = value; + if (typeof updateData === 'function') updateData(); + return chart; + }; + + return chart; +} \ No newline at end of file diff --git a/haitidata/static/js/line.js b/haitidata/static/js/line.js new file mode 100644 index 0000000..e00908f --- /dev/null +++ b/haitidata/static/js/line.js @@ -0,0 +1,179 @@ +function lineChart(cat, qnt, title){ + var width = 900, + height = 500, + xScale = d3.scaleBand(), + yScale = d3.scaleLinear(), + colour = "steelblue", + x, + y, + margin = { top: 30, bottom: 30, left: 30, right: 30 }, + floatFormat = d3.format("." + d3.precisionFixed(0.5) + "f"), + xAxis = d3.axisBottom(xScale), + yAxis = d3.axisLeft(yScale); + + function my(selection){ + + selection.each(function(data) { + + var svg = selection.append('svg') + .attr("id", "chart_svg") + .attr("width", width) + .attr("height", height); + + var g = svg.selectAll("g") + .data([1]); + g = g.enter().append("g") + .merge(g) + .attr("transform", + "translate(" + margin.left + "," + margin.top +")"); + + + var innerWidth = width - margin.left - margin.right ; + var innerHeight = function(){ return height - margin.top - margin.bottom} ; + + + xScale + .domain(data.map(function (d){ return d[x]; })) + .range([0, (innerWidth - margin.right)]); + + + // X axis + var xAxisG = g.selectAll(".x-axis").data([1]); + xAxisG.enter().append("g") + .attr("class", "x-axis") + .merge(xAxisG) + .attr("transform", "translate(0," + innerHeight() +")") + .call(xAxis); + + // labels X axis + // rotate text if is longer than... + + d3.select(this).select(".x-axis").selectAll("text").each(function(){ + if (this.getBBox().width > xScale.bandwidth()) + d3.selectAll("text").attr("transform", "rotate(-90)") + .attr("y", 0) + .attr("x", -10) + .attr("dy", ".35em") + .style("text-anchor", "end"); + }); + + // adjust margin and x axis title + var maxh = 0; + d3.select(this).select(".x-axis").selectAll("text").each(function(){ + if (this.getBBox().width > maxh) + maxh = this.getBBox().width; + }); + margin.bottom = margin.bottom + maxh; + d3.select(".x-axis").attr("transform", "translate(0," + (innerHeight()) + ")"); + + + g.append("text") + .attr("transform", "translate(" + (innerWidth/2) + ", " + (innerHeight() + margin.bottom - 5) + ")") + .style("font-size", "12px") + .style("text-anchor", "middle") + .text(cat); + + yScale + .domain([0, d3.max(data, function (d){ return d[y] ;})]) + .range([innerHeight(), 0]); + + // Y axis + var yAxisG = g.selectAll(".y-axis").data([1]); + yAxisG.enter().append("g") + .attr("class", "y-axis") + .merge(yAxisG) + .call(yAxis); + + //labels Y axis + var maxw = 0; + d3.select(this).select(".y-axis").selectAll("text").each(function(){ + if (this.getBBox().width > maxw) { + maxw = this.getBBox().width; + } + }); + margin.left = margin.left + maxw; + g.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + var yLabel = function(){ + if (title == "Count"){ + return "Count"; + } else { + return qnt; + }}; + + g.append("text") + .attr("transform", "rotate(-90)") + .attr("x", 0 - (innerHeight()/2)) + .attr("y", 0 - (margin.left)) + .attr("dy", "1em") + .style("font-size", "12px") + .style("text-anchor", "middle") + .text(yLabel); + //div for tooltip + var div = d3.select("body").append("div") + .attr("class", "tooltip") + .style("opacity", 0); + + + // lines + var line = d3.line() + .x(function(d) { return xScale(d[x]); }) + .y(function(d) { return yScale(d[y]); }); + + + g.append("path") + .data([data]) + .attr("fill", "none") + .attr("stroke", "steelblue") + .attr("stroke-linejoin", "round") + .attr("stroke-linecap", "round") + .attr("stroke-width", 1.5) + .attr("transform", "translate(" + (xScale.bandwidth() /2) + ", 0)") + .attr("d", line); + + g.selectAll("dot") + .data(data) + .enter().append("circle") + .attr("r", 3.5) + .attr("transform", "translate(" + (xScale.bandwidth() /2) + ", 0)") + .attr("cx", function(d) { return xScale(d[x]); }) + .attr("cy", function(d) { return yScale(d[y]); }) + .on("mouseover", function(d){ + div.transition() + .duration(200) + .style("opacity", 1); + div.html(cat + ": " + d.key + "
    " + yLabel() + ": " + d.value) + .style("left", (d3.event.pageX) + "px") + .style("top", (d3.event.pageY - 40) + "px"); + }) + .on("mouseout", function(d) { + div.transition() + .duration(500) + .style("opacity", 0); + }); + + }); + } + + my.x = function (_){ + return arguments.length ? (x = _, my) : x; + }; + + my.y = function (_){ + return arguments.length ? (y = _, my) : y; + }; + + my.width = function (_){ + return arguments.length ? (width = _, my) : width; + }; + + my.height = function (_){ + return arguments.length ? (height = _, my) : height; + }; + + my.padding = function (_){ + return arguments.length ? (xScale.padding(_), my) : xScale.padding(); + }; + + return my; + } \ No newline at end of file diff --git a/haitidata/static/js/pie.js b/haitidata/static/js/pie.js new file mode 100644 index 0000000..a3ddc7c --- /dev/null +++ b/haitidata/static/js/pie.js @@ -0,0 +1,416 @@ +function pieChart(cat, qnt, title){ + var width = 900, + height = 500, + margin = {top: 80, right: 100, bottom: 10, left: 10}, + colour = d3.scaleOrdinal(d3.schemeCategory20c), // colour scheme + variable, // value in data that will dictate proportions on chart + category, // compare data by + padAngle = 0.015, // effectively dictates the gap between slices + transTime, // transition time + updateData, + floatFormat = d3.format("." + d3.precisionFixed(0.5) + "f"), + cornerRadius = 3; // sets how rounded the corners are on each slice + + function chart(selection){ + selection.each(function() { + // generate chart + // =========================================================================================== + // Set up constructors for making donut. See https://github.com/d3/d3-shape/blob/master/README.md + var radius = Math.min(width, (height - margin.top)) / 2; + + // creates a new pie generator + var pie = d3.pie() + .value(function(d) { return floatFormat(d[variable]); }) + .sort(null); + + // contructs and arc generator. This will be used for the donut. The difference between outer and inner + // radius will dictate the thickness of the donut + var arc = d3.arc() + .outerRadius(radius * 0.8) + .innerRadius(2) + .cornerRadius(cornerRadius) + .padAngle(padAngle); + + // this arc is used for aligning the text labels + var outerArc = d3.arc() + .outerRadius(radius * 0.83) + .innerRadius(radius * 0.83); + // =========================================================================================== + + // =========================================================================================== + // append the svg object to the selection + var svg = selection.append('svg') + .attr("id", "chart_svg") + .attr('width', width + margin.left + margin.right) + .attr('height', height + margin.top + margin.bottom) + .append('g') + .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); + // =========================================================================================== + + // =========================================================================================== + // g elements to keep elements within svg modular + svg.append('g').attr('class', 'slices'); + svg.append('g').attr('class', 'labelName'); + svg.append('g').attr('class', 'lines'); + // =========================================================================================== + + // =========================================================================================== + // add and colour the donut slices + var path = svg.select('.slices') + .selectAll('path') + .data(pie) + .enter().append('path') + .attr('fill', function(d) { return colour(d.data[category]); }) + .attr('d', arc); + // =========================================================================================== + + // =========================================================================================== + // add text labels + var occupedLabel = []; + var label = svg.select('.labelName').selectAll('text') + .data(pie) + .enter().append('text') + .attr('dy', '.35em') + .html(updateLabelText) + .attr('transform', labelTransform) + .style('text-anchor', function(d) { + // if slice centre is on the left, anchor text to start, otherwise anchor to end + return (midAngle(d)) < Math.PI ? 'start' : 'end'; + }); + // =========================================================================================== + + // =========================================================================================== + // add lines connecting labels to slice. A polyline creates straight lines connecting several points + var occupedPoint = []; + var polyline = svg.select('.lines') + .selectAll('polyline') + .data(pie) + .enter().append('polyline') + .attr('points', calculatePoints); + // =========================================================================================== + + // =========================================================================================== + // add tooltip to mouse events on slices and labels + d3.selectAll('.labelName text, .slices path').call(toolTip); + // =========================================================================================== + + // =========================================================================================== + // FUNCTION TO UPDATE CHART + updateData = function() { + + var updatePath = d3.select('.slices').selectAll('path'); + var updateLines = d3.select('.lines').selectAll('polyline'); + var updateLabels = d3.select('.labelName').selectAll('text'); + + var data0 = path.data(), // store the current data before updating to the new + data1 = pie(data); + + // update data attached to the slices, labels, and polylines. the key function assigns the data to + // the correct element, rather than in order of how the data appears. This means that if a category + // already exists in the chart, it will have its data updated rather than removed and re-added. + updatePath = updatePath.data(data1, key); + updateLines = updateLines.data(data1, key); + updateLabels = updateLabels.data(data1, key); + + // adds new slices/lines/labels + updatePath.enter().append('path') + .each(function(d, i) { this._current = findNeighborArc(i, data0, data1, key) || d; }) + .attr('fill', function(d) { return colour(d.data[category]); }) + .attr('d', arc); + + updateLines.enter().append('polyline') + .each(function(d, i) { this._current = findNeighborArc(i, data0, data1, key) || d; }) + .attr('points', calculatePoints); + + updateLabels.enter().append('text') + .each(function(d, i) { this._current = findNeighborArc(i, data0, data1, key) || d; }) + .html(updateLabelText) + .attr('transform', labelTransform) + .style('text-anchor', function(d) { return (midAngle(d)) < Math.PI ? 'start' : 'end'; }); + + // removes slices/labels/lines that are not in the current dataset + updatePath.exit() + .transition() + .duration(transTime) + .attrTween("d", arcTween) + .remove(); + + updateLines.exit() + .transition() + .duration(transTime) + .attrTween("points", pointTween) + .remove(); + + updateLabels.exit() + .remove(); + + // animates the transition from old angle to new angle for slices/lines/labels + updatePath.transition().duration(transTime) + .attrTween('d', arcTween); + + updateLines.transition().duration(transTime) + .attrTween('points', pointTween); + + updateLabels.transition().duration(transTime) + .attrTween('transform', labelTween) + .styleTween('text-anchor', labelStyleTween); + + updateLabels.html(updateLabelText); // update the label text + + // add tooltip to mouse events on slices and labels + d3.selectAll('.labelName text, .slices path').call(toolTip); + + }; + // =========================================================================================== + // Functions + // calculates the angle for the middle of a slice + function midAngle(d) { return d.startAngle + (d.endAngle - d.startAngle) / 2}; + + // function that creates and adds the tool tip to a selected element + function toolTip(selection) { + + // add tooltip (svg circle element) when mouse enters label or slice + selection.on('mouseenter', function (data) { + + svg.append('rect') + .attr('transform', "translate(250, -200)") + .attr('class', 'toolRect') + .attr("width", 190) + .attr("height", 60) + .style('fill', colour(data.data[category])) // colour based on category mouse is over + .style('fill-opacity', 0.35); + + svg.append('text') + .attr('transform', "translate(400,-175)") + .attr('class', 'toolRect') + .html(toolTipHTML(data)) // add text to the rect + .style('font-size', '.9em') + .style('text-anchor', 'end'); // centres text in tooltip + }); + + // remove the tooltip when mouse leaves the slice/label + selection.on('mouseout', function () { + d3.selectAll('.toolRect').remove(); + }); + } + + // function to create the HTML string for the tool tip. Loops through each key in data object + // and returns the html string key: value + function toolTipHTML(data) { + + var tip = '', + i = 0; + + for (var key in data.data) { + + // if value is a number, format it as a float + var value = (!isNaN(data.data[key])) ? parseFloat(data.data[key]) : data.data[key]; + qnt = (title == "Count" ? "Count" : qnt); + + // leave off 'dy' attr for first tspan so the 'dy' attr on text element works. The 'dy' attr on + // tspan effectively imitates a line break. + if (i === 0) tip += '' + cat + ': ' + value + ''; + else tip += '' + qnt + ': ' + parseFloat(value) + ''; + i++; + } + + return tip; + } + + // calculate the points for the polyline to pass through + function calculatePoints(d) { + // see label transform function for explanations of these three lines. + var pos = outerArc.centroid(d); + var rev_pos = outerArc.centroid(d); + pos[0] = radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1); + // verify label conflict: if overlaps move up of 10 + if (pos[0] < 0 && pos[1] < 0) { + var i = 0; + if (occupedPoint.length >= 1){ + for (i = 0; i < occupedPoint.length; i++){ + if (pos[1] <= (occupedPoint[i] + 10) && (pos[1] >= (occupedPoint[i] - 10))){ + pos[1] = pos[1] - 20; + rev_pos[1] = rev_pos[1] - 20; + } + } + } occupedPoint.push(pos[1]); + } + return [arc.centroid(d), rev_pos, pos] + } + + + function labelTransform(d) { + // effectively computes the centre of the slice. + // see https://github.com/d3/d3-shape/blob/master/README.md#arc_centroid + var pos = outerArc.centroid(d); + // verify label conflict: if overlaps move up of 10 + if (pos[0] < 0 && pos[1] < 0) { + var i = 0; + if (occupedLabel.length >= 1){ + for (i = 0; i < occupedLabel.length; i++){ + if (pos[1] <= (occupedLabel[i] + 10) && (pos[1] >= (occupedLabel[i] - 10))){ + pos[1] = pos[1] - 20; + } + } + } occupedLabel.push(pos[1]); + } + // changes the point to be on left or right depending on where label is. + pos[0] = radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1); + return 'translate(' + pos + ')'; + } + + function updateLabelText(d) { + return d.data[category] + ': ' + floatFormat(d.data[variable]) + ''; + } + + // function that calculates transition path for label and also it's text anchoring + function labelStyleTween(d) { + this._current = this._current || d; + var interpolate = d3.interpolate(this._current, d); + this._current = interpolate(0); + return function(t){ + var d2 = interpolate(t); + return midAngle(d2) < Math.PI ? 'start':'end'; + }; + } + + function labelTween(d) { + this._current = this._current || d; + var interpolate = d3.interpolate(this._current, d); + this._current = interpolate(0); + return function(t){ + var d2 = interpolate(t), + pos = outerArc.centroid(d2); // computes the midpoint [x,y] of the centre line that would be + // generated by the given arguments. It is defined as startangle + endangle/2 and innerR + outerR/2 + pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1); // aligns the labels on the sides + return 'translate(' + pos + ')'; + }; + } + + function pointTween(d) { + this._current = this._current || d; + var interpolate = d3.interpolate(this._current, d); + this._current = interpolate(0); + return function(t){ + var d2 = interpolate(t), + pos = outerArc.centroid(d2); + pos[0] = radius * 0.95 * (midAngle(d2) < Math.PI ? 1 : -1); + return [arc.centroid(d2), outerArc.centroid(d2), pos]; + }; + } + + // function to calculate the tween for an arc's transition. + // see http://bl.ocks.org/mbostock/5100636 for a thorough explanation. + function arcTween(d) { + var i = d3.interpolate(this._current, d); + this._current = i(0); + return function(t) { return arc(i(t)); }; + } + + function findNeighborArc(i, data0, data1, key) { + var d; + return (d = findPreceding(i, data0, data1, key)) ? {startAngle: d.endAngle, endAngle: d.endAngle} + : (d = findFollowing(i, data0, data1, key)) ? {startAngle: d.startAngle, endAngle: d.startAngle} + : null; + } + // Find the element in data0 that joins the highest preceding element in data1. + function findPreceding(i, data0, data1, key) { + var m = data0.length; + while (--i >= 0) { + var k = key(data1[i]); + for (var j = 0; j < m; ++j) { + if (key(data0[j]) === k) return data0[j]; + } + } + } + + function key(d) { + return d.data[category]; + } + + // Find the element in data0 that joins the lowest following element in data1. + function findFollowing(i, data0, data1, key) { + var n = data1.length, m = data0.length; + while (++i < n) { + var k = key(data1[i]); + for (var j = 0; j < m; ++j) { + if (key(data0[j]) === k) return data0[j]; + } + } + } + + // =========================================================================================== + + }); + } + + // getter and setter functions. See Mike Bostocks post "Towards Reusable Charts" for a tutorial on how this works. + chart.width = function(value) { + if (!arguments.length) return width; + width = value; + return chart; + }; + + chart.height = function(value) { + if (!arguments.length) return height; + height = value; + return chart; + }; + + chart.margin = function(value) { + if (!arguments.length) return margin; + margin = value; + return chart; + }; + + chart.radius = function(value) { + if (!arguments.length) return radius; + radius = value; + return chart; + }; + + chart.padAngle = function(value) { + if (!arguments.length) return padAngle; + padAngle = value; + return chart; + }; + + chart.cornerRadius = function(value) { + if (!arguments.length) return cornerRadius; + cornerRadius = value; + return chart; + }; + + chart.colour = function(value) { + if (!arguments.length) return colour; + colour = value; + return chart; + }; + + chart.variable = function(value) { + if (!arguments.length) return variable; + variable = value; + return chart; + }; + + chart.category = function(value) { + if (!arguments.length) return category; + category = value; + return chart; + }; + + chart.transTime = function(value) { + if (!arguments.length) return transTime; + transTime = value; + return chart; + }; + + chart.data = function(value) { + if (!arguments.length) return data; + data = value; + if (typeof updateData === 'function') updateData(); + return chart; + }; + + return chart; +} \ No newline at end of file diff --git a/haitidata/static/less/haitidata.less b/haitidata/static/less/haitidata.less index bfb7224..f2a51af 100755 --- a/haitidata/static/less/haitidata.less +++ b/haitidata/static/less/haitidata.less @@ -175,7 +175,7 @@ form { .navbar-header { i { - color: white; + color: @brand-primary; font-size: 2em; } @@ -712,6 +712,41 @@ footer { .grid-spacer { margin-top: 30px; } + +#accordion { + .panel { + border-radius: 0; + border-top: 2px solid @gray-light; + line-height: 1em; + } + .panel-heading { + background: none; + border: 0; + } + .panel-body { + img { + margin-bottom: 1em; + } + small { + color: @gray; + font-size: 0.825em; + } + form { + font-size: 0.825em; + .form-control { + background: none; + color: @gray-dark; + font-size: 0.825em; + height: 24px; + line-height: 1; + -webkit-appearance:none; + -webkit-box-shadow: none; + box-shadow: none; + } + } + } +} + /* ------ OSGeo Importer ------------------------------ */ body.importer { diff --git a/haitidata/templates/charts_app/chart_base.html b/haitidata/templates/charts_app/chart_base.html new file mode 100644 index 0000000..031c954 --- /dev/null +++ b/haitidata/templates/charts_app/chart_base.html @@ -0,0 +1,12 @@ +{% extends "site_base.html" %} +{% load i18n %} +{% load url from future %} + +{% block title %} {{ block.super }} {% endblock %} + +{% block body_class %}charts{% endblock %} + +{% block body_outer %} +{% block body %}{% endblock body %} +{% block sidebar %}{% endblock sidebar %} +{% endblock body_outer %} diff --git a/haitidata/templates/charts_app/chart_confirm_delete.html b/haitidata/templates/charts_app/chart_confirm_delete.html new file mode 100644 index 0000000..7695d94 --- /dev/null +++ b/haitidata/templates/charts_app/chart_confirm_delete.html @@ -0,0 +1,5 @@ +

    Do you really want to delete this chart?

    +
    + {% csrf_token %} + +
    \ No newline at end of file diff --git a/haitidata/templates/charts_app/chart_detail.html b/haitidata/templates/charts_app/chart_detail.html new file mode 100644 index 0000000..834d1fc --- /dev/null +++ b/haitidata/templates/charts_app/chart_detail.html @@ -0,0 +1,106 @@ +{% extends "charts_app/chart_base.html" %} +{% load i18n %} +{% load dialogos_tags %} +{% load agon_ratings_tags %} +{% load bootstrap_tags %} +{% load url from future %} +{% load base_tags %} +{% load guardian_tags %} + +{% block page-title %} + {% trans "Explore Graphs" %} + +{% endblock %} + +{% block subheader %} +
    +
    +
    +
    +

    {{ object.title }} Title

    +

    + Chart based on {{ object.layer.title }} layer (Category field: {{ object.category }}, Quantity field: {{ object.quantity }}) + + {% if resource.category %} + {% trans "in" %} {{ resource.category }} | + {% endif %} + {% if resource.date %} + {% if resource.date_type == 'creation' %} {% trans "Created on" %} {{ resource.date }} + {% elif resource.date_type == 'publication' %} {% trans "Published on" %} {{ resource.date }} + {% else %} {% trans "Updated on" %} {{ resource.date }} + {% endif %} + {% endif %} +

    + {% block social_links %} + {% if SOCIAL_BUTTONS %} + {% include "social_links.html" %} + {% endif %} + {% endblock %} +
    +
    +
    +
    +{% endblock %} + + + +{% block body_outer %} + + + + + + + + +
    +
    +
    +

    Abstract

    +

    {{ object.abstract }}

    +
    + + +
    + + + + +{% include "charts_app/chart_main_script.html" %} + + + +{% endblock %} diff --git a/haitidata/templates/charts_app/chart_form.html b/haitidata/templates/charts_app/chart_form.html new file mode 100644 index 0000000..ddda0ca --- /dev/null +++ b/haitidata/templates/charts_app/chart_form.html @@ -0,0 +1,87 @@ + + + + + + + + + + +

    {{ layer.title }}

    + +
    +
    + +
    + {% csrf_token %} + {{ form.non_field_errors }} +
    + {{ form.layer.errors }} + {{ form.layer.as_hidden }} +
    +
    + {{ form.title.errors }} + + {{ form.title }} +
    +
    + {{ form.category.errors }} + + +
    +
    + {{ form.quantity.errors }} + + +
    +
    + {{ form.type.errors }} + + {{ form.type }} +
    +
    + {{ form.aggr_type.errors }} + + {{ form.aggr_type }} +
    +
    + {{ form.abstract.errors }} + + {{ form.abstract }} +
    + + +
    + +Download CSV + + + +{% include "charts_app/chart_main_script.html" %} diff --git a/haitidata/templates/charts_app/chart_main_script.html b/haitidata/templates/charts_app/chart_main_script.html new file mode 100644 index 0000000..01d41a3 --- /dev/null +++ b/haitidata/templates/charts_app/chart_main_script.html @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/haitidata/templates/charts_app/chart_update_form.html b/haitidata/templates/charts_app/chart_update_form.html new file mode 100644 index 0000000..0d4f735 --- /dev/null +++ b/haitidata/templates/charts_app/chart_update_form.html @@ -0,0 +1,94 @@ + + + + + + + + + +

    {{ object.layer.title }}

    + +
    +
    + +
    + {% csrf_token %} + {{ form.non_field_errors }} +
    + {{ form.layer.as_hidden }} +
    +
    + {{ form.category.as_hidden }} +
    +
    + {{ form.quantity.as_hidden }} +
    +
    + {{ form.title.errors }} + + {{ form.title }} +
    +
    + {{ form.type.errors }} + + {{ form.type }} +
    +
    + {{ form.aggr_type.errors }} + + {{ form.aggr_type }} +
    +
    + {{ form.abstract.errors }} + + {{ form.abstract }} +
    + +
    + +Download CSV +
    + +
    +Delete this Chart + + + + +{% include "charts_app/chart_main_script.html" %} + + + + \ No newline at end of file diff --git a/haitidata/templates/layers/layer_base.html b/haitidata/templates/layers-paolo/layer_base.html similarity index 100% rename from haitidata/templates/layers/layer_base.html rename to haitidata/templates/layers-paolo/layer_base.html diff --git a/haitidata/templates/layers/layer_change_poc.html b/haitidata/templates/layers-paolo/layer_change_poc.html similarity index 100% rename from haitidata/templates/layers/layer_change_poc.html rename to haitidata/templates/layers-paolo/layer_change_poc.html diff --git a/haitidata/templates/layers-paolo/layer_detail.html b/haitidata/templates/layers-paolo/layer_detail.html new file mode 100644 index 0000000..17d24a8 --- /dev/null +++ b/haitidata/templates/layers-paolo/layer_detail.html @@ -0,0 +1,708 @@ +{% extends "layers/layer_base.html" %} +{% load i18n %} +{% load dialogos_tags %} +{% load agon_ratings_tags %} +{% load bootstrap_tags %} +{% load pagination_tags %} +{% load url from future %} +{% load base_tags %} +{% load guardian_tags %} + +{% block title %}{{ block.super }} {% trans "Layers" %} | {{ resource.title|default:resource.typename }}{% endblock %} +{% block page-title %} + {% trans "Explore Layers" %} + +{% endblock %} + +{% block head %} + + {% if TWITTER_CARD %} + {% include "base/_resourcebase_twittercard.html" %} + {% endif %} + + {% if OPENGRAPH_ENABLED %} + {% include "base/_resourcebase_opengraph.html" %} + {% endif %} + + {% if preview == 'geoext' %} + {% include "layers/layer_geoext_map.html" %} + {% elif preview == 'OL3' %} + {% include "layers/layer_ol3_map.html" %} + {% elif preview == 'react' %} + {% include 'geonode-client/layer_map.html' %} + {% else %} + {% include "layers/layer_leaflet_map.html" %} + {% endif %} +{{ block.super }} +{% endblock %} + + +{% block body_class %}layers{% endblock %} + +{% block subheader %} +
    +
    +
    +
    +

    {{ resource.title|default:resource.typename }}

    +

    + {{ resource.service_type }} + {% if resource.category %} + {% trans "in" %} {{ resource.category }} | + {% endif %} + {% if resource.date %} + {% if resource.date_type == 'creation' %} {% trans "Created on" %} {{ resource.date }} + {% elif resource.date_type == 'publication' %} {% trans "Published on" %} {{ resource.date }} + {% else %} {% trans "Updated on" %} {{ resource.date }} + {% endif %} + {% endif %} +

    + {% block social_links %} + {% if SOCIAL_BUTTONS %} + {% include "social_links.html" %} + {% endif %} + {% endblock %} +
    +
    +
    +
    +{% endblock %} + +{% block body_outer %} +
    + + + +
    +
    +
    +
    + + + + + + + + +
    + {% include "_actions.html" %} +
    + +
    + +
    + {% include "base/resourcebase_info_panel.html" %} +
    + + {% if resource.is_mosaic %} +
    +
    + +
    + {% csrf_token %} +   +
    + {% if filter %} + {% trans "Active Filter:" %} {{ filter }} + {% endif %} +
    + + + + + + + + + + + + {% autopaginate all_granules.features 10 %} + {% for feature in granules.features %} + + + + + + + + {% endfor %} + +
    {% trans "Bounding Box" %} (minX,minY,maxX,maxY){% trans "Time" %}{% trans "Actions" %}
    {% if feature.properties.time %}{{ feature.properties.time }}{% endif %} + Granule Preview + Granule Remove + +
    +
    + {% paginate %} +
    + {% endif %} + +
    + {% if resource.is_mosaic %} + + + + + + + + + + + + + + + + +
    {% trans "Dimension" %}{% trans "Enabled" %}{% trans "Regex" %}
    {% trans "TIME" %}{% if resource.has_time %}{% trans "True" %}{% else %}{% trans "False" %}{% endif %}{% if resource.time_regex %}{{ resource.time_regex }}{% else %} {% endif %}
    + {% endif %} + {% if resource.attribute_set.all %} + + + + + + + {% if wps_enabled %} + + + + + {% endif %} + + + + {% for attribute in resource.attributes %} + + + + + {% if wps_enabled %} + {% if attribute.unique_values == "NA" %} + + {% else %} + + {% endif %} + + + + {% endif %} + + {% endfor %} + +
    {% trans "Attribute Name" %}{% trans "Label" %}{% trans "Description" %}{% trans "Range" %}{% trans "Average" %}{% trans "Median" %}{% trans "Standard Deviation" %}
    {{ attribute }}{{ attribute.attribute_label|default:"" }}{{ attribute.description|default:"" }}{{ attribute.unique_values }} + + {{ attribute.average|floatformat:"2" }}{{ attribute.median|floatformat:"2" }}{{ attribute.stddev|floatformat:"2" }}
    + {% endif %} +
    + +
    + {% with resource as obj %} + {% include "_comments.html" %} + {% endwith %} +
    + + {% if GEOGIG_ENABLED and resource.link_set.geogig %} + {% with "_geogig_layer_detail.html" as geogig_template %} +
    + {% with resource as obj %} + {% include geogig_template %} + {% endwith %} +
    + {% endwith %} + {% endif %} + +
    + + {% if request.user.is_authenticated %} +

    {% trans "Rate this layer" %}

    + {% user_rating request.user resource "layer" as user_layer_rating %} +
    + {% endif %} +

    {% trans 'Average Rating' %}

    + {% overall_rating resource "layer" as layer_rating %} + {% num_ratings resource as num_votes %} +
    ({{num_votes}}) +
    + +
    + +
    + +
    + +
    +
    + +
    +
    + + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. +
    +
    +
    +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    +
    + +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + + +
    +
    +
    +
    + +
      + {% if resource.storeType != "remoteStore" %} +
    • + {% if links or links_download %} + + {% else %} + {% if request.user.is_authenticated %} + + {% endif %} + {% endif %} +
    • + {% endif %} + + + {% if GEOSERVER_BASE_URL %} + {% get_obj_perms request.user for resource.layer as "layer_perms" %} + {% endif %} + +
    • + + + +
    • + + {% if "change_resourcebase_metadata" in perms_list or "change_resourcebase" in perms_list or "delete_resourcebase" in perms_list or "change_layer_style" in layer_perms %} +
    • + +
    • + + {% endif %} + +
    • + +
    • + + + + {% if resource.get_legend %} +
    • +

      {%trans "Legend" %}

      +

      {{ style.sld_title }}

      + {% if resource.get_legend.link_type == 'image' %} + {% if request.user.is_authenticated and 'access_token' in request.session %} +

      + {% else %} +

      + {% endif %} + {% else %} +
      + {% endif %} +
    • + {% endif %} +
    • +

      {% trans "Maps using this layer" %}

      + {% if resource.maps %} +

      {% trans "List of maps using this layer:" %}

      + {% endif %} +
        + {% for maplayer in resource.maps %} +
      • {{ maplayer.map.title }}
      • + {% empty %} +
      • {% trans "This layer is not currently used in any maps." %}
      • + {% endfor %} +
      +
    • +
    • +

      {% trans "Create a map using this layer" %}

      +

      {% trans "Click the button below to generate a new map based on this layer." %}

      + {% trans "Create a Map" %} +
    • + + {% if documents.count > 0 %} +
    • +

      {% trans "Documents related to this layer" %}

      +

      {% trans "List of documents related to this layer:" %}

      + +
    • + {% endif %} + + {% if user.is_authenticated %} +
    • +

      {% trans "Styles" %}

      +

      {% trans "The following styles are associated with this layer. Choose a style to view it in the preview map." %}

      +
        + {% for style in resource.styles.all %} +
      • + {% if resource.default_style == style %} + + (default style) + {% else %} + + {% endif %} + {{ style.sld_title }} +
      • + {% empty %} +
      • {% trans "No styles associated with this layer" %}
      • + {% endfor %} +
      +
    • + {% endif %} + + {% if GEONODE_SECURITY_ENABLED %} + {% if "change_resourcebase_permissions" in perms_list %} +
    • +

      {% trans "Permissions" %}

      +

      {% trans "Click the button below to change the permissions of this layer." %}

      +

      {% trans "Change Layer Permissions" %}

      +
    • + {% endif %} + {% endif %} + + {% if resource.storeType == "remoteStore" %} +
    • +

      {% trans "External service layer" %}

      +
      {% trans "Source" %}: {{ resource.service.title }}
      +
      {% trans "Type" %}: {{ resource.service.type }}
      +
    • + {% endif %} + + {% include "base/_resourcebase_contact_snippet.html" %} +
    + + + {% if GEONODE_SECURITY_ENABLED %} + {% include "_permissions_form.html" %} + {% endif %} + +
    +
    +{% endblock %} + +{% block extra_script %} + + {% if GEOGIG_ENABLED and resource.link_set.geogig %} + {% with "_geogig_scripts.html" as geogig_scripts_template %} + {% include geogig_scripts_template %} + {% endwith %} + {% endif %} + + {% if request.user.is_authenticated %} + {% user_rating_js request.user resource "layer" %} + {% else %} + {% overall_rating resource 'layer' as the_layer_rating %} + {% endif %} + {% include 'rating.html' %} + {% include 'request_download.html' %} + + + {% if GEONODE_SECURITY_ENABLED %} + {% include "_permissions_form_js.html" %} + {% endif %} +{% endblock extra_script %} diff --git a/haitidata/templates/layers-paolo/layer_detail.html.save b/haitidata/templates/layers-paolo/layer_detail.html.save new file mode 100644 index 0000000..9662e80 --- /dev/null +++ b/haitidata/templates/layers-paolo/layer_detail.html.save @@ -0,0 +1,601 @@ +{% extends "layers/layer_base.html" %} +{% load i18n %} +{% load dialogos_tags %} +{% load agon_ratings_tags %} +{% load bootstrap_tags %} +{% load pagination_tags %} +{% load url from future %} +{% load base_tags %} +{% load guardian_tags %} + +{% block title %}{{ block.super }} {% trans "Layers" %} | {{ resource.title|default:resource.typename }}{% endblock %} +{% block page-title %}{% trans "Explore Layers" %}{% endblock %} + +{% block head %} + + {% if TWITTER_CARD %} + {% include "base/_resourcebase_twittercard.html" %} + {% endif %} + + {% if OPENGRAPH_ENABLED %} + {% include "base/_resourcebase_opengraph.html" %} + {% endif %} + + {% if preview == 'geoext' %} + {% include "layers/layer_geoext_map.html" %} + {% elif preview == 'OL3' %} + {% include "layers/layer_ol3_map.html" %} + {% elif preview == 'react' %} + {% include 'geonode-client/layer_map.html' %} + {% else %} + {% include "layers/layer_leaflet_map.html" %} + {% endif %} +{{ block.super }} +{% endblock %} + + +{% block body_class %}layers{% endblock %} + +{% block subheader %} +
    +
    +
    +
    +

    {{ resource.title|default:resource.typename }}

    +

    + {{ resource.service_type }} + {% if resource.category %} + {% trans "in" %} {{ resource.category }} | + {% endif %} + {% if resource.date %} + {% if resource.date_type == 'creation' %} {% trans "Created on" %} {{ resource.date }} + {% elif resource.date_type == 'publication' %} {% trans "Published on" %} {{ resource.date }} + {% else %} {% trans "Updated on" %} {{ resource.date }} + {% endif %} + {% endif %} +

    + {% block social_links %} + {% if SOCIAL_BUTTONS %} + {% include "social_links.html" %} + {% endif %} + {% endblock %} +
    +
    +
    +
    +{% endblock %} + +{% block body_outer %} +
    +
    + +
    +
    +
    + +
    + {% include "_actions.html" %} +
    + +
    + +
    + {% include "base/resourcebase_info_panel.html" %} +
    + + {% if resource.is_mosaic %} +
    +
    + +
    + {% csrf_token %} +   +
    + {% if filter %} + {% trans "Active Filter:" %} {{ filter }} + {% endif %} +
    + + + + + + + + + + + + {% autopaginate all_granules.features 10 %} + {% for feature in granules.features %} + + + + + + + + {% endfor %} + +
    {% trans "Bounding Box" %} (minX,minY,maxX,maxY){% trans "Time" %}{% trans "Actions" %}
    {% if feature.properties.time %}{{ feature.properties.time }}{% endif %} + Granule Preview + Granule Remove + +
    +
    + {% paginate %} +
    + {% endif %} + +
    + {% if resource.is_mosaic %} + + + + + + + + + + + + + + + + +
    {% trans "Dimension" %}{% trans "Enabled" %}{% trans "Regex" %}
    {% trans "TIME" %}{% if resource.has_time %}{% trans "True" %}{% else %}{% trans "False" %}{% endif %}{% if resource.time_regex %}{{ resource.time_regex }}{% else %} {% endif %}
    + {% endif %} + {% if resource.attribute_set.all %} + + + + + + + {% if wps_enabled %} + + + + + {% endif %} + + + + {% for attribute in resource.attributes %} + + + + + {% if wps_enabled %} + {% if attribute.unique_values == "NA" %} + + {% else %} + + {% endif %} + + + + {% endif %} + + {% endfor %} + +
    {% trans "Attribute Name" %}{% trans "Label" %}{% trans "Description" %}{% trans "Range" %}{% trans "Average" %}{% trans "Median" %}{% trans "Standard Deviation" %}
    {{ attribute }}{{ attribute.attribute_label|default:"" }}{{ attribute.description|default:"" }}{{ attribute.unique_values }} + + {{ attribute.average|floatformat:"2" }}{{ attribute.median|floatformat:"2" }}{{ attribute.stddev|floatformat:"2" }}
    + {% endif %} +
    + +
    + {% with resource as obj %} + {% include "_comments.html" %} + {% endwith %} +
    + + {% if GEOGIG_ENABLED and resource.link_set.geogig %} + {% with "_geogig_layer_detail.html" as geogig_template %} +
    + {% with resource as obj %} + {% include geogig_template %} + {% endwith %} +
    + {% endwith %} + {% endif %} + +
    + + {% if request.user.is_authenticated %} +

    {% trans "Rate this layer" %}

    + {% user_rating request.user resource "layer" as user_layer_rating %} +
    + {% endif %} +

    {% trans 'Average Rating' %}

    + {% overall_rating resource "layer" as layer_rating %} + {% num_ratings resource as num_votes %} +
    ({{num_votes}}) +
    + +
    + +
    + +
    + +
      + {% if resource.storeType != "remoteStore" %} +
    • + {% if links or links_download %} + + {% else %} + {% if request.user.is_authenticated %} + + {% endif %} + {% endif %} +
    • + {% endif %} + + + {% if GEOSERVER_BASE_URL %} + {% get_obj_perms request.user for resource.layer as "layer_perms" %} + {% endif %} + +
    • + + + +
    • + + {% if "change_resourcebase_metadata" in perms_list or "change_resourcebase" in perms_list or "delete_resourcebase" in perms_list or "change_layer_style" in layer_perms %} +
    • + +
    • + + {% endif %} + +
    • + +
    • + + + + {% if resource.get_legend %} +
    • +

      {%trans "Legend" %}

      +

      {{ style.sld_title }}

      + {% if resource.get_legend.link_type == 'image' %} + {% if request.user.is_authenticated and 'access_token' in request.session %} +

      + {% else %} +

      + {% endif %} + {% else %} +
      + {% endif %} +
    • + {% endif %} +
    • +

      {% trans "Maps using this layer" %}

      + {% if resource.maps %} +

      {% trans "List of maps using this layer:" %}

      + {% endif %} +
        + {% for maplayer in resource.maps %} +
      • {{ maplayer.map.title }}
      • + {% empty %} +
      • {% trans "This layer is not currently used in any maps." %}
      • + {% endfor %} +
      +
    • +
    • +

      {% trans "Create a map using this layer" %}

      +

      {% trans "Click the button below to generate a new map based on this layer." %}

      + {% trans "Create a Map" %} +
    • + + {% if documents.count > 0 %} +
    • +

      {% trans "Documents related to this layer" %}

      +

      {% trans "List of documents related to this layer:" %}

      + +
    • + {% endif %} + + {% if user.is_authenticated %} +
    • +

      {% trans "Styles" %}

      +

      {% trans "The following styles are associated with this layer. Choose a style to view it in the preview map." %}

      +
        + {% for style in resource.styles.all %} +
      • + {% if resource.default_style == style %} + + (default style) + {% else %} + + {% endif %} + {{ style.sld_title }} +
      • + {% empty %} +
      • {% trans "No styles associated with this layer" %}
      • + {% endfor %} +
      +
    • + {% endif %} + + {% if GEONODE_SECURITY_ENABLED %} + {% if "change_resourcebase_permissions" in perms_list %} +
    • +

      {% trans "Permissions" %}

      +

      {% trans "Click the button below to change the permissions of this layer." %}

      +

      {% trans "Change Layer Permissions" %}

      +
    • + {% endif %} + {% endif %} + + {% if resource.storeType == "remoteStore" %} +
    • +

      {% trans "External service layer" %}

      +
      {% trans "Source" %}: {{ resource.service.title }}
      +
      {% trans "Type" %}: {{ resource.service.type }}
      +
    • + {% endif %} + + {% include "base/_resourcebase_contact_snippet.html" %} +
    + + + {% if GEONODE_SECURITY_ENABLED %} + {% include "_permissions_form.html" %} + {% endif %} + +
    +
    +{% endblock %} + +{% block extra_script %} + + {% if GEOGIG_ENABLED and resource.link_set.geogig %} + {% with "_geogig_scripts.html" as geogig_scripts_template %} + {% include geogig_scripts_template %} + {% endwith %} + {% endif %} + + {% if request.user.is_authenticated %} + {% user_rating_js request.user resource "layer" %} + {% else %} + {% overall_rating resource 'layer' as the_layer_rating %} + {% endif %} + {% include 'rating.html' %} + {% include 'request_download.html' %} + + + {% if GEONODE_SECURITY_ENABLED %} + {% include "_permissions_form_js.html" %} + {% endif %} +{% endblock extra_script %} diff --git a/haitidata/templates/layers/layer_geoext_map.html b/haitidata/templates/layers-paolo/layer_geoext_map.html similarity index 100% rename from haitidata/templates/layers/layer_geoext_map.html rename to haitidata/templates/layers-paolo/layer_geoext_map.html diff --git a/haitidata/templates/layers/layer_granule_remove.html b/haitidata/templates/layers-paolo/layer_granule_remove.html similarity index 100% rename from haitidata/templates/layers/layer_granule_remove.html rename to haitidata/templates/layers-paolo/layer_granule_remove.html diff --git a/haitidata/templates/layers/layer_leaflet_map.html b/haitidata/templates/layers-paolo/layer_leaflet_map.html similarity index 100% rename from haitidata/templates/layers/layer_leaflet_map.html rename to haitidata/templates/layers-paolo/layer_leaflet_map.html diff --git a/haitidata/templates/layers/layer_list.html b/haitidata/templates/layers-paolo/layer_list.html similarity index 100% rename from haitidata/templates/layers/layer_list.html rename to haitidata/templates/layers-paolo/layer_list.html diff --git a/haitidata/templates/layers/layer_map.html b/haitidata/templates/layers-paolo/layer_map.html similarity index 100% rename from haitidata/templates/layers/layer_map.html rename to haitidata/templates/layers-paolo/layer_map.html diff --git a/haitidata/templates/layers/layer_metadata.html b/haitidata/templates/layers-paolo/layer_metadata.html similarity index 100% rename from haitidata/templates/layers/layer_metadata.html rename to haitidata/templates/layers-paolo/layer_metadata.html diff --git a/haitidata/templates/layers/layer_metadata_detail.html b/haitidata/templates/layers-paolo/layer_metadata_detail.html similarity index 100% rename from haitidata/templates/layers/layer_metadata_detail.html rename to haitidata/templates/layers-paolo/layer_metadata_detail.html diff --git a/haitidata/templates/layers/layer_ol3_map.html b/haitidata/templates/layers-paolo/layer_ol3_map.html similarity index 100% rename from haitidata/templates/layers/layer_ol3_map.html rename to haitidata/templates/layers-paolo/layer_ol3_map.html diff --git a/haitidata/templates/layers/layer_remove.html b/haitidata/templates/layers-paolo/layer_remove.html similarity index 100% rename from haitidata/templates/layers/layer_remove.html rename to haitidata/templates/layers-paolo/layer_remove.html diff --git a/haitidata/templates/layers/layer_replace.html b/haitidata/templates/layers-paolo/layer_replace.html similarity index 100% rename from haitidata/templates/layers/layer_replace.html rename to haitidata/templates/layers-paolo/layer_replace.html diff --git a/haitidata/templates/layers/layer_style_manage.html b/haitidata/templates/layers-paolo/layer_style_manage.html similarity index 100% rename from haitidata/templates/layers/layer_style_manage.html rename to haitidata/templates/layers-paolo/layer_style_manage.html diff --git a/haitidata/templates/layers/wps_execute_gs_aggregate.xml b/haitidata/templates/layers-paolo/wps_execute_gs_aggregate.xml similarity index 100% rename from haitidata/templates/layers/wps_execute_gs_aggregate.xml rename to haitidata/templates/layers-paolo/wps_execute_gs_aggregate.xml diff --git a/haitidata/templates/layers/wps_execute_gs_unique.xml b/haitidata/templates/layers-paolo/wps_execute_gs_unique.xml similarity index 100% rename from haitidata/templates/layers/wps_execute_gs_unique.xml rename to haitidata/templates/layers-paolo/wps_execute_gs_unique.xml diff --git a/haitidata/templates/layers/layer_detail.html b/haitidata/templates/layers/layer_detail.html index 2ae8065..6725573 100644 --- a/haitidata/templates/layers/layer_detail.html +++ b/haitidata/templates/layers/layer_detail.html @@ -8,8 +8,7 @@ {% load base_tags %} {% load guardian_tags %} -{% block title %}{{ block.super }} {% trans "Layers" %} | {{ resource.title|default:resource.typename }}{% endblock %} -{% block page-title %}{% trans "Explore Layers" %}{% endblock %} +{% block title %}{{ resource.title|default:resource.typename }} — {{ block.super }}{% endblock %} {% block head %} @@ -36,36 +35,11 @@ {% block body_class %}layers{% endblock %} -{% block subheader %} -
    -
    -
    -
    -

    {{ resource.title|default:resource.typename }}

    -

    - {{ resource.service_type }} - {% if resource.category %} - {% trans "in" %} {{ resource.category }} | - {% endif %} - {% if resource.date %} - {% if resource.date_type == 'creation' %} {% trans "Created on" %} {{ resource.date }} - {% elif resource.date_type == 'publication' %} {% trans "Published on" %} {{ resource.date }} - {% else %} {% trans "Updated on" %} {{ resource.date }} - {% endif %} - {% endif %} -

    - {% block social_links %} - {% if SOCIAL_BUTTONS %} - {% include "social_links.html" %} - {% endif %} - {% endblock %} -
    -
    -
    -
    -{% endblock %} - {% block body_outer %} + +
    @@ -227,6 +201,24 @@ {% endif %} + {% block social_links %} + {% if SOCIAL_BUTTONS %} + {% include "social_links.html" %} + {% endif %} + {% endblock %} + +
    +
      + {% for chart in resource.chart_set.all %} +
    • {{ chart.get_type_display }}, Category field = {{chart.category}}, Quantity field = {{chart.quantity}}
    • + {% empty %} +
    • No charts available at this time

    • + {% endfor %} +
    + + Generate New Chart +
    +
    {% with resource as obj %} {% include "_comments.html" %} @@ -266,8 +258,8 @@ {% if resource.storeType != "remoteStore" %}
  • {% if links or links_download %} - - {% else %} + + {% else %} {% if request.user.is_authenticated %} {% endif %} @@ -279,7 +271,7 @@
  • +
  • +

    {% trans "Charts with this layer" %}

    + {% if resource.maps %} +

    {% trans "List of chart with this layer:" %}

    + {% endif %} +
      + {% for chart in resource.chart_set.all %} +
    • {{ chart.get_type_display }}, Category field = {{chart.category}}, Quantity field = {{chart.quantity}}
    • + {% empty %} +
    • No charts available at this time

    • + {% endfor %} +
    +
  • +
  • +

    {% trans "Create a chart using this layer" %}

    +

    {% trans "Click the button below to generate a new chart based on this layer, choosing a quantity and a category field." %}

    + +
    +
    + {% csrf_token %} +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +

    Suggested title:

    +

    Text_di provas

    +
    +
    +
    + +
    + + +
    +
    + + +
    +
    +
  • + + + + {% if documents.count > 0 %}
  • {% trans "Documents related to this layer" %}

    @@ -542,6 +731,7 @@ {% endblock %} {% block extra_script %} +{{ block.super }} {% if GEOGIG_ENABLED and resource.link_set.geogig %} {% with "_geogig_scripts.html" as geogig_scripts_template %} diff --git a/haitidata/templates/site_base.html b/haitidata/templates/site_base.html index ace650b..8e8b490 100644 --- a/haitidata/templates/site_base.html +++ b/haitidata/templates/site_base.html @@ -47,79 +47,79 @@
  • -
  • - - -
  • - {% block extra_tab %} - {% endblock %} - {% endblock %} - - -
    -
    - - {% endblock header %} - + +
  • + + +
  • + {% block extra_tab %} + {% endblock %} + {% endblock %} + + +
    + + +{% endblock header %} + - {% block middle %} +{% block middle %}

    {% block page-title %}{% endblock %}

    @@ -132,12 +132,12 @@ {% include "_messages.html" %} {% block body_outer %}
    -
    - {% block body %}{% endblock %} -
    -
    - {% block sidebar %}{% endblock %} -
    +
    + {% block body %}{% endblock %} +
    +
    + {% block sidebar %}{% endblock %} +
    {% endblock %}
    diff --git a/haitidata/urls.py b/haitidata/urls.py index 2d53e1e..6acf437 100644 --- a/haitidata/urls.py +++ b/haitidata/urls.py @@ -8,6 +8,9 @@ urlpatterns = patterns('', url(r'^/?$', TemplateView.as_view(template_name='site_index.html'), name='home'), - ) + urlpatterns + ) + urlpatterns + [ + url(r'^chart/', include('charts_app.urls')), + url(r'^table/', include('wfs_harvest.urls')) + ] urlpatterns += importer_urlpatterns diff --git a/wfs_harvest/__init__.py b/wfs_harvest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wfs_harvest/admin.py b/wfs_harvest/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/wfs_harvest/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/wfs_harvest/migrations/__init__.py b/wfs_harvest/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wfs_harvest/models.py b/wfs_harvest/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/wfs_harvest/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/wfs_harvest/templates/wfs-harvest/field_choice.html b/wfs_harvest/templates/wfs-harvest/field_choice.html new file mode 100644 index 0000000..b9312ef --- /dev/null +++ b/wfs_harvest/templates/wfs-harvest/field_choice.html @@ -0,0 +1,41 @@ +

    Field choice

    +

    Choose category field

    + +
    + +

    Choose quantity field

    + +
    +
    + Build Chart + Add Chart +
    + \ No newline at end of file diff --git a/wfs_harvest/tests.py b/wfs_harvest/tests.py new file mode 100644 index 0000000..fb01967 --- /dev/null +++ b/wfs_harvest/tests.py @@ -0,0 +1,20 @@ +import urllib + +from django.test import SimpleTestCase +from django.core.urlresolvers import reverse + +from . import views + +# Create your tests here. +class GetFieldsView(SimpleTestCase): + def test_non_existing_lyr(self): + # url = reverse(views.get_fields) + enc_lyrname = urllib.urlencode({'lyrname': 'idontexist'}) + # complete_url = url + '?' + enc_lyrname + # self.client.get('/table') + # self.client.get(complete_url) + self.assertTrue(False) + + def test_non_existing_field(self): + # self.client.get(reverse('wfs_harvest:get_fields')) + self.assertTrue(False) \ No newline at end of file diff --git a/wfs_harvest/urls.py b/wfs_harvest/urls.py new file mode 100644 index 0000000..c29d41e --- /dev/null +++ b/wfs_harvest/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url(r'^$', views.get_wfs_csv), + url(r'^fields$', views.get_fields) + ] diff --git a/wfs_harvest/utils.py b/wfs_harvest/utils.py new file mode 100644 index 0000000..c599214 --- /dev/null +++ b/wfs_harvest/utils.py @@ -0,0 +1,56 @@ +import urllib +import xml.etree.ElementTree as ET + +from geonode.layers.models import Layer + + +def get_fields(layer_id): + layer = Layer.objects.get(pk=layer_id) + lyrname = layer.typename + params = urllib.urlencode({ + 'service':'WFS', + 'version':'1.1.0', + 'request':'DescribeFeatureType', + 'typename':lyrname + }) + + wfs_request = urllib.urlopen('http://localhost/geoserver/ows?%s' + % params) + content = wfs_request.read() + + root = ET.fromstring(content) + namespace = root.tag.split('}')[0] + '}' + xpath = ('./{0}complexType/{0}complexContent/' + '{0}extension/{0}sequence/{0}element').format(namespace) + numeric_types = ['byte', 'decimal', 'int', 'integer', 'long', 'short', + 'boolean', 'double', 'float'] + fields = {} + for element in root.findall(xpath): + type = element.attrib['type'] + if not type.startswith('gml:'): + fields[element.attrib['name']] = type[4:] + + fieldnames = fields.keys() + num_fieldnames = [k for k, v in fields.iteritems() if v in numeric_types] + + return fieldnames, num_fieldnames + + +def get_featno(layer_id): + lyr = Layer.objects.get(pk=layer_id) + wfs_baseurl = lyr.link_set.get(link_type='OGC:WFS').url + + params = urllib.urlencode({ + 'service': 'WFS', + 'version': '1.1.0', + 'request': 'GetFeature', + 'typename': lyr.typename, + 'resultType': 'hits' + }) + + wfs_count_request = urllib.urlopen(wfs_baseurl + '?%s' % params) + content = wfs_count_request.read() + + root = ET.fromstring(content) + + return int(root.attrib['numberOfFeatures']) diff --git a/wfs_harvest/views.py b/wfs_harvest/views.py new file mode 100644 index 0000000..b11b404 --- /dev/null +++ b/wfs_harvest/views.py @@ -0,0 +1,69 @@ +import urllib +import xml.etree.ElementTree as ET + +from django.http import HttpResponse +from django.shortcuts import render +from django.conf import settings + +from geonode.layers.models import Layer + + +def get_fields(request): + lyrname = request.GET['lyrname'] + params = urllib.urlencode({ + 'service':'WFS', + 'version':'1.1.0', + 'request':'DescribeFeatureType', + 'typename':lyrname + }) + + wfs_request = urllib.urlopen('http://localhost/geoserver/ows?%s' + % params) + content = wfs_request.read() + + root = ET.fromstring(content) + namespace = root.tag.split('}')[0] + '}' + xpath = ('./{0}complexType/{0}complexContent/' + '{0}extension/{0}sequence/{0}element').format(namespace) + numeric_types = ['byte', 'decimal', 'int', 'integer', 'long', 'short', + 'boolean', 'double', 'float'] + fields = {} + for element in root.findall(xpath): + type = element.attrib['type'] + if not type.startswith('gml:'): + fields[element.attrib['name']] = type[4:] + + fieldnames = fields.keys() + num_fieldnames = [k for k, v in fields.iteritems() if v in numeric_types] + + context = { + 'lyrname': lyrname, + 'fieldnames': fieldnames, + 'num_fieldnames': num_fieldnames + } + return render(request, 'wfs_harvest/field_choice.html', context) + + +def get_wfs_csv(request): + qdict = request.GET + typename = qdict['lyrname'] + category_field = qdict['category'] + quantity_field = qdict['quantity'] + + params = urllib.urlencode({ + 'service':'WFS', + 'version':'1.1.0', + 'request':'GetFeature', + 'typename':typename, + 'propertyName':category_field + ',' + quantity_field, + 'outputFormat':'csv', + 'maxFeatures': str(settings.MAX_CSV_RECORDS) + }) + + lyr = Layer.objects.get(typename=typename) + wfs_baseurl = lyr.link_set.get(link_type='OGC:WFS').url + wfs_request = urllib.urlopen(wfs_baseurl + '?%s' % params) + content = wfs_request.read() + response = HttpResponse(content, content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="myfile.csv"' + return response From d63421a37e70f9c31a11820ade13d1870b0053a9 Mon Sep 17 00:00:00 2001 From: Emere Date: Mon, 3 Jul 2017 10:53:10 +0200 Subject: [PATCH 02/11] Added Simone's commits --- .gitignore | 3 + .idea/vcs.xml | 6 ++ charts_app/models.py | 28 ++++++- charts_app/urls.py | 8 +- charts_app/views.py | 84 +------------------ haitidata/settings.py | 5 +- .../templates/charts_app/chart_base.html | 2 +- .../templates/charts_app/chart_list.html | 51 +++++++++++ .../templates/geonode-client/layer_map.html | 4 +- wfs_harvest/views.py | 13 ++- 10 files changed, 110 insertions(+), 94 deletions(-) create mode 100644 .idea/vcs.xml create mode 100644 haitidata/templates/charts_app/chart_list.html diff --git a/.gitignore b/.gitignore index b903977..9ce3160 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,6 @@ geoserver/ uploaded/ development.db development.db-journal + +# PyCharm +.idea diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/charts_app/models.py b/charts_app/models.py index 298ff2b..e8200a8 100644 --- a/charts_app/models.py +++ b/charts_app/models.py @@ -3,10 +3,11 @@ from django.core.urlresolvers import reverse from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from django.forms import ModelForm, ValidationError as VE from geonode.layers.models import Layer -from wfs_harvest.utils import get_featno +from wfs_harvest.utils import get_featno, get_fields def validate_wfs(value): @@ -51,3 +52,28 @@ class Chart(models.Model): def get_absolute_url(self): return reverse('chart_detail', kwargs={'pk': self.pk}) + +class ChartForm(ModelForm): + def clean(self): + cleaned_data = super(ChartForm, self).clean() + layer = cleaned_data.get('layer') + category = cleaned_data.get('category') + quantity = cleaned_data.get('quantity') + + categories, quantities = get_fields(layer) + + if category not in categories: + lyr = Layer.objects.get(pk=layer) + raise VE(_('%(lyr_title)s does not contain the field %(category)s'), + code='non_extant_category_field', + params={'lyr_title': lyr.title, 'category': category}) + + if quantity not in quantities: + lyr = Layer.objects.get(pk=layer) + raise VE(_('%(lyr_title)s does not contain the field %(quantity)s'), + code='non_extant_quantity_field', + params={'lyr_title': lyr.title, 'quantity': quantity}) + + class Meta: + model = Chart + fields = '__all__' \ No newline at end of file diff --git a/charts_app/urls.py b/charts_app/urls.py index 9d7ab99..842fce4 100644 --- a/charts_app/urls.py +++ b/charts_app/urls.py @@ -2,13 +2,7 @@ from django.conf.urls import url from . import views -urlpatterns = [url(r'^sample_csv$', views.get_csv, name='get_csv'), - url(r'^sample_csv2$', views.get_csv_aggr, name='get_csv2'), - url(r'^pie_chart_v1$', views.pie_chart_v1), - url(r'^donut_chart_v1$', views.donut_chart_v1), - url(r'^pie_chart_v2$', views.pie_chart_v2), - url(r'^donut_chart_v2$', views.donut_chart_v2), - url(r'^(?P[0-9]+)/$', views.ChartDetailView.as_view(), name='chart_detail'), +urlpatterns = [url(r'^(?P[0-9]+)/$', views.ChartDetailView.as_view(), name='chart_detail'), url(r'^add/(?P[0-9]+)/$', views.ChartCreate.as_view(), name='chart_add'), url(r'^(?P[0-9]+)/update/$', views.ChartUpdate.as_view(), name='chart_update'), url(r'^(?P[0-9]+)/delete/$', views.ChartDelete.as_view(), name='chart_delete') diff --git a/charts_app/views.py b/charts_app/views.py index d646bc6..093f319 100644 --- a/charts_app/views.py +++ b/charts_app/views.py @@ -7,7 +7,7 @@ from django.views.generic.edit import CreateView, UpdateView, DeleteView from geonode.layers.models import Layer -from .models import Chart +from .models import Chart, ChartForm from wfs_harvest.utils import get_fields @@ -16,8 +16,8 @@ class ChartDetailView(DetailView): class ChartCreate(CreateView): - model = Chart - fields = '__all__' + form_class = ChartForm + template_name = 'charts_app/chart_form.html' def get_initial(self): layer_id = self.kwargs['layer_id'] @@ -44,80 +44,4 @@ class ChartUpdate(UpdateView): class ChartDelete(DeleteView): model = Chart - success_url = '/' - - -def get_csv(request): - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="table.csv"' - - writer = csv.writer(response) - writer.writerow(['name', 'area']) - writer.writerow(['Af', 3.51]) - writer.writerow(['Ems / ems', 0.00]) - writer.writerow(['Ep / ep', 0.00]) - writer.writerow(['Lmst', 4.88]) - writer.writerow(['Mpb', 18.93]) - writer.writerow(['Pf', 30.53]) - writer.writerow(['Ppf', 6.84]) - writer.writerow(['Qa', 0.49]) - writer.writerow(['Qhac', 5.91]) - writer.writerow(['Qhad', 0.00]) - writer.writerow(['Qham', 11.01]) - writer.writerow(['Qht1', 1.10]) - writer.writerow(['Qht2', 27.60]) - writer.writerow(['Qpf', 34.40]) - writer.writerow(['Qphf', 42.30]) - - return response - -def get_csv_aggr(request): - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="table.csv"' - - writer = csv.writer(response) - writer.writerow(['name', 'area']) - writer.writerow(['Af', 3.51]) - writer.writerow(['Ems / ems', 0.00]) - writer.writerow(['Ep / ep', 0.00]) - writer.writerow(['Lmst', 4.88]) - writer.writerow(['Mpb', 18.93]) - writer.writerow(['Pf', 30.53]) - writer.writerow(['Ppf', 6.84]) - writer.writerow(['Qa', 0.49]) - writer.writerow(['Qhac', 5.91]) - writer.writerow(['Qhad', 0.00]) - writer.writerow(['Qham', 11.01]) - writer.writerow(['Qht1', 1.10]) - writer.writerow(['Qht2', 27.60]) - writer.writerow(['Qphf', 42.30]) - writer.writerow(['Af', 6.58]) - writer.writerow(['Qa', 30.78]) - writer.writerow(['Af', 16.58]) - - return response - - -def pie_chart_v1(request): - return render(request, 'pie_chart_v1.html') - - -def donut_chart_v1(request): - return render(request, 'donut_chart_v1.html') - - -def pie_chart_v2(request): - qdict = request.GET - typename = qdict['lyrname'] - category_field = qdict['category'] - quantity_field = qdict['quantity'] - context = { - 'lyrname': typename, - 'category': category_field, - 'quantity': quantity_field - } - return render(request, 'pie_chart_v2.html', context) - - -def donut_chart_v2(request): - return render(request, 'donut_chart_v2.html') + success_url = '/' \ No newline at end of file diff --git a/haitidata/settings.py b/haitidata/settings.py index e1470c1..5361f3b 100644 --- a/haitidata/settings.py +++ b/haitidata/settings.py @@ -53,7 +53,7 @@ LOCALE_PATHS = ( os.path.join(LOCAL_ROOT, 'locale'), ) + LOCALE_PATHS -INSTALLED_APPS = INSTALLED_APPS + ('haitidata', 'osgeo_importer', 'geonode-client', 'charts_app', 'wfs_harvest') +INSTALLED_APPS = INSTALLED_APPS + ('haitidata', 'osgeo_importer','charts_app', 'wfs_harvest') LAYER_PREVIEW_LIBRARY = 'react' @@ -112,7 +112,8 @@ CELERY_ALWAYS_EAGER = True IMPORT_TASK_SOFT_TIME_LIMIT = 90 MAX_CSV_RECORDS = 20000 - +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True SOCIAL_ORIGINS = [{ "label":"paper-plane-o", diff --git a/haitidata/templates/charts_app/chart_base.html b/haitidata/templates/charts_app/chart_base.html index 031c954..63868a1 100644 --- a/haitidata/templates/charts_app/chart_base.html +++ b/haitidata/templates/charts_app/chart_base.html @@ -4,7 +4,7 @@ {% block title %} {{ block.super }} {% endblock %} -{% block body_class %}charts{% endblock %} +{% block body_class %}Charts{% endblock %} {% block body_outer %} {% block body %}{% endblock body %} diff --git a/haitidata/templates/charts_app/chart_list.html b/haitidata/templates/charts_app/chart_list.html new file mode 100644 index 0000000..5c76b3c --- /dev/null +++ b/haitidata/templates/charts_app/chart_list.html @@ -0,0 +1,51 @@ +{% extends "charts_app/chart_base.html" %} +{% load i18n %} +{% load staticfiles %} +{% load url from future %} + +{% block title %}{{ block.super }} {% trans "Charts" %}{% endblock %} +{% block page-title %} + {% trans "Explore Charts" %} + {% if user.is_authenticated %} + + {% endif %} +{% endblock %} + +{% block body_class %}charts{% endblock %} + +{% block body %} + {% with include_type_filter='true' %} + {% with facet_type='documents' %} + {% with header='Document Type' %} + {% with filter='doc_type__in' %} + {% include "search/_search_content.html" %} + {% endwith %} + {% endwith %} + {% endwith %} + {% endwith %} + {% include "_bulk_permissions_form.html" %} +{% endblock %} + +{% block extra_script %} + {% if GEONODE_SECURITY_ENABLED %} + {% include "_permissions_form_js.html" %} + {% endif %} + + {% with include_spatial='true' %} + {% include 'search/search_scripts.html' %} + {% endwith %} +{% endblock extra_script %} diff --git a/haitidata/templates/geonode-client/layer_map.html b/haitidata/templates/geonode-client/layer_map.html index 2ba679b..bb77184 100644 --- a/haitidata/templates/geonode-client/layer_map.html +++ b/haitidata/templates/geonode-client/layer_map.html @@ -1,6 +1,6 @@ -{% load geonode_client_tags %} -{% client_viewer_js %} + +