Merge pull request #6 from ppasq/charts

Initial commit for Charts
This commit is contained in:
Paolo Pasquali authored and GitHub committed 2017-07-07 15:59:55 +02:00
commit aa0f45a465
58 files changed
+5077 -8421

No files matched your search

+3
View File
@@ -65,3 +65,6 @@ geoserver/
uploaded/
development.db
development.db-journal
# PyCharm
.idea
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
View File
Whitespace-only changes.
+58
View File
@@ -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 = ['*']
+8
View File
@@ -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
View File
Whitespace-only changes.
View File
Whitespace-only changes.
View File
Whitespace-only changes.
+24
View File
@@ -0,0 +1,24 @@
{% load i18n %}
<ul class="nav nav-tabs">
<li class="active"><a href="#info" data-toggle="tab"><i class="fa fa-info-circle"></i>{% trans "Info" %}</a></li>
{% if resource.polymorphic_ctype.name == 'layer' %}
<li><a href="#attributes" data-toggle="tab"><i class="fa fa-bars"></i>{% trans "Attributes" %}</a></li>
{% endif %}
{% if resource.is_mosaic %}
<li><a href="#granules" data-toggle="tab"><i class="fa fa-bars"></i>{% trans "Granules" %}</a></li>
{% endif %}
{% if resource.service_type == 'WFS' %}
<li><a href="#charts" data-toggle="tab"><i class="fa fa-charts"></i>{% trans "Charts" %}</a></li>
{% endif %}
{% if SOCIAL_BUTTONS %}
<li><a href="#share" data-toggle="tab"><i class="fa fa-share"></i>{% trans "Share" %}</a></li>
{% endif %}
<li><a href="#rate" data-toggle="tab"><i class="fa fa-star"></i>{% trans "Ratings" %}</a></li>
<li><a href="#comments" data-toggle="tab"><i class="fa fa-comment-o"></i> {% trans "Comments" %}</a></li>
{% if GEOGIG_ENABLED and resource.link_set.geogig %}
<li><a href="#geogig" data-toggle="tab"><i class="fa fa-code-fork"></i> {% trans "GeoGig" %}</a></li>
{% endif %}
{% if EXIF_ENABLED and resource.polymorphic_ctype.name == 'document' and exif_data %}
<li><a href="#exif" data-toggle="tab"><i class="fa fa-photo"></i> {% trans "Exif" %}</a></li>
{% endif %}
</ul>
+4
View File
@@ -0,0 +1,4 @@
{% extends "site_base.html" %}
{% load i18n %}
{% load staticfiles %}
+12
View File
@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block extra_head %}
<link href="{{ STATIC_URL }}css/site_base.css" rel="stylesheet"/>
{% endblock %}
{% block extra_tab %}
{% comment %}
Add Tab for Third Party Apps
<li>
<a href="{{ PROJECT_ROOT }}app">App</a>
</li>
{% endcomment %}
{% endblock %}
+15
View File
@@ -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 %}
<div class="jumbotron">
<div class="container">
<h1>{% trans "Welcome to the charts GeoNode" %}</h1>
<p>{% blocktrans %}GeoNode is an open source platform for sharing geospatial data and maps.{% endblocktrans %}</p>
<p>{% blocktrans %}If you have any questions about the software or service, join our <a href="https://groups.google.com/d/forum/geonode-users">mailing list</a>.{% endblocktrans %}</p>
<span>{% trans 'Need help?' %} <a href="http://docs.geonode.org/en/latest/tutorials/users/quickstart/index.html">{% trans 'Getting Started' %}</a></span>
</div>
</div>
{% endblock %}
+109
View File
@@ -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 = ''
+13
View File
@@ -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')),
]
+31
View File
@@ -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)
View File
Whitespace-only changes.
+10
View File
@@ -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)
+27
View File
@@ -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')),
],
),
]
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import charts_app.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('charts_app', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='chart',
name='created_by',
field=models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True),
),
migrations.AlterField(
model_name='chart',
name='layer',
field=models.ForeignKey(to='layers.Layer', validators=[charts_app.models.validate_wfs]),
),
migrations.AlterField(
model_name='chart',
name='type',
field=models.SmallIntegerField(default=0, choices=[(0, b'Bar chart'), (1, b'Pie chart'), (2, b'Donut chart'), (3, b'Line chart')]),
),
]
View File
Whitespace-only changes.
+80
View File
@@ -0,0 +1,80 @@
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 django.forms import ModelForm, ValidationError as VE
from geonode.layers.models import Layer
from wfs_harvest.utils import get_featno, get_fields
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)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null = True)
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__'
+68
View File
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Donut Chart v1</title>
<h1>Simple Donut Chart v1</h1>
<h2>Zero values excluded</h2>
<p>D3js version 4, use canvas</p>
<canvas width="960" height="500"></canvas>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var canvas = document.querySelector("canvas"),
context = canvas.getContext("2d");
var width = canvas.width,
height = canvas.height,
radius = Math.min(width, height) / 2;
var colors = ["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00", "#800000", "#8E388E",
"#7171C6", "#7D9EC0", "#388E8E", "#71C671", "#8E8E38", "#C5C1AA", "#C67171"];
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(radius - 70)
.context(context);
var labelArc = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40)
.context(context);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d.area; });
context.translate(width / 2, height / 2);
d3.csv("{% url "get_csv" %}", function(d) {
d.area = +d.area;
if (d.area !== 0) {
return d;}
}, function(error, data) {
if (error) throw error;
var arcs = pie(data);
arcs.forEach(function(d, i) {
context.beginPath();
arc(d);
context.fillStyle = colors[i];
context.fill();
});
context.beginPath();
arcs.forEach(arc);
context.strokeStyle = "#fff";
context.stroke();
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = "#000";
arcs.forEach(function(d) {
var c = labelArc.centroid(d);
context.fillText(d.data.name, c[0], c[1]);
});
});
</script>
<body></body>
+169
View File
@@ -0,0 +1,169 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Donut Chart v2</title>
<h1>Simple Donut Chart v2</h1>
<h2>Zero values excluded, label with ticks</h2>
<p>D3js version 3, use svg</p>
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
width: 960px;
height: 500px;
position: relative;
}
svg {
width: 100%;
height: 100%;
}
path.slice{
stroke-width:2px;
}
polyline{
opacity: .3;
stroke: black;
stroke-width: 2px;
fill: none;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var svg = d3.select("body")
.append("svg")
.append("g")
svg.append("g")
.attr("class", "slices");
svg.append("g")
.attr("class", "labels");
svg.append("g")
.attr("class", "lines");
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.area;
});
var arc = d3.svg.arc()
.outerRadius(radius * 0.8)
.innerRadius(radius * 0.4);
var outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
d3.csv("{% url "get_csv" %}", function(d) {
d.area = +d.area;
if (d.area !== 0) {
return d;}
}, function(error, data) {
if (error) throw error;
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var key = function(d){ return d.data.name};
var color = d3.scale.ordinal()
.domain([key])
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var slice = svg.select(".slices").selectAll("path.slice")
.data(pie(data), key);
slice.enter()
.insert("path")
.style("fill", function(d) { return color(d.data.area); })
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
/* ------- TEXT LABELS -------*/
var text = svg.select(".labels").selectAll("text")
.data(pie(data), key);
text.enter()
.append("text")
.attr("dy", ".35em")
.text(function(d) {
return d.data.name + ' - ' +d.data.area; ;
});
function midAngle(d){
return d.startAngle + (d.endAngle - d.startAngle)/2;
}
text.transition().duration(1000)
.attrTween("transform", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1);
return "translate("+ pos +")";
};
})
.styleTween("text-anchor", function(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";
};
});
/* ------- SLICE TO TEXT POLYLINES -------*/
var polyline = svg.select(".lines").selectAll("polyline")
.data(pie(data), key);
polyline.enter()
.append("polyline");
polyline.transition().duration(1000)
.attrTween("points", function(d){
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = radius * 0.95 * (midAngle(d2) < Math.PI ? 1 : -1);
return [arc.centroid(d2), outerArc.centroid(d2), pos];
};
});
});
</script>
</body>
+65
View File
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Pie Chart v1</title>
<h1>Simple Pie Chart v1</h1>
<h2>All data</h2>
<p>D3js version 4, use svg</p>
<style>
.arc text {
font: 10px sans-serif;
text-anchor: middle;
}
.arc path {
stroke: #fff;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = Math.min(width, height) / 2,
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var color = d3.scaleOrdinal(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d.area; });
var path = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var label = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
d3.csv("{% url "get_csv" %}", function(d) {
d.area = +d.area;
return d;
}, function(error, data) {
if (error) throw error;
var arc = g.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
arc.append("path")
.attr("d", path)
.attr("fill", function(d) { return color(d.data.name); });
arc.append("text")
.attr("transform", function(d) { return "translate(" + label.centroid(d) + ")"; })
.attr("dy", "0.35em")
.text(function(d) { return d.data.name; });
});
</script>
+67
View File
@@ -0,0 +1,67 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Pie Chart v2</title>
<h1>Simple Pie Chart v2</h1>
<h2>Zero values excluded</h2>
<p>D3js version 4, use svg</p>
<style>
.arc text {
font: 10px sans-serif;
text-anchor: middle;
}
.arc path {
stroke: #fff;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
radius = Math.min(width, height) / 2,
g = svg.append("g").attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d.{{ quantity }}; });
var path = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var label = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
d3.csv("/table?lyrname={{ lyrname }}&category={{ category }}&quantity={{ quantity }}", function(d) {
d.area = +d.area;
if (d.area !== 0) {
return d;}
}, function(error, data) {
if (error) throw error;
var arc = g.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
arc.append("path")
.attr("d", path)
.attr("fill", function(d) { return color(d.data.{{ category }}); });
arc.append("text")
.attr("transform", function(d) { return "translate(" + label.centroid(d) + ")"; })
.attr("dy", "0.35em")
.text(function(d) { return d.data.{{ category }}; });
});
</script>
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+10
View File
@@ -0,0 +1,10 @@
from django.conf.urls import url
from . import views
urlpatterns = [url(r'^(?P<pk>[0-9]+)/$', views.ChartDetail.as_view(), name='chart_detail'),
url(r'^add/(?P<layer_id>[0-9]+)/$', views.ChartCreate.as_view(), name='chart_add'),
url(r'^(?P<pk>[0-9]+)/update/$', views.ChartUpdate.as_view(), name='chart_update'),
url(r'^(?P<pk>[0-9]+)/delete/$', views.ChartDelete.as_view(), name='chart_delete'),
url(r'^$', views.ChartList.as_view(), name='chart_list')
]
+105
View File
@@ -0,0 +1,105 @@
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.list import ListView
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from geonode.layers.models import Layer
from .models import Chart, ChartForm
from wfs_harvest.utils import get_fields
class LoginRequiredMixin(object):
@classmethod
def as_view(cls, **initkwargs):
view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
return login_required(view)
class ChartDetail(DetailView):
model = Chart
def get_object(self, queryset=None):
obj = super(ChartDetail, self).get_object(queryset=queryset)
lyr_obj = Layer.objects.get(pk=obj.layer)
if not self.request.user.has_perm('download_resourcebase', lyr_obj.get_self_resource()):
raise PermissionDenied
return obj
def get_context_data(self, **kwargs):
context = super(ChartDetail, self).get_context_data(**kwargs)
return context
class ChartCreate(LoginRequiredMixin, CreateView):
form_class = ChartForm
template_name_suffix = 'charts_app/chart_update_form.html'
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)
if not self.request.user.has_perm('download_resourcebase', layer.get_self_resource()):
raise PermissionDenied
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
def form_valid(self, form):
if not self.request.user.has_perm('download_resourcebase', form.instance.layer.get_self_resource()):
raise PermissionDenied
form.instance.created_by = self.request.user
return super(ChartCreate, self).form_valid(form)
class ChartUpdate(LoginRequiredMixin, UpdateView):
model = Chart
fields = '__all__'
template_name = 'charts_app/chart_update_form.html'
def get_object(self, queryset=None):
obj = super(ChartUpdate, self).get_object(queryset=queryset)
is_chart_owner = (self.request.user == obj.created_by)
lyr_obj = Layer.objects.get(pk=obj.layer)
is_lyr_owner = (self.request.user == lyr_obj.owner)
if not (self.request.user.is_superuser or is_chart_owner or is_lyr_owner):
raise PermissionDenied
return obj
def form_valid(self, form):
form.instance.created_by = self.request.user
return super(ChartUpdate, self).form_valid(form)
class ChartDelete(LoginRequiredMixin, DeleteView):
model = Chart
success_url = '/layers/'
def get_object(self, queryset=None):
obj = super(ChartDelete, self).get_object(queryset=queryset)
is_chart_owner = (self.request.user == obj.created_by)
lyr_obj = Layer.objects.get(pk=obj.layer)
is_lyr_owner = (self.request.user == lyr_obj.owner)
if not (self.request.user.is_superuser or is_chart_owner or is_lyr_owner):
raise PermissionDenied
return obj
class ChartList(ListView):
model = Chart
fields = '__all__'
template_name = 'charts_app/chart_list.html'
def get_context_data(self, **kwargs):
context = super(ChartList, self).get_context_data(**kwargs)
return context
+4 -1
View File
@@ -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,9 @@ BROKER_URL = "amqp://guest@localhost:5672"
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",
File diff suppressed because it is too large. Load diff
+39
View File
@@ -0,0 +1,39 @@
/* 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;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
+185
View File
@@ -0,0 +1,185 @@
function barChart(cat, qnt, title,id){
var width = function(){ return document.getElementById(id).offsetWidth},
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: 120 },
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_name = "chart_svg" + id;
var svg = selection.append('svg')
.attr("id", svg_name)
.attr("width", width())
.attr("height", height);
var g = svg.selectAll("g")
.data([1]);
g = g.enter().append("g")
.merge(g)
.attr("class", svg_name)
.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 xAxis_name = "x-axis_" + id;
var xAxis_call = "." + xAxis_name;
var xAxisG = g.selectAll(xAxis_call).data([1]);
xAxisG.enter().append("g")
.attr("class", xAxis_name)
.merge(xAxisG)
.attr("transform", "translate(0," + innerHeight() +")")
.call(xAxis);
// labels X axis
// rotate text if is longer than...
var rotate = 0;
d3.select(this).select(xAxis_call).selectAll("text").each(function(){
if (this.getBBox().width > (xScale.bandwidth() - (padding*2))){
rotate = 1;
d3.select(xAxis_call).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 = 15;
if (rotate == 1) {
d3.select(this).select(xAxis_call).selectAll("text").each(function(){
if (this.getBBox().width > maxh)
maxh = this.getBBox().width;
});};
margin.bottom = margin.bottom + maxh;
d3.select(xAxis_call).attr("transform", "translate(0," + (innerHeight()) + ")");
var text_name = "text_" + id;
g.append("text")
.attr("class", text_name)
.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 yAxis_name = "y-axis_" + id;
var yAxis_call = "." + yAxis_name;
var yAxisG = g.selectAll(yAxis_call).data([1]);
yAxisG.enter().append("g")
.attr("class", yAxis_name)
.merge(yAxisG)
.call(yAxis);
//labels Y axis
var maxw = 0;
d3.select(this).select(yAxis_call).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("class", text_name)
.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 leg_name = "legend" + id;
var leg_call = "." + leg_name;
var legend = svg.selectAll("leg_call")
.data(data)
.enter().append("g")
.attr("class", leg_name)
.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("class", text_name)
.attr("x", width() - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function (d){ return d[x] + ": " + floatFormat(d[y]); });
});
}
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;
}
+163
View File
@@ -0,0 +1,163 @@
var getCsv = function(csv_link, category, quantity, agg, chartType, id){
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){
var firstSet = dati.slice(0,19);
var lastSet = dati.slice(19,).reduce(function(a, b){ return {key: 'others', value: a.value + b.value }});
firstSet.push(lastSet);
dati = firstSet;
};
var chartArea = "#" + id;
var bar = barChart(category, quantity, title, id)
.x('key')
.y('value')
d3.select(chartArea)
.datum(dati)
.call(bar);
} else if (chartType == 1){
aggregateData(data, agg)
if (dati.length > 10){
var firstSet = dati.slice(0,9);
var lastSet = dati.slice(9,).reduce(function(a, b){ return {key: 'others', value: a.value + b.value }});
firstSet.push(lastSet);
dati = firstSet;
};
var chartArea = "#" + id;
var pie = pieChart(category, quantity, title, id)
.variable('value')
.category('key')
d3.select(chartArea)
.datum(dati)
.call(pie);
} else if (chartType == 2){
aggregateData(data, agg)
if (dati.length > 10){
var firstSet = dati.slice(0,9);
var lastSet = dati.slice(9,).reduce(function(a, b){ return {key: 'others', value: a.value + b.value }});
firstSet.push(lastSet);
dati = firstSet;
};
var chartArea = "#" + id;
var donut = donutChart(category, quantity, title, id)
.variable('value')
.category('key')
d3.select(chartArea)
.datum(dati)
.call(donut);
} else if (chartType == 3){
aggregateData(data, agg)
if (dati.length > 20){
var firstSet = dati.slice(0,19);
var lastSet = dati.slice(19,).reduce(function(a, b){ return {key: 'others', value: a.value + b.value }});
firstSet.push(lastSet);
dati = firstSet;
};
var chartArea = "#" + id;
var line = lineChart(category, quantity, title, id)
.x('key')
.y('value')
d3.select(chartArea)
.datum(dati)
.call(line);
}
};
create(chartType);
});
};
+415
View File
@@ -0,0 +1,415 @@
function donutChart(cat, qnt, title, id){
var width = function(){ return document.getElementById(id).parentNode.offsetWidth},
height = 500,
margin = {top: 80, 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 += '<tspan x="0">' + cat + ': ' + value + '</tspan>';
else tip += '<tspan x="0" dy="1.2em">' + qnt + ': ' + value + '</tspan>';
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] + ': <tspan>' + floatFormat(d.data[variable]) + '</tspan>';
}
// 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;
}
+56
View File
@@ -0,0 +1,56 @@
var dict_title_tc = {0:"Bar",1:"Pie", 2:"Donut", 3:"Line"};
var dict_title_agg = {0:"Sum",1:"Mean",2:"Count", 3:"Max", 4:"Min"};
function get_chart_detail(){
var id = "chart_area_plain"
//create area for chart
var createChartArea = function() {
var chartArea = document.getElementById(id);
chartArea.style= "display:table; height:100%";
};
// update chart
var replaceChart = function(){
doc_chart = document.getElementById(id);
if(doc_chart.childElementCount >= 1){
doc_chart.removeChild(doc_chart.childNodes[1]);
createChartArea();
};
};
var chartArea = document.getElementById(id);
createChartArea();
replaceChart();
console.log("son qui")
getCsv(csv_url, category, quantity, agg, chart_type, id);
}
function title_detail(){
chart_type = dict_title_tc[chart_type]
return document.getElementById("title_detail").innerHTML = chart_type + " chart based on " + lyr + " layer (Category field: " + category + ", Quantity field: " + quantity + ")";
}
function get_chart_popup(){
var id = "chart_area";
//create area for chart
var createChartArea = function() {
var chartArea = document.getElementById(id);
chartArea.style= "display:table";
};
// update chart
var replaceChart = function(){
doc_chart = document.getElementById(id);
if(doc_chart.childElementCount >= 1){
doc_chart.removeChild(doc_chart.childNodes[1]);
createChartArea();
};
};
if (document.getElementById("id_title").value == ""){
document.getElementById("chart_title").innerHTML = suggestedTitle;
}else document.getElementById("chart_title").innerHTML = document.getElementById("id_title").value;
createChartArea();
replaceChart();
attachAbstract();
getCsv(csv_url, category, quantity, agg, chart_type, id);
}
+192
View File
@@ -0,0 +1,192 @@
function lineChart(cat, qnt, title, id){
var width = function(){ return document.getElementById(id).offsetWidth},
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_name = "chart_svg" + id;
var svg = selection.append('svg')
.attr("id", svg_name)
.attr("width", width())
.attr("height", height);
var g = svg.selectAll("g")
.data([1]);
g = g.enter().append("g")
.merge(g)
.attr("class", svg_name)
.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 xAxis_name = "x-axis_" + id;
var xAxis_call = "." + xAxis_name;
var xAxisG = g.selectAll(xAxis_call).data([1]);
xAxisG.enter().append("g")
.attr("class", xAxis_name)
.merge(xAxisG)
.attr("transform", "translate(0," + innerHeight() +")")
.call(xAxis);
// labels X ax,is
// rotate text if is longer than...
var rotate = 0;
d3.select(this).select(xAxis_call).selectAll("text").each(function(){
if (this.getBBox().width > xScale.bandwidth()){
rotate = 1
d3.select(xAxis_call).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 = 15;
if (rotate == 1) {
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(xAxis_call).attr("transform", "translate(0," + (innerHeight()) + ")");
var text_name = "text_" + id;
g.append("text")
.attr("class", text_name)
.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 yAxis_name = "y-axis_" + id;
var yAxis_call = "." + yAxis_name;
var yAxisG = g.selectAll(yAxis_call).data([1]);
yAxisG.enter().append("g")
.attr("class", yAxis_name)
.merge(yAxisG)
.call(yAxis);
//labels Y axis
var maxw = 0;
d3.select(this).select(yAxis_call).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("class", text_name)
.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")
.style("width","auto")
.style("height", "auto")
.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 + "<br/>" + 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;
}
+415
View File
@@ -0,0 +1,415 @@
function pieChart(cat, qnt, title, id){
var width = function(){ return document.getElementById(id).parentNode.offsetWidth},
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(-320, 230)")
.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(-310, 255)")
.attr('class', 'toolRect')
.html(toolTipHTML(data)) // add text to the rect
.style('font-size', '.9em'); // 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 += '<tspan x="0">' + cat + ': ' + value + '</tspan>';
else tip += '<tspan x="0" dy="1.2em">' + qnt + ': ' + floatFormat(value) + '</tspan>';
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] + ': <tspan>' + floatFormat(d.data[variable]) + '</tspan>';
}
// 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;
}
+55 -1
View File
@@ -175,7 +175,7 @@ form {
.navbar-header {
i {
color: white;
color: @brand-primary;
font-size: 2em;
}
@@ -712,6 +712,60 @@ 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-box-shadow: none;
box-shadow: none;
}
}
}
}
#create-chart {
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-box-shadow: none;
box-shadow: none;
}
}
}
/* ------ OSGeo Importer
------------------------------ */
body.importer {
@@ -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 %}
@@ -0,0 +1,26 @@
{% extends "charts_app/chart_base.html" %}
{% load i18n %}
{% block title %}{% trans "Deleting" %} {{ object.title }} — {{ block.super }}{% endblock %}
{% block body %}
<div class="page-header">
<h2 class="page-title">{% trans "Remove Chart" %}</h2>
</div>
<div class="row">
<div class="col-md-8">
<p class="lead">
{% blocktrans with object.title as object_title %}
Are you sure you want to remove <a href="{{ object.get_absolute_url }}">{{ object_title }}</a> chart?
{% endblocktrans %}
</p>
<form method="POST" "{% url "chart_delete" chart.id %}">
{% csrf_token %}
<div class="form-actions">
<input type="submit" value="{% trans "Yes, I am sure" %}" class="btn btn-danger" />
<a href="{{ object.get_absolute_url }}" class="btn btn-default">{% trans "No, don't remove it" %}</a>
</div>
</form>
</div>
</div>
{% endblock %}
@@ -0,0 +1,368 @@
{% 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 Charts" %}
<div class="btn-group pull-right">
<a class="btn btn-primary btn-icon" href="{% url 'chart_list' %}">
<i class="fa fa-align-justify" title="List"></i>
</a>
<a class="btn btn-primary" href="{% url 'chart_list' %}">
{% trans "Charts" %}
</a>
</div>
{% endblock %}
{% block subheader %}
<section id="subheader">
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>{{ object.title }}</h2>
<p id="title_detail">
<span style="text-transform: capitalize;"></span>
</p>
{% block social_links %}
{% if SOCIAL_BUTTONS %}
{% include "social_links.html" %}
{% endif %}
{% endblock %}
</div>
</div>
</div>
</section>
{% endblock %}
{% block body_outer %}
<script src="/static/js/csv.js"></script>
<script src="/static/js/donut.js"></script>
<script src="/static/js/bar.js"></script>
<script src="/static/js/pie.js"></script>
<script src="/static/js/line.js"></script>
<script src="/static/js/getChart.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<link rel="stylesheet" href="/static/css/index.css">
<div class="row">
<div class="col-md-8">
<div id="chart_area_plain"></div>
<h2>Abstract</h2>
<p>{{ object.abstract }}</p>
<div class="documents-actions">
{% include "_actions.html" %}
</div>
<div class="tab-content">
<article id="info" class="tab-pane active">
</article>
<article id="comments" class="tab-pane">
</article>
<article id="rate" class="tab-pane">
<!-- TODO: Move this to a reusable template snippet -->
<h4>{% trans "Rate this document" %}</h4>
<div id="user_rating" class="category-layer" data-score="{{user_document_rating}}"></div>
<h4>{% trans 'Average Rating' %}</h4>
<div class="overall_rating" style="float:left" data-score="{{ document_rating }}"></div> ({{num_votes}})
<!-- TODO: Add display of who gave what rating based -->
</article>
</div>
</div>
<div class="col-md-4">
<li class="list-group-item">
<a href="/table?" id="link">
<button class="btn btn-primary btn-md btn-block">{% trans "Download CSV" %}</button>
</a>
</li>
<li class="list-group-item">
<h4>{% trans "Modify this Chart" %}</h4>
<p>{% trans "Change type of the chart or type of aggregation." %}</p>
<a id="updt_chart">
<button class="btn btn-primary btn-md btn-block" data-toggle="collapse" data-target="#form" onclick="update_params();" >{% trans "Modify this Chart" %}</button>
</a>
<div id="form" class="collapse">
<form name="f" method="post" class="form-horizontal" onchange="update_params();" action="{% url 'chart_update' chart.id %}" >
{% csrf_token %}
<div class="form-group">
<input id="id_layer" name="layer" value="{{ resource.id }}" type="hidden">
</div>
<div class="form-group">
<label class="control-label col-md-2" for="id_type">Type:</label>
<div class="col-md-8">
<select class="form-control" id="id_type" name="type">
<option value="0">Bar chart</option>
<option value="1">Pie chart</option>
<option value="2">Donut chart</option>
<option value="3">Line chart</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="id_aggr_type">Aggregation:</label>
<div class="col-md-8">
<select class="form-control" id="id_aggr_type" name="aggr_type">
<option value="0">Sum</option>
<option value="1">Mean</option>
<option value="2">Category count</option>
<option value="3">Max</option>
<option value="4">Min</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="id_title">Title:</label>
<div class="col-md-8">
<input class="form-control" id="id_title" maxlength="128" name="title" type="text">
<p style="font-size: small; margin:0">Suggested title:</p>
<p id="title" style="font-size: small; margin:0">Testo di prova</p>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="id_abstract">Abstract:</label>
<div class="col-md-8">
<textarea class="form-control" cols="40" id="id_abstract" name="abstract" rows="5"></textarea>
</div>
</div>
<button type="button" class="btn btn-primary btn-block" data-toggle="modal" data-target="#chart_popup" onclick="get_chart_popup()">Display chart</button>
<button type="submit" class="btn btn-primary btn-block" value="Submit">Update chart in db</button>
</form>
</div>
</li>
<li class="list-group-item">
<a href="{% url 'chart_delete' object.id %}" id="del_chart">
<button class="btn btn-primary btn-md btn-block">{% trans "Delete this Chart" %}</button>
</a>
</li>
<li class="list-group-item">
<button id='saveButton' class="btn btn-primary btn-md btn-block">Export my D3 visualization to PNG</button>
</li>
</div>
</div>
<div id="chart_popup" class="modal fade" role="dialog" >
<script src="/static/js/csv.js"></script>
<script src="/static/js/donut.js"></script>
<script src="/static/js/bar.js"></script>
<script src="/static/js/pie.js"></script>
<script src="/static/js/line.js"></script>
<script src="/static/js/getChart.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<link rel="stylesheet" href="/static/css/index.css">
<div class="modal-dialog" id="chart" style="width: 55%">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 id="chart_title" class="modal-title"></h4>
</div>
<div class="modal-body">
<div id="chart_area" style="display: table; float: inherit; width: 50%;">
</div>
<div>
<h5>Abstract</h5>
<p id="abstract"></p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
var typename = "{{ object.layer.typename }}";
var lyr = "{{ object.layer.name }}"
var category = "{{ object.category }}";
var quantity = "{{ object.quantity }}";
var agg = "{{ object.aggr_type }}";
var chart_type = "{{ object.type }}";
var csv_url;
var suggestedTitle = "";
var dict_title_agg = {0:"Sum",1:"Mean",2:"Count", 3:"Max", 4:"Min"};
function update_link(){
csv_url = '/table?lyrname=' + typename;
csv_url = csv_url + '&category=' + category;
csv_url = csv_url + '&quantity=' + quantity;
document.getElementById('link').href = csv_url;
return;
}
function attachAbstract(){
var abstractText = document.getElementById("id_abstract").value;
document.getElementById('abstract').innerHTML = abstractText;
}
function chart_title(){
if (agg == dict_title_agg[2]){
suggestedTitle = dict_title_agg[agg] + " of elements for " + category;
} else {
var title = dict_title_agg[agg]
suggestedTitle = title + " of " + quantity + " for " + category;
}
document.getElementById("title").innerHTML = suggestedTitle;
return suggestedTitle;
};
function title_eval(){
if (document.getElementById("id_title").textLength == 0){
document.getElementById("id_title").value = suggestedTitle;
}
}
function update_params(){
agg = document.f.aggr_type.value;
chart_type = document.f.type.value;
chart_title();
}
update_link();
get_chart_detail();
title_detail();
</script>
<script src="https://cdn.rawgit.com/eligrey/canvas-toBlob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toBlob.js"></script>
<script src="https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js"></script>
<script>
// Set-up the export button
d3.select('#saveButton').on('click', function(){
var svg_img = document.getElementById("chart_svg");
console.log(svg_img)
var svgString = getSVGString(document.getElementById("chart_svg"));
svgString2Image( svgString, 300, 250, 'png', save ); // passes Blob and filesize String to the callback
function save( dataBlob, filesize ){
saveAs( dataBlob, 'D3 vis exported to PNG.png' ); // FileSaver.js function
}
});
// Below are the functions that handle actual exporting:
// getSVGString ( svgNode ) and svgString2Image( svgString, width, height, format, callback )
function getSVGString( svgNode ) {
svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink');
var cssStyleText = getCSSStyles( svgNode );
appendCSS( cssStyleText, svgNode );
var serializer = new XMLSerializer();
var svgString = serializer.serializeToString(svgNode);
svgString = svgString.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace
svgString = svgString.replace(/NS\d+:href/g, 'xlink:href'); // Safari NS namespace fix
return svgString;
function getCSSStyles( parentElement ) {
var selectorTextArr = [];
// Add Parent element Id and Classes to the list
selectorTextArr.push( '#'+parentElement.id );
for (var c = 0; c < parentElement.classList.length; c++)
if ( !contains('.'+parentElement.classList[c], selectorTextArr) )
selectorTextArr.push( '.'+parentElement.classList[c] );
// Add Children element Ids and Classes to the list
var nodes = parentElement.getElementsByTagName("*");
for (var i = 0; i < nodes.length; i++) {
var id = nodes[i].id;
if ( !contains('#'+id, selectorTextArr) )
selectorTextArr.push( '#'+id );
var classes = nodes[i].classList;
for (var c = 0; c < classes.length; c++)
if ( !contains('.'+classes[c], selectorTextArr) )
selectorTextArr.push( '.'+classes[c] );
}
// Extract CSS Rules
var extractedCSSText = "";
for (var i = 0; i < document.styleSheets.length; i++) {
var s = document.styleSheets[i];
try {
if(!s.cssRules) continue;
} catch( e ) {
if(e.name !== 'SecurityError') throw e; // for Firefox
continue;
}
var cssRules = s.cssRules;
for (var r = 0; r < cssRules.length; r++) {
if ( contains( cssRules[r].selectorText, selectorTextArr ) )
extractedCSSText += cssRules[r].cssText;
}
}
return extractedCSSText;
function contains(str,arr) {
return arr.indexOf( str ) === -1 ? false : true;
}
}
function appendCSS( cssText, element ) {
var styleElement = document.createElement("style");
styleElement.setAttribute("type","text/css");
styleElement.innerHTML = cssText;
var refNode = element.hasChildNodes() ? element.children[0] : null;
element.insertBefore( styleElement, refNode );
}
}
function svgString2Image( svgString, width, height, format, callback ) {
var format = format ? format : 'png';
var imgsrc = 'data:image/svg+xml;base64,'+ btoa( unescape( encodeURIComponent( svgString ) ) ); // Convert SVG string to data URL
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
var image = new Image();
image.onload = function() {
context.clearRect ( 0, 0, width, height );
context.drawImage(image, 0, 0, width, height);
canvas.toBlob( function(blob) {
var filesize = Math.round( blob.length/1024 ) + ' KB';
if ( callback ) callback( blob, filesize );
});
};
image.src = imgsrc;
}
</script>
{% endblock %}
@@ -0,0 +1,65 @@
{% 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 %}
<div class="btn-group pull-right">
<a class="btn btn-primary btn-icon" href="{% url "document_upload" %}">
<i class="fa fa-plus" title="Upload Document"></i>
</a>
<a class="btn btn-primary" href="{% url "document_upload" %}">
{% trans "Upload Document" %}
</a>
</div>
{% endif %}
{% endblock %}
{% block body_class %}charts
{% endblock %}
{% block body %}
{% with include_type_filter='true' %}
{% with facet_type='charts' %}
{% with header='Chart Type' %}
{% with filter='doc_type__in' %}
{% include "search/_search_content.html" %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
<div class="col-md-9">
<h1>CHARTS</h1>
<ul>
{% for chart in object_list %}
<li>{{ chart.title }}</li>
<li>{{ chart.layer }} - {{ chart.category }} - {{ chart.quantity }}</li>
<li>{{ chart.type }} - {{ chart.agg_type }}</li>
<li>{{ chart.abstract }}</li>
<br>
{% empty %}
<li>No Charts yet!</li>
{% endfor %}
</ul>
</div>
{% include "_bulk_permissions_form.html" %}
{% endblock %}
{% block extra_script %}
{% if GEONODE_SECURITY_ENABLED %}
{% include "_permissions_form_js.html" %}
{% endif %}
<script type="text/javascript">
</script>
{% with include_spatial='true' %}
{% endwith %}
{% endblock extra_script %}
@@ -0,0 +1,356 @@
{% 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 Charts" %}
<div class="btn-group pull-right">
<a class="btn btn-primary btn-icon" href="{% url 'chart_list' %}">
<i class="fa fa-align-justify" title="List"></i>
</a>
<a class="btn btn-primary" href="{% url 'chart_list' %}">
{% trans "Charts" %}
</a>
</div>
{% endblock %}
{% block subheader %}
<section id="subheader">
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>{{ object.title }}</h2>
<p id="title_detail">
<span style="text-transform: capitalize;"></span>
</p>
{% block social_links %}
{% if SOCIAL_BUTTONS %}
{% include "social_links.html" %}
{% endif %}
{% endblock %}
</div>
</div>
</div>
</section>
{% endblock %}
{% block body_outer %}
<script src="/static/js/csv.js"></script>
<script src="/static/js/donut.js"></script>
<script src="/static/js/bar.js"></script>
<script src="/static/js/pie.js"></script>
<script src="/static/js/line.js"></script>
<script src="/static/js/getChart.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<link rel="stylesheet" href="/static/css/index.css">
<div class="row">
<div class="col-md-8">
<div id="chart_area_plain"></div>
<h2>Abstract</h2>
<p>{{ object.abstract }}</p>
<div class="documents-actions">
{% include "_actions.html" %}
</div>
<div class="tab-content">
<article id="info" class="tab-pane active">
</article>
<article id="comments" class="tab-pane">
</article>
<article id="rate" class="tab-pane">
<!-- TODO: Move this to a reusable template snippet -->
<h4>{% trans "Rate this document" %}</h4>
<div id="user_rating" class="category-layer" data-score="{{user_document_rating}}"></div>
<h4>{% trans 'Average Rating' %}</h4>
<div class="overall_rating" style="float:left" data-score="{{ document_rating }}"></div> ({{num_votes}})
<!-- TODO: Add display of who gave what rating based -->
</article>
</div>
</div>
<div class="col-md-4">
<li class="list-group-item">
<a href="/table?" id="link">
<button class="btn btn-primary btn-md btn-block">{% trans "Download CSV" %}</button>
</a>
</li>
<li class="list-group-item">
<h4>{% trans "Modify this Chart" %}</h4>
<p>{% trans "Change type of the chart or type of aggregation." %}</p>
<a id="updt_chart">
<button class="btn btn-primary btn-md btn-block" data-toggle="collapse" data-target="#form" onclick="update_params();" >{% trans "Modify this Chart" %}</button>
</a>
<div id="form" class="collapse">
<form name="f" method="post" class="form-horizontal" onchange="update_params();" >
{% csrf_token %}
{{ form.non_field_errors }}
<div class="form-group">
{{ form.layer.as_hidden }}
</div>
<div class="form-group">
{{ form.category.as_hidden }}
</div>
<div class="form-group">
{{ form.quantity.as_hidden }}
</div>
<div class="form-group">
{{ form.title.errors }}
<label class="control-label col-md-2" for="{{ form.title.id_for_label }}">Title:</label>
{{ form.title }}
</div>
<div class="form-group">
{{ form.type.errors }}
<label class="control-label col-md-2" for="{{ form.type.id_for_label }}">Type:</label>
{{ form.type }}
</div>
<div class="form-group">
{{ form.aggr_type.errors }}
<label class="control-label col-md-2" for="{{ form.aggr_type.id_for_label }}">Aggregation:</label>
{{ form.aggr_type }}
</div>
<div class="form-group">
{{ form.abstract.errors }}
<label class="control-label col-md-2" for="{{ form.abstract.id_for_label }}">Abstract:</label>
{{ form.abstract }}
</div>
<button type="button" class="btn btn-primary btn-block" data-toggle="modal" data-target="#chart_popup" onclick="get_chart_popup()">Display chart</button>
<button type="submit" class="btn btn-primary btn-block" value="Submit">Update chart in db</button>
</form>
</div>
</li>
<li class="list-group-item">
<a href="{% url 'chart_delete' object.id %}" id="del_chart">
<button class="btn btn-primary btn-md btn-block">{% trans "Delete this Chart" %}</button>
</a>
</li>
</div>
</div>
<div id="chart_popup" class="modal fade" role="dialog" >
<script src="/static/js/csv.js"></script>
<script src="/static/js/donut.js"></script>
<script src="/static/js/bar.js"></script>
<script src="/static/js/pie.js"></script>
<script src="/static/js/line.js"></script>
<script src="/static/js/getChart.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<link rel="stylesheet" href="/static/css/index.css">
<div class="modal-dialog" id="chart" style="width: 55%">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 id="chart_title" class="modal-title"></h4>
</div>
<div class="modal-body">
<div id="chart_area" style="display: table; float: inherit; width: 50%;">
</div>
<div>
<h5>Abstract</h5>
<p id="abstract"></p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
var typename = "{{ object.layer.typename }}";
var lyr = "{{ object.layer.name }}"
var category = "{{ object.category }}";
var quantity = "{{ object.quantity }}";
var agg = "{{ object.aggr_type }}";
var chart_type = "{{ object.type }}";
var csv_url;
var suggestedTitle = "";
var dict_title_agg = {0:"Sum",1:"Mean",2:"Count", 3:"Max", 4:"Min"};
function update_link(){
csv_url = '/table?lyrname=' + typename;
csv_url = csv_url + '&category=' + category;
csv_url = csv_url + '&quantity=' + quantity;
document.getElementById('link').href = csv_url;
return;
}
function attachAbstract(){
var abstractText = document.getElementById("id_abstract").value;
document.getElementById('abstract').innerHTML = abstractText;
}
function chart_title(){
if (agg == dict_title_agg[2]){
suggestedTitle = dict_title_agg[agg] + " of elements for " + category;
} else {
var title = dict_title_agg[agg]
suggestedTitle = title + " of " + quantity + " for " + category;
}
document.getElementById("title").innerHTML = suggestedTitle;
return suggestedTitle;
};
function title_eval(){
if (document.getElementById("id_title").textLength == 0){
document.getElementById("id_title").value = suggestedTitle;
}
}
function update_params(){
agg = document.f.aggr_type.value;
chart_type = document.f.type.value;
chart_title();
}
update_link();
get_chart_detail();
title_detail();
</script>
<div>
<button id='saveButton'>Export my D3 visualization to PNG</button>
</div>
<script src="https://cdn.rawgit.com/eligrey/canvas-toBlob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toBlob.js"></script>
<script src="https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js"></script>
<script>
// Set-up the export button
d3.select('#saveButton').on('click', function(){
var svg_img = document.getElementById("chart_svg");
console.log(svg_img)
var svgString = getSVGString(document.getElementById("chart_svg"));
svgString2Image( svgString, 300, 250, 'png', save ); // passes Blob and filesize String to the callback
function save( dataBlob, filesize ){
saveAs( dataBlob, 'D3 vis exported to PNG.png' ); // FileSaver.js function
}
});
// Below are the functions that handle actual exporting:
// getSVGString ( svgNode ) and svgString2Image( svgString, width, height, format, callback )
function getSVGString( svgNode ) {
svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink');
var cssStyleText = getCSSStyles( svgNode );
appendCSS( cssStyleText, svgNode );
var serializer = new XMLSerializer();
var svgString = serializer.serializeToString(svgNode);
svgString = svgString.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace
svgString = svgString.replace(/NS\d+:href/g, 'xlink:href'); // Safari NS namespace fix
return svgString;
function getCSSStyles( parentElement ) {
var selectorTextArr = [];
// Add Parent element Id and Classes to the list
selectorTextArr.push( '#'+parentElement.id );
for (var c = 0; c < parentElement.classList.length; c++)
if ( !contains('.'+parentElement.classList[c], selectorTextArr) )
selectorTextArr.push( '.'+parentElement.classList[c] );
// Add Children element Ids and Classes to the list
var nodes = parentElement.getElementsByTagName("*");
for (var i = 0; i < nodes.length; i++) {
var id = nodes[i].id;
if ( !contains('#'+id, selectorTextArr) )
selectorTextArr.push( '#'+id );
var classes = nodes[i].classList;
for (var c = 0; c < classes.length; c++)
if ( !contains('.'+classes[c], selectorTextArr) )
selectorTextArr.push( '.'+classes[c] );
}
// Extract CSS Rules
var extractedCSSText = "";
for (var i = 0; i < document.styleSheets.length; i++) {
var s = document.styleSheets[i];
try {
if(!s.cssRules) continue;
} catch( e ) {
if(e.name !== 'SecurityError') throw e; // for Firefox
continue;
}
var cssRules = s.cssRules;
for (var r = 0; r < cssRules.length; r++) {
if ( contains( cssRules[r].selectorText, selectorTextArr ) )
extractedCSSText += cssRules[r].cssText;
}
}
return extractedCSSText;
function contains(str,arr) {
return arr.indexOf( str ) === -1 ? false : true;
}
}
function appendCSS( cssText, element ) {
var styleElement = document.createElement("style");
styleElement.setAttribute("type","text/css");
styleElement.innerHTML = cssText;
var refNode = element.hasChildNodes() ? element.children[0] : null;
element.insertBefore( styleElement, refNode );
}
}
function svgString2Image( svgString, width, height, format, callback ) {
var format = format ? format : 'png';
var imgsrc = 'data:image/svg+xml;base64,'+ btoa( unescape( encodeURIComponent( svgString ) ) ); // Convert SVG string to data URL
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
var image = new Image();
image.onload = function() {
context.clearRect ( 0, 0, width, height );
context.drawImage(image, 0, 0, width, height);
canvas.toBlob( function(blob) {
var filesize = Math.round( blob.length/1024 ) + ' KB';
if ( callback ) callback( blob, filesize );
});
};
image.src = imgsrc;
}
</script>
{% endblock %}
@@ -20,8 +20,8 @@
{% else %}
printService: "",
{% endif %}
/* The URL to a REST map configuration service. This service
* provides listing and, with an authenticated user, saving of
/* The URL to a REST map configuration service. This service
* provides listing and, with an authenticated user, saving of
* maps on the server for sharing and editing.
*/
rest: "{% url "maps_browse" %}",
@@ -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" %}
<div class="btn-group pull-right">
<a class="btn btn-primary btn-icon" href="{% url "layer_browse" %}">
<i class="fa fa-bars" title="All Layers"></i>
</a>
<a class="btn btn-primary" href="{% url "layer_browse" %}">
{% trans "All Layers" %}
</a>
</div>
{% 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 %}
<section id="subheader">
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>{{ resource.title|default:resource.typename }}</h2>
<p>
{{ resource.service_type }}
{% if resource.category %}
{% trans "in" %} <a href="{% url "search" %}?category__identifier__in={{ resource.category.identifier }}">{{ resource.category }}</a> |
{% 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 %}
</p>
{% block social_links %}
{% if SOCIAL_BUTTONS %}
{% include "social_links.html" %}
{% endif %}
{% endblock %}
</div>
</div>
</div>
</section>
{% endblock %}
{% block body_outer %}
<div class="row">
<!-- Map Here -->
<div class="col-md-8">
<div id="embedded_map" class="mrg-btm">
<div id="preview_map"></div>
</div>
<!-- End of Map -->
<div class="layer-actions">
{% include "_actions.html" %}
</div>
<div class="tab-content">
<article id="info" class="tab-pane active">
{% include "base/resourcebase_info_panel.html" %}
</article>
{% if resource.is_mosaic %}
<article id="granules" class="description tab-pane {% if tab and tab == 'granules' %}active{% endif %}">
<div class="paginate-contents">
<form method="GET" action="{% url "layer_detail" resource.layer.service_typename %}">
{% csrf_token %}
<input type="text" id="filter" name="filter" style="witdh:400px"/>&nbsp;<input type="submit" value='{% trans "Filter Granules" %}'/>
</br>
{% if filter %}
<strong>{% trans "Active Filter:" %}</strong> <em>{{ filter }}</em>
{% endif %}
</form>
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr>
<!-- th>{% trans "Granule ID" %}</th -->
<th>{% trans "Bounding Box" %} (minX,minY,maxX,maxY)</th>
<th>{% trans "Time" %}</th>
<!-- th>{% trans "Elevation" %}</th -->
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% autopaginate all_granules.features 10 %}
{% for feature in granules.features %}
<tr>
<!-- td>{{ feature.id }}</td -->
<td><script language="Javascript">var bbox = JSON.parse('{{ feature.geometry.coordinates }}'); document.write('{{ granules.crs.properties.name }};' + bbox[0][0][0] + ',' + bbox[0][0][1] + ',' + bbox[0][2][0] + ',' + bbox[0][2][1]);</script></td>
<td>{% if feature.properties.time %}{{ feature.properties.time }}{% endif %}</td>
<!-- td>{% if feature.properties.elevation %}{{ feature.properties.elevation }}{% endif %}</td -->
<td>
<a class="btn btn-xs" href="#" onclick="doPreview('{{ resource.service_typename }}', '{{ feature.id }}', '{{ granules.crs.properties.name }}', '{{ feature.geometry.coordinates }}', {% if feature.properties.time %}'{{ feature.properties.time }}'{% else %}null{% endif %});"><img alt="Granule Preview" title="Granule Preview" src="/static/lib/img/map_go.png"/></a>
<a class="btn btn-xs" href="{% url "layer_granule_remove" feature.id resource.service_typename %}"><img alt="Granule Remove" title="Granule Remove" src="/static/lib/img/cancel-on.png"/></a>
<script language="Javascript">
function doPreview(layer_name, feature_id, srs, the_geom, time) {
var map = app.mapPanel.map;
for (var ll = 0; ll < app.map.layers.length; ll++) {
var layer = app.map.layers[ll];
if (layer.name == layer_name) {
if (time) {
var re = new RegExp("(\\+[0-9]{4})", "gm");
var timePosition = time;
if (re.test(time)) timePosition = time.replace(re, 'Z');
map.layers[ll].mergeNewParams({"TIME" : timePosition, "CQL_FILTER" : "IN ('" + feature_id + "')"});
}
else {
map.layers[ll].mergeNewParams({"CQL_FILTER" : "IN ('" + feature_id + "')"});
}
map.layers[ll].addOptions({
singleTile: true
});
bbox = JSON.parse(the_geom);
bbox = new OpenLayers.Bounds(bbox[0][0][0],bbox[0][0][1],bbox[0][2][0],bbox[0][2][1]).transform(srs, map.projection);
//map.maxExtent = bbox;
//map.restrictedExtent = bbox;
map.maxExtent = new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34);
map.restrictedExtent = map.maxExtent;
map.zoomToExtent(bbox);
}
}
}
</script>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% paginate %}
</article>
{% endif %}
<article id="attributes" class="tab-pane">
{% if resource.is_mosaic %}
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th>{% trans "Dimension" %}</th>
<th>{% trans "Enabled" %}</th>
<th>{% trans "Regex" %}</th>
</tr>
</thead>
<tbody>
<tr>
<td title="TIME">{% trans "TIME" %}</td>
<td>{% if resource.has_time %}{% trans "True" %}{% else %}{% trans "False" %}{% endif %}</td>
<td>{% if resource.time_regex %}{{ resource.time_regex }}{% else %}&nbsp;{% endif %}</td>
</tr>
<!-- tr>
<td title="ELEVATION">{% trans "ELEVATION" %}</td>
<td>{% if resource.has_elevation %}{% trans "True" %}{% else %}{% trans "False" %}{% endif %}</td>
<td>{% if resource.elevation_regex %}{{ resource.elevation_regex }}{% else %}&nbsp;{% endif %}</td>
</tr -->
</tbody>
</table>
{% endif %}
{% if resource.attribute_set.all %}
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th>{% trans "Attribute Name" %}</th>
<th>{% trans "Label" %}</th>
<th>{% trans "Description" %}</th>
{% if wps_enabled %}
<th>{% trans "Range" %}</th>
<th>{% trans "Average" %}</th>
<th>{% trans "Median" %}</th>
<th>{% trans "Standard Deviation" %}</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for attribute in resource.attributes %}
<tr>
<td {% if attribute.attribute_label and attribute.attribute_label != attribute.attribute %}title="{{ attribute.attribute }}"{% endif %}>{{ attribute }}</td>
<td>{{ attribute.attribute_label|default:"" }}</td>
<td>{{ attribute.description|default:"" }}</td>
{% if wps_enabled %}
{% if attribute.unique_values == "NA" %}
<td>{{ attribute.unique_values }}</td>
{% else %}
<td>
<select name="unique_values">
{% for value in attribute.unique_values_as_list %}
<option value="{{ value }}">{{ value }}</option>
{% endfor %}
</select>
</td>
{% endif %}
<td>{{ attribute.average|floatformat:"2" }}</td>
<td>{{ attribute.median|floatformat:"2" }}</td>
<td>{{ attribute.stddev|floatformat:"2" }}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</article>
<article id="comments" class="tab-pane">
{% with resource as obj %}
{% include "_comments.html" %}
{% endwith %}
</article>
{% if GEOGIG_ENABLED and resource.link_set.geogig %}
{% with "_geogig_layer_detail.html" as geogig_template %}
<article id="geogig" class="tab-pane">
{% with resource as obj %}
{% include geogig_template %}
{% endwith %}
</article>
{% endwith %}
{% endif %}
<article id="rate" class="tab-pane">
<!-- TODO: Move this to a reusable template snippet -->
{% if request.user.is_authenticated %}
<h4>{% trans "Rate this layer" %}</h4>
{% user_rating request.user resource "layer" as user_layer_rating %}
<div id="user_rating" class="category-layer" data-score="{{ user_layer_rating }}"></div>
{% endif %}
<h4>{% trans 'Average Rating' %}</h4>
{% overall_rating resource "layer" as layer_rating %}
{% num_ratings resource as num_votes %}
<div class="overall_rating" data-score="{{ layer_rating }}"></div> ({{num_votes}})
</article>
</div>
</div>
<div class="col-md-4">
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingOne">
<h4 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
A Layer Graph
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">
<div class="panel-body">
<img class="img-responsive" src="https://raw.githubusercontent.com/d3/d3-hexbin/master/img/color.jpg">
<small>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.</small>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingTwo">
<h4 class="panel-title">
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
Another Layer Graph
</a>
</h4>
</div>
<div id="collapseTwo" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingTwo">
<div class="panel-body">
<small>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.</small>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingThree">
<h4 class="panel-title">
<a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
<i class="fa fa-plus"></i> Create graph
</a>
</h4>
</div>
<div id="collapseThree" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingThree">
<div class="panel-body">
<form>
<div class="form-group">
<label for="exampleInputEmail1">Category</label>
<select class="form-control">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
<div class="form-group">
<label for="exampleInputPassword1">Quantity</label>
<select class="form-control">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
<div class="form-group">
<label for="exampleInputPassword1">Type</label>
<select class="form-control">
<option>Pie Chart</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
<div class="form-group">
<label for="exampleInputPassword1">Aggregation</label>
<select class="form-control">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
<button type="submit" class="btn btn-default">Create Graph</button>
<button type="submit" class="btn btn-default">Save Chart</button>
</form>
</div>
</div>
</div>
<ul class="list-group">
{% if resource.storeType != "remoteStore" %}
<li class="list-group-item">
{% if links or links_download %}
<button class="btn btn-primary btn-md btn-block" data-toggle="modal" data-target="#download-layer">{% trans "Download Layer" %}</button>
{% else %}
{% if request.user.is_authenticated %}
<button class="btn btn-primary btn-md btn-block" id="request-download">{% trans "Request Download" %}</button>
{% endif %}
{% endif %}
</li>
{% endif %}
<div class="modal fade" id="download-layer" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">{% trans "Download Layer" %}</h4>
</div>
<div class="modal-body">
<div class="tabbable">
<ul class="nav nav-tabs">
{% if links %}
<li class="active"><a href="#download_tab1" data-toggle="tab">Images</a></li>
{% endif %}
{% if links_download %}
<li><a href="#download_tab2" data-toggle="tab">Data</a></li>
{% endif %}
</ul>
<div class="tab-content">
{% if links %}
<div class="tab-pane active" id="download_tab1">
<li class="active">
<ul>
{% for link in links %}
<li><a href="{{ link.url }}" target="_blank">{% trans link.name %}</a></li>
{% endfor %}
</ul>
</li>
</div>
{% endif %}
{% if links_download %}
<div class="tab-pane" id="download_tab2">
<li>
<ul>
{% for link in links_download %}
<li><a href="{{ link.url }}" target="_blank">{% trans link.name %}</a></li>
{% endfor %}
</ul>
</li>
</div>
{% endif %}
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button>
</div>
</div>
</div>
</div>
{% if GEOSERVER_BASE_URL %}
{% get_obj_perms request.user for resource.layer as "layer_perms" %}
{% endif %}
<li class="list-group-item">
<a href="{% url "layer_metadata_detail" resource.typename %}">
<button class="btn btn-primary btn-md btn-block">{% trans "Metadata Detail" %}</button>
</a>
</li>
{% 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 %}
<li class="list-group-item">
<button class="btn btn-primary btn-md btn-block" data-toggle="modal" data-target="#edit-layer">{% trans "Edit Layer" %}</button>
</li>
<div class="modal fade" id="edit-layer" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">{% trans "Edit Layer" %}</h4>
</div>
<div class="modal-body">
<div class="row edit-modal">
{% if "change_resourcebase_metadata" in perms_list %}
<div class="col-sm-3">
<i class="fa fa-list-alt fa-3x"></i>
<h4>{% trans "Metadata" %}</h4>
<a class="btn btn-default btn-block btn-xs" href="{% url "layer_metadata" resource.service_typename %}">{% trans "Edit" %}</a>
</div>
{% endif %}
{% if GEOSERVER_BASE_URL and not resource.service %}
{% if "change_layer_style" in layer_perms %}
<div class="col-sm-3">
<i class="fa fa-tint fa-3x"></i>
<h4>{% trans "Styles" %}</h4>
{% if preview == 'geoext' %}
<a class="btn btn-default btn-block btn-xs style-edit" data-dismiss="modal" href="#">{% trans "Edit" %}</a>
<a class="btn btn-default btn-block btn-xs" href="{% url "layer_style_manage" resource.service_typename %}">{% trans "Manage" %}</a>
{% endif %}
</div>
{% endif %}
{% endif %}
{% if "change_resourcebase" in perms_list %}
<div class="col-sm-3">
<i class="fa fa-photo fa-3x"></i>
<h4>{% trans "Thumbnail" %}</h4>
<a class="btn btn-default btn-block btn-xs" href="#" id="set_thumbnail">{% trans "Set" %}</a>
</div>
{% endif %}
{% if "change_resourcebase" in perms_list or "delete_resourcebase" in perms_list or "change_layer_data" in layer_perms %}
<div class="col-sm-3">
<i class="fa fa-square-o fa-3x rotate-45"></i>
<h4>{% trans "Layer" %}</h4>
{% if "change_resourcebase" in perms_list and not resource.service %}
<a class="btn btn-default btn-block btn-xs" href="{% url "layer_replace" resource.service_typename %}">{% trans "Replace" %}</a>
{% endif %}
{% if resource.storeType == 'dataStore' and "change_layer_data" in layer_perms %}
<a class="btn btn-default btn-block btn-xs" href="{% url "new_map" %}?layer={{resource.service_typename}}">{% trans "Edit data" %}</a>
{% endif %}
{% if "delete_resourcebase" in perms_list %}
<a class="btn btn-danger btn-block btn-xs" href="{% url "layer_remove" resource.service_typename %}">{% trans "Remove" %}</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button>
</div>
</div>
</div>
</div>
{% endif %}
<li class="list-group-item">
<button class="btn btn-default btn-md btn-block" data-toggle="modal" data-target="#download-metadata">{% trans "Download Metadata" %}</button>
</li>
<div class="modal fade" id="download-metadata" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">{% trans "Download Metadata" %}</h4>
</div>
<div class="modal-body">
<div style="margin-bottom:20px">
<h5 class="modal-title" id="myModalLabel">
Full metadata
</h5>
<ul>
<li><a target="_blank" href="../catalogue/csw_to_extra_format/{{resource.uuid}}/{{resource.title}}.txt"> Text format </a></li>
<li><a target="_blank" href="../catalogue/csw_to_extra_format/{{resource.uuid}}/{{resource.title}}.html"> HTML format </a></li>
</ul>
</div>
<div style="margin-bottom:20px">
<h5 class="modal-title" id="myModalLabel">
Standard Metadata - XML format
</h5>
<ul>
{% for link in metadata %}
<li><a href="{{ link.url }}" target="_blank">{{ link.name }}</a></li>
{% endfor %}
<ul>
</div>
{% if resource.storeType == 'dataStore' %}
<h5 class="modal-title" id="myModalLabel">Attribute Information</h5>
<ul>
<li><a href="{{ resource.get_absolute_url }}/feature_catalogue" target="_blank">ISO Feature Catalogue</a></li>
<ul>
{% endif %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button>
</div>
</div>
</div>
</div>
{% if resource.get_legend %}
<li class="list-group-item">
<h4 class="list-group-item-heading">{%trans "Legend" %}</h4>
<p>{{ style.sld_title }}</p>
{% if resource.get_legend.link_type == 'image' %}
{% if request.user.is_authenticated and 'access_token' in request.session %}
<p><img id="legend_icon" src="{{ resource.get_legend.url }}&access_token={{ request.session.access_token }}"></p>
{% else %}
<p><img id="legend_icon" src="{{ resource.get_legend.url }}"></p>
{% endif %}
{% else %}
<div id='legend_placeholder'></div>
{% endif %}
</li>
{% endif %}
<li class="list-group-item">
<h4>{% trans "Maps using this layer" %}</h4>
{% if resource.maps %}
<p>{% trans "List of maps using this layer:" %}</p>
{% endif %}
<ul class="list-unstyled">
{% for maplayer in resource.maps %}
<li><a href="{{ maplayer.map.get_absolute_url }}">{{ maplayer.map.title }}</a></li>
{% empty %}
<li>{% trans "This layer is not currently used in any maps." %}</li>
{% endfor %}
</ul>
</li>
<li class="list-group-item">
<h4>{% trans "Create a map using this layer" %}</h4>
<p>{% trans "Click the button below to generate a new map based on this layer." %}</p>
<a href="{% url "new_map" %}?layer={{resource.service_typename}}" class="btn btn-primary btn-block">{% trans "Create a Map" %}</a>
</li>
{% if documents.count > 0 %}
<li class="list-group-item">
<h4>{% trans "Documents related to this layer" %}</h4>
<p>{% trans "List of documents related to this layer:" %}</p>
<ul class="list-unstyled">
{% for document in documents %}
<li><a href="{{ document.get_absolute_url }}">{{ document.title }}</a></li>
{% endfor %}
</ul>
</li>
{% endif %}
{% if user.is_authenticated %}
<li class="list-group-item">
<h4>{% trans "Styles" %}</h4>
<p>{% trans "The following styles are associated with this layer. Choose a style to view it in the preview map." %}</p>
<ul class="list-unstyled">
{% for style in resource.styles.all %}
<li>
{% if resource.default_style == style %}
<input type="radio" checked name="style" id="{{ style.name }}" value="{{ style.title }}"/>
(default style)
{% else %}
<input type="radio" name="style" id="{{ style.name }}" value="{{ style.title }}"/>
{% endif %}
<a href="{{ GEOSERVER_BASE_URL }}{{ style.absolute_url }}" >{{ style.sld_title }}</a>
</li>
{% empty %}
<li>{% trans "No styles associated with this layer" %}</li>
{% endfor %}
</ul>
</li>
{% endif %}
{% if GEONODE_SECURITY_ENABLED %}
{% if "change_resourcebase_permissions" in perms_list %}
<li class="list-group-item">
<h4>{% trans "Permissions" %}</h4>
<p>{% trans "Click the button below to change the permissions of this layer." %}</p>
<p><a href="#modal_perms" data-toggle="modal" class="btn btn-primary btn-block" data-target="#_permissions">{% trans "Change Layer Permissions" %}</a></p>
</li>
{% endif %}
{% endif %}
{% if resource.storeType == "remoteStore" %}
<li class="list-group-item">
<h4>{% trans "External service layer" %}</h4>
<div>{% trans "Source" %}: <a href="/services/{{resource.service.id}}">{{ resource.service.title }}</a></div>
<div>{% trans "Type" %}: {{ resource.service.type }}</div>
</li>
{% endif %}
{% include "base/_resourcebase_contact_snippet.html" %}
</ul>
{% if GEONODE_SECURITY_ENABLED %}
{% include "_permissions_form.html" %}
{% endif %}
</div> <!-- col-md-4 -->
</div> <!-- row -->
{% 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' %}
<script type="text/javascript">
$("#comment_submit_btn").click(function(event) {
$.ajax({
type: "POST",
url: $("#form_post_comment").attr('action'),
data: $("#form_post_comment").serialize(),
success: function() {
$('#form_post_comment_div').modal().find('form')[0].reset();
$('#form_post_comment_div').modal('hide');
$('#comments_section').load(window.location.pathname + ' #comments_section',
function(){$(this).children().unwrap()})
}
});
return false;
});
{% if resource.get_legend.link_type == 'json' %}
$.getJSON('{{resource.get_legend_url}}', function (data) {
var legend = data.layers[0].legend;
var items = [];
$.each(legend, function (key, dvalue) {
items.push('<li><img src="data:image/png;base64,' + dvalue.imageData + '">'+dvalue.label+'</li>');
});
$('<ul/>', {
'class': 'arcgis-legend',
html: items.join('')
}).appendTo('#legend_placeholder');
});
{% endif %}
</script>
<script type="text/javascript">
$('#set_thumbnail').click(function(){
createMapThumbnail();
$('#edit-layer').modal('toggle');
});
</script>
{% if GEONODE_SECURITY_ENABLED %}
{% include "_permissions_form_js.html" %}
{% endif %}
{% endblock extra_script %}
@@ -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 %}
<section id="subheader">
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>{{ resource.title|default:resource.typename }}</h2>
<p>
{{ resource.service_type }}
{% if resource.category %}
{% trans "in" %} <a href="{% url "search" %}?category__identifier__in={{ resource.category.identifier }}">{{ resource.category }}</a> |
{% 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 %}
</p>
{% block social_links %}
{% if SOCIAL_BUTTONS %}
{% include "social_links.html" %}
{% endif %}
{% endblock %}
</div>
</div>
</div>
</section>
{% endblock %}
{% block body_outer %}
<div class="row">
<div class="col-md-8">
<div id="embedded_map" class="mrg-btm">
<div id="preview_map"></div>
</div>
<div class="layer-actions">
{% include "_actions.html" %}
</div>
<div class="tab-content">
<article id="info" class="tab-pane active">
{% include "base/resourcebase_info_panel.html" %}
</article>
{% if resource.is_mosaic %}
<article id="granules" class="description tab-pane {% if tab and tab == 'granules' %}active{% endif %}">
<div class="paginate-contents">
<form method="GET" action="{% url "layer_detail" resource.layer.service_typename %}">
{% csrf_token %}
<input type="text" id="filter" name="filter" style="witdh:400px"/>&nbsp;<input type="submit" value='{% trans "Filter Granules" %}'/>
</br>
{% if filter %}
<strong>{% trans "Active Filter:" %}</strong> <em>{{ filter }}</em>
{% endif %}
</form>
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr>
<!-- th>{% trans "Granule ID" %}</th -->
<th>{% trans "Bounding Box" %} (minX,minY,maxX,maxY)</th>
<th>{% trans "Time" %}</th>
<!-- th>{% trans "Elevation" %}</th -->
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% autopaginate all_granules.features 10 %}
{% for feature in granules.features %}
<tr>
<!-- td>{{ feature.id }}</td -->
<td><script language="Javascript">var bbox = JSON.parse('{{ feature.geometry.coordinates }}'); document.write('{{ granules.crs.properties.name }};' + bbox[0][0][0] + ',' + bbox[0][0][1] + ',' + bbox[0][2][0] + ',' + bbox[0][2][1]);</script></td>
<td>{% if feature.properties.time %}{{ feature.properties.time }}{% endif %}</td>
<!-- td>{% if feature.properties.elevation %}{{ feature.properties.elevation }}{% endif %}</td -->
<td>
<a class="btn btn-xs" href="#" onclick="doPreview('{{ resource.service_typename }}', '{{ feature.id }}', '{{ granules.crs.properties.name }}', '{{ feature.geometry.coordinates }}', {% if feature.properties.time %}'{{ feature.properties.time }}'{% else %}null{% endif %});"><img alt="Granule Preview" title="Granule Preview" src="/static/lib/img/map_go.png"/></a>
<a class="btn btn-xs" href="{% url "layer_granule_remove" feature.id resource.service_typename %}"><img alt="Granule Remove" title="Granule Remove" src="/static/lib/img/cancel-on.png"/></a>
<script language="Javascript">
function doPreview(layer_name, feature_id, srs, the_geom, time) {
var map = app.mapPanel.map;
for (var ll = 0; ll < app.map.layers.length; ll++) {
var layer = app.map.layers[ll];
if (layer.name == layer_name) {
if (time) {
var re = new RegExp("(\\+[0-9]{4})", "gm");
var timePosition = time;
if (re.test(time)) timePosition = time.replace(re, 'Z');
map.layers[ll].mergeNewParams({"TIME" : timePosition, "CQL_FILTER" : "IN ('" + feature_id + "')"});
}
else {
map.layers[ll].mergeNewParams({"CQL_FILTER" : "IN ('" + feature_id + "')"});
}
map.layers[ll].addOptions({
singleTile: true
});
bbox = JSON.parse(the_geom);
bbox = new OpenLayers.Bounds(bbox[0][0][0],bbox[0][0][1],bbox[0][2][0],bbox[0][2][1]).transform(srs, map.projection);
//map.maxExtent = bbox;
//map.restrictedExtent = bbox;
map.maxExtent = new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34);
map.restrictedExtent = map.maxExtent;
map.zoomToExtent(bbox);
}
}
}
</script>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% paginate %}
</article>
{% endif %}
<article id="attributes" class="tab-pane">
{% if resource.is_mosaic %}
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th>{% trans "Dimension" %}</th>
<th>{% trans "Enabled" %}</th>
<th>{% trans "Regex" %}</th>
</tr>
</thead>
<tbody>
<tr>
<td title="TIME">{% trans "TIME" %}</td>
<td>{% if resource.has_time %}{% trans "True" %}{% else %}{% trans "False" %}{% endif %}</td>
<td>{% if resource.time_regex %}{{ resource.time_regex }}{% else %}&nbsp;{% endif %}</td>
</tr>
<!-- tr>
<td title="ELEVATION">{% trans "ELEVATION" %}</td>
<td>{% if resource.has_elevation %}{% trans "True" %}{% else %}{% trans "False" %}{% endif %}</td>
<td>{% if resource.elevation_regex %}{{ resource.elevation_regex }}{% else %}&nbsp;{% endif %}</td>
</tr -->
</tbody>
</table>
{% endif %}
{% if resource.attribute_set.all %}
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th>{% trans "Attribute Name" %}</th>
<th>{% trans "Label" %}</th>
<th>{% trans "Description" %}</th>
{% if wps_enabled %}
<th>{% trans "Range" %}</th>
<th>{% trans "Average" %}</th>
<th>{% trans "Median" %}</th>
<th>{% trans "Standard Deviation" %}</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for attribute in resource.attributes %}
<tr>
<td {% if attribute.attribute_label and attribute.attribute_label != attribute.attribute %}title="{{ attribute.attribute }}"{% endif %}>{{ attribute }}</td>
<td>{{ attribute.attribute_label|default:"" }}</td>
<td>{{ attribute.description|default:"" }}</td>
{% if wps_enabled %}
{% if attribute.unique_values == "NA" %}
<td>{{ attribute.unique_values }}</td>
{% else %}
<td>
<select name="unique_values">
{% for value in attribute.unique_values_as_list %}
<option value="{{ value }}">{{ value }}</option>
{% endfor %}
</select>
</td>
{% endif %}
<td>{{ attribute.average|floatformat:"2" }}</td>
<td>{{ attribute.median|floatformat:"2" }}</td>
<td>{{ attribute.stddev|floatformat:"2" }}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</article>
<article id="comments" class="tab-pane">
{% with resource as obj %}
{% include "_comments.html" %}
{% endwith %}
</article>
{% if GEOGIG_ENABLED and resource.link_set.geogig %}
{% with "_geogig_layer_detail.html" as geogig_template %}
<article id="geogig" class="tab-pane">
{% with resource as obj %}
{% include geogig_template %}
{% endwith %}
</article>
{% endwith %}
{% endif %}
<article id="rate" class="tab-pane">
<!-- TODO: Move this to a reusable template snippet -->
{% if request.user.is_authenticated %}
<h4>{% trans "Rate this layer" %}</h4>
{% user_rating request.user resource "layer" as user_layer_rating %}
<div id="user_rating" class="category-layer" data-score="{{ user_layer_rating }}"></div>
{% endif %}
<h4>{% trans 'Average Rating' %}</h4>
{% overall_rating resource "layer" as layer_rating %}
{% num_ratings resource as num_votes %}
<div class="overall_rating" data-score="{{ layer_rating }}"></div> ({{num_votes}})
</article>
</div>
</div>
<div class="col-md-4">
<ul class="list-group">
{% if resource.storeType != "remoteStore" %}
<li class="list-group-item">
{% if links or links_download %}
<button class="btn btn-primary btn-md btn-block" data-toggle="modal" data-target="#download-layer">{% trans "Download Layer" %}</button>
{% else %}
{% if request.user.is_authenticated %}
<button class="btn btn-primary btn-md btn-block" id="request-download">{% trans "Request Download" %}</button>
{% endif %}
{% endif %}
</li>
{% endif %}
<div class="modal fade" id="download-layer" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">{% trans "Download Layer" %}</h4>
</div>
<div class="modal-body">
<div class="tabbable">
<ul class="nav nav-tabs">
{% if links %}
<li class="active"><a href="#download_tab1" data-toggle="tab">Images</a></li>
{% endif %}
{% if links_download %}
<li><a href="#download_tab2" data-toggle="tab">Data</a></li>
{% endif %}
</ul>
<div class="tab-content">
{% if links %}
<div class="tab-pane active" id="download_tab1">
<li class="active">
<ul>
{% for link in links %}
<li><a href="{{ link.url }}" target="_blank">{% trans link.name %}</a></li>
{% endfor %}
</ul>
</li>
</div>
{% endif %}
{% if links_download %}
<div class="tab-pane" id="download_tab2">
<li>
<ul>
{% for link in links_download %}
<li><a href="{{ link.url }}" target="_blank">{% trans link.name %}</a></li>
{% endfor %}
</ul>
</li>
</div>
{% endif %}
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button>
</div>
</div>
</div>
</div>
{% if GEOSERVER_BASE_URL %}
{% get_obj_perms request.user for resource.layer as "layer_perms" %}
{% endif %}
<li class="list-group-item">
<a href="{% url "layer_metadata_detail" resource.typename %}">
<button class="btn btn-primary btn-md btn-block">{% trans "Metadata Detail" %}</button>
</a>
</li>
{% 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 %}
<li class="list-group-item">
<button class="btn btn-primary btn-md btn-block" data-toggle="modal" data-target="#edit-layer">{% trans "Edit Layer" %}</button>
</li>
<div class="modal fade" id="edit-layer" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">{% trans "Edit Layer" %}</h4>
</div>
<div class="modal-body">
<div class="row edit-modal">
{% if "change_resourcebase_metadata" in perms_list %}
<div class="col-sm-3">
<i class="fa fa-list-alt fa-3x"></i>
<h4>{% trans "Metadata" %}</h4>
<a class="btn btn-default btn-block btn-xs" href="{% url "layer_metadata" resource.service_typename %}">{% trans "Edit" %}</a>
</div>
{% endif %}
{% if GEOSERVER_BASE_URL and not resource.service %}
{% if "change_layer_style" in layer_perms %}
<div class="col-sm-3">
<i class="fa fa-tint fa-3x"></i>
<h4>{% trans "Styles" %}</h4>
{% if preview == 'geoext' %}
<a class="btn btn-default btn-block btn-xs style-edit" data-dismiss="modal" href="#">{% trans "Edit" %}</a>
<a class="btn btn-default btn-block btn-xs" href="{% url "layer_style_manage" resource.service_typename %}">{% trans "Manage" %}</a>
{% endif %}
</div>
{% endif %}
{% endif %}
{% if "change_resourcebase" in perms_list %}
<div class="col-sm-3">
<i class="fa fa-photo fa-3x"></i>
<h4>{% trans "Thumbnail" %}</h4>
<a class="btn btn-default btn-block btn-xs" href="#" id="set_thumbnail">{% trans "Set" %}</a>
</div>
{% endif %}
{% if "change_resourcebase" in perms_list or "delete_resourcebase" in perms_list or "change_layer_data" in layer_perms %}
<div class="col-sm-3">
<i class="fa fa-square-o fa-3x rotate-45"></i>
<h4>{% trans "Layer" %}</h4>
{% if "change_resourcebase" in perms_list and not resource.service %}
<a class="btn btn-default btn-block btn-xs" href="{% url "layer_replace" resource.service_typename %}">{% trans "Replace" %}</a>
{% endif %}
{% if resource.storeType == 'dataStore' and "change_layer_data" in layer_perms %}
<a class="btn btn-default btn-block btn-xs" href="{% url "new_map" %}?layer={{resource.service_typename}}">{% trans "Edit data" %}</a>
{% endif %}
{% if "delete_resourcebase" in perms_list %}
<a class="btn btn-danger btn-block btn-xs" href="{% url "layer_remove" resource.service_typename %}">{% trans "Remove" %}</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button>
</div>
</div>
</div>
</div>
{% endif %}
<li class="list-group-item">
<button class="btn btn-default btn-md btn-block" data-toggle="modal" data-target="#download-metadata">{% trans "Download Metadata" %}</button>
</li>
<div class="modal fade" id="download-metadata" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">{% trans "Download Metadata" %}</h4>
</div>
<div class="modal-body">
<div style="margin-bottom:20px">
<h5 class="modal-title" id="myModalLabel">
Full metadata
</h5>
<ul>
<li><a target="_blank" href="../catalogue/csw_to_extra_format/{{resource.uuid}}/{{resource.title}}.txt"> Text format </a></li>
<li><a target="_blank" href="../catalogue/csw_to_extra_format/{{resource.uuid}}/{{resource.title}}.html"> HTML format </a></li>
</ul>
</div>
<div style="margin-bottom:20px">
<h5 class="modal-title" id="myModalLabel">
Standard Metadata - XML format
</h5>
<ul>
{% for link in metadata %}
<li><a href="{{ link.url }}" target="_blank">{{ link.name }}</a></li>
{% endfor %}
<ul>
</div>
{% if resource.storeType == 'dataStore' %}
<h5 class="modal-title" id="myModalLabel">Attribute Information</h5>
<ul>
<li><a href="{{ resource.get_absolute_url }}/feature_catalogue" target="_blank">ISO Feature Catalogue</a></li>
<ul>
{% endif %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Close" %}</button>
</div>
</div>
</div>
</div>
{% if resource.get_legend %}
<li class="list-group-item">
<h4 class="list-group-item-heading">{%trans "Legend" %}</h4>
<p>{{ style.sld_title }}</p>
{% if resource.get_legend.link_type == 'image' %}
{% if request.user.is_authenticated and 'access_token' in request.session %}
<p><img id="legend_icon" src="{{ resource.get_legend.url }}&access_token={{ request.session.access_token }}"></p>
{% else %}
<p><img id="legend_icon" src="{{ resource.get_legend.url }}"></p>
{% endif %}
{% else %}
<div id='legend_placeholder'></div>
{% endif %}
</li>
{% endif %}
<li class="list-group-item">
<h4>{% trans "Maps using this layer" %}</h4>
{% if resource.maps %}
<p>{% trans "List of maps using this layer:" %}</p>
{% endif %}
<ul class="list-unstyled">
{% for maplayer in resource.maps %}
<li><a href="{{ maplayer.map.get_absolute_url }}">{{ maplayer.map.title }}</a></li>
{% empty %}
<li>{% trans "This layer is not currently used in any maps." %}</li>
{% endfor %}
</ul>
</li>
<li class="list-group-item">
<h4>{% trans "Create a map using this layer" %}</h4>
<p>{% trans "Click the button below to generate a new map based on this layer." %}</p>
<a href="{% url "new_map" %}?layer={{resource.service_typename}}" class="btn btn-primary btn-block">{% trans "Create a Map" %}</a>
</li>
{% if documents.count > 0 %}
<li class="list-group-item">
<h4>{% trans "Documents related to this layer" %}</h4>
<p>{% trans "List of documents related to this layer:" %}</p>
<ul class="list-unstyled">
{% for document in documents %}
<li><a href="{{ document.get_absolute_url }}">{{ document.title }}</a></li>
{% endfor %}
</ul>
</li>
{% endif %}
{% if user.is_authenticated %}
<li class="list-group-item">
<h4>{% trans "Styles" %}</h4>
<p>{% trans "The following styles are associated with this layer. Choose a style to view it in the preview map." %}</p>
<ul class="list-unstyled">
{% for style in resource.styles.all %}
<li>
{% if resource.default_style == style %}
<input type="radio" checked name="style" id="{{ style.name }}" value="{{ style.title }}"/>
(default style)
{% else %}
<input type="radio" name="style" id="{{ style.name }}" value="{{ style.title }}"/>
{% endif %}
<a href="{{ GEOSERVER_BASE_URL }}{{ style.absolute_url }}" >{{ style.sld_title }}</a>
</li>
{% empty %}
<li>{% trans "No styles associated with this layer" %}</li>
{% endfor %}
</ul>
</li>
{% endif %}
{% if GEONODE_SECURITY_ENABLED %}
{% if "change_resourcebase_permissions" in perms_list %}
<li class="list-group-item">
<h4>{% trans "Permissions" %}</h4>
<p>{% trans "Click the button below to change the permissions of this layer." %}</p>
<p><a href="#modal_perms" data-toggle="modal" class="btn btn-primary btn-block" data-target="#_permissions">{% trans "Change Layer Permissions" %}</a></p>
</li>
{% endif %}
{% endif %}
{% if resource.storeType == "remoteStore" %}
<li class="list-group-item">
<h4>{% trans "External service layer" %}</h4>
<div>{% trans "Source" %}: <a href="/services/{{resource.service.id}}">{{ resource.service.title }}</a></div>
<div>{% trans "Type" %}: {{ resource.service.type }}</div>
</li>
{% endif %}
{% include "base/_resourcebase_contact_snippet.html" %}
</ul>
{% if GEONODE_SECURITY_ENABLED %}
{% include "_permissions_form.html" %}
{% endif %}
</div> <!-- col-md-4 -->
</div> <!-- row -->
{% 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' %}
<script type="text/javascript">
$("#comment_submit_btn").click(function(event) {
$.ajax({
type: "POST",
url: $("#form_post_comment").attr('action'),
data: $("#form_post_comment").serialize(),
success: function() {
$('#form_post_comment_div').modal().find('form')[0].reset();
$('#form_post_comment_div').modal('hide');
$('#comments_section').load(window.location.pathname + ' #comments_section',
function(){$(this).children().unwrap()})
}
});
return false;
});
{% if resource.get_legend.link_type == 'json' %}
$.getJSON('{{resource.get_legend_url}}', function (data) {
var legend = data.layers[0].legend;
var items = [];
$.each(legend, function (key, dvalue) {
items.push('<li><img src="data:image/png;base64,' + dvalue.imageData + '">'+dvalue.label+'</li>');
});
$('<ul/>', {
'class': 'arcgis-legend',
html: items.join('')
}).appendTo('#legend_placeholder');
});
{% endif %}
</script>
<script type="text/javascript">
$('#set_thumbnail').click(function(){
createMapThumbnail();
$('#edit-layer').modal('toggle');
});
</script>
{% if GEONODE_SECURITY_ENABLED %}
{% include "_permissions_form_js.html" %}
{% endif %}
{% endblock extra_script %}
+204 -17
View File
@@ -9,7 +9,17 @@
{% load guardian_tags %}
{% block title %}{{ block.super }} {% trans "Layers" %} | {{ resource.title|default:resource.typename }}{% endblock %}
{% block page-title %}{% trans "Explore Layers" %}{% endblock %}
{% block page-title %}
{% trans "Explore Layers" %}
<div class="btn-group pull-right">
<a class="btn btn-primary btn-icon" href="{% url "layer_browse" %}">
<i class="fa fa-bars" title="All Layers"></i>
</a>
<a class="btn btn-primary" href="{% url "layer_browse" %}">
{% trans "All Layers" %}
</a>
</div>
{% endblock %}
{% block head %}
@@ -38,16 +48,16 @@
{% block subheader %}
<section id="subheader">
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>{{ resource.title|default:resource.typename }}</h2>
<p>
<div class="container">
<div class="row">
<div class="col-md-12">
<h2>{{ resource.title|default:resource.typename }}</h2>
<p>
{{ resource.service_type }}
{% if resource.category %}
{% trans "in" %} <a href="{% url "search" %}?category__identifier__in={{ resource.category.identifier }}">{{ resource.category }}</a> |
{% trans "in" %} <a href="{% url "search" %}?category__identifier__in={{ resource.category.identifier }}">{{ resource.category }}</a> |
{% endif %}
{% if resource.date %}
{% 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 }}
@@ -59,13 +69,14 @@
{% include "social_links.html" %}
{% endif %}
{% endblock %}
</div>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block body_outer %}
<div class="row">
<div class="col-md-8">
@@ -227,11 +238,6 @@
{% endif %}
</article>
<article id="charts" class="tab-pane">
<h4>No charts available at this time</h4>
<a href="/table/fields?lyrname={{ resource.typename }}">Create Chart</a>
</article>
<article id="comments" class="tab-pane">
{% with resource as obj %}
{% include "_comments.html" %}
@@ -271,8 +277,8 @@
{% if resource.storeType != "remoteStore" %}
<li class="list-group-item">
{% if links or links_download %}
<button class="btn btn-primary btn-md btn-block" data-toggle="modal" data-target="#download-layer">{% trans "Download Layer" %}</button>
{% else %}
<button class="btn btn-primary btn-md btn-block" data-toggle="modal" data-target="#download-layer">{% trans "Download Layer" %}</button>
{% else %}
{% if request.user.is_authenticated %}
<button class="btn btn-primary btn-md btn-block" id="request-download">{% trans "Request Download" %}</button>
{% endif %}
@@ -482,6 +488,186 @@
<a href="{% url "new_map" %}?layer={{resource.service_typename}}" class="btn btn-primary btn-block">{% trans "Create a Map" %}</a>
</li>
<!-- Charts section -->
<li class="list-group-item">
<h4>{% trans "Charts using this layer" %}</h4>
{% if resource.maps %}
<p>{% trans "List of charts with this layer:" %}</p>
{% endif %}
<ul class="list-unstyled">
{% for chart in resource.chart_set.all %}
<li><a href="{% url 'chart_detail' chart.id %}">{{ chart.get_type_display }}</a>, Category field = {{chart.category}}, Quantity field = {{chart.quantity}}</li>
{% empty %}
<li><p>No charts available at this time</p></li>
{% endfor %}
</ul>
</li>
{% if "download_resourcebase" in perms_list %}
<li class="list-group-item" id="create-chart">
<h4>{% trans "Create a chart using this layer" %}</h4>
<p>{% trans "Click the button below to generate a new chart based on this layer, choosing a quantity and a category field." %}</p>
<button class="btn btn-primary btn-md btn-block" data-toggle="collapse" data-target="#form" onclick="updateLinkAndTitle();">Create a chart</button>
<div id="form" class="collapse">
<form name="f" method="post" class="form-horizontal" onchange="updateLinkAndTitle();" action="/chart/add/{{ resource.id }}/">
{% csrf_token %}
<div class="form-group">
<input id="id_layer" name="layer" value="{{ resource.id }}" type="hidden">
</div>
<div class="form-group">
<label class="control-label col-md-2" for="id_category">Category:</label>
<div class="col-md-8">
<select class="form-control" id="id_category" name="category" >
{% for attribute in resource.attributes %}
<option value={{ attribute.attribute }}>{{ attribute.attribute }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="id_quantity">Quantity:</label>
<div class="col-md-8">
<select class="form-control" id="id_quantity" name="quantity">
{% for attribute in resource.attributes %}
{% if 'gml:' not in attribute.attribute_type and 'string' not in attribute.attribute_type%}
<option value={{ attribute.attribute }}>{{ attribute.attribute }}</option>
{% endif %}
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="id_type">Type:</label>
<div class="col-md-8">
<select class="form-control" id="id_type" name="type">
<option value="0" selected="selected">Bar chart</option>
<option value="1">Pie chart</option>
<option value="2">Donut chart</option>
<option value="3">Line chart</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="id_aggr_type">Aggregation:</label>
<div class="col-md-8">
<select class="form-control" id="id_aggr_type" name="aggr_type">
<option value="0">Sum</option>
<option value="1">Mean</option>
<option value="2">Category count</option>
<option value="3">Max</option>
<option value="4">Min</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="id_title">Title:</label>
<div class="col-md-8">
<input id="id_title" maxlength="128" name="title" type="text" class="form-control" >
<p style="font-size: small; margin:0">Suggested title:</p>
<p id="title" style="font-size: small; margin:0">Testo di prova</p>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="id_abstract">Abstract:</label>
<div class="col-md-8">
<textarea class="form-control" cols="40" id="id_abstract" name="abstract" rows="5"></textarea>
</div>
</div>
<button type="button" class="btn btn-primary btn-block" data-toggle="modal" data-target="#chart_popup" onclick="get_chart_popup()">Preview</button>
<button type="submit" class="btn btn-primary btn-block" onclick="title_eval();">Save</button>
</form>
</div>
</li>
{% endif %}
<div id="chart_popup" class="modal fade" role="dialog" >
<script src="/static/js/csv.js"></script>
<script src="/static/js/donut.js"></script>
<script src="/static/js/bar.js"></script>
<script src="/static/js/pie.js"></script>
<script src="/static/js/line.js"></script>
<script src="/static/js/getChart.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdn.rawgit.com/eligrey/canvas-toBlob.js/f1a01896135ab378aa5c0118eadd81da55e698d8/canvas-toBlob.js"></script>
<script src="https://cdn.rawgit.com/eligrey/FileSaver.js/e9d941381475b5df8b7d7691013401e171014e89/FileSaver.min.js"></script>
<link rel="stylesheet" href="/static/css/index.css">
<div class="modal-dialog" id="chart" style="width: 55%">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 id="chart_title" class="modal-title"></h4>
</div>
<div class="modal-body">
<div id="chart_area" style="display: table; float: inherit; width: 50%;">
</div>
<div>
<h5>Abstract</h5>
<p id="abstract"></p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
var typename = "{{ resource.typename }}";
var lyr;
var category;
var quantity;
var agg;
var chart_type;
var csv_url;
var suggestedTitle = "";
function update_link(){
csv_url = '/table?lyrname=' + typename;
category = document.f.category.value;
csv_url = csv_url + '&category=' + category;
quantity = document.f.quantity.value;
csv_url = csv_url + '&quantity=' + quantity;
agg = document.f.aggr_type.value;
chart_type = document.f.type.value;
return;
}
function attachAbstract(){
var abstractText = document.getElementById("id_abstract").value;
document.getElementById('abstract').innerHTML = abstractText;
}
function chart_title(){
if (agg == dict_title_agg[2]){
suggestedTitle = dict_title_agg[agg] + " of elements for " + category;
} else {
var title = dict_title_agg[agg]
suggestedTitle = title + " of " + quantity + " for " + category;
}
document.getElementById("title").innerHTML = suggestedTitle;
return suggestedTitle;
};
function title_eval(){
if (document.getElementById("id_title").textLength == 0){
document.getElementById("id_title").value = suggestedTitle;
}
}
function updateLinkAndTitle(){
update_link();
chart_title();
attachAbstract();
}
</script>
{% if documents.count > 0 %}
<li class="list-group-item">
<h4>{% trans "Documents related to this layer" %}</h4>
@@ -547,6 +733,7 @@
{% endblock %}
{% block extra_script %}
{{ block.super }}
{% if GEOGIG_ENABLED and resource.link_set.geogig %}
{% with "_geogig_scripts.html" as geogig_scripts_template %}
+75 -74
View File
@@ -36,6 +36,7 @@
<ul class="dropdown-menu">
<li><a href="{% url "layer_browse" %}">{% trans "Layers" %}</a></li>
<li><a href="{% url "document_browse" %}">{% trans "Documents" %}</a></li>
<li><a href="{% url "chart_list" %}">{% trans "Charts" %}</a></li>
{% if user.is_authenticated %}
<li><a href="/uploads">{% trans "Uploads" %}</a></li>
<li><a href="{% url "services" %}">{% trans "Remote Services" %}</a></li>
@@ -47,79 +48,79 @@
</ul>
</li>
<li id="nav_maps">
{% if user.is_authenticated %}
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Maps" %}<i class="fa fa-angle-down fa-lg"></i></a>
<ul class="dropdown-menu">
<li><a href="{% url "maps_browse" %}">{% trans "Explore Maps" %}</a></li>
<li> <a href="{% url "new_map" %}">{% trans "Create Map" %}</a></li>
</ul>
{% else %}
<a href="{% url "maps_browse" %}">{% trans "Maps" %}</a>
{% endif %}
</li>
<li>
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "About" %}<i class="fa fa-angle-down fa-lg"></i></a>
<ul class="dropdown-menu">
<li><a href="{% url "profile_browse" %}">{% trans "People" %}</a></li>
<li><a href="{% url "group_list" %}">{% trans "Groups" %}</a></li>
{% if perms.announcements.can_manage %}
<li><a href="{% url "announcements_list" %}">{% trans "Announcements" %}</a></li>
{% endif %}
{% if user.is_superuser %}
<li role="separator" class="divider"></li>
<li><a href="/admin/people/profile/add/">{% trans "Add User" %}</a></li>
<li><a href="{% url "account_invite_user" %}">{% trans "Invite User" %}</a></li>
<li><a href="{% url "group_create" %}">{% trans "Create Group" %}</a></li>
{% endif %}
</ul>
</li>
{% block extra_tab %}
{% endblock %}
{% endblock %}
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<div class="search">
<form id="search" action="{% url "search" %}" >
<span class="fa fa-search"></span>
{% if HAYSTACK_SEARCH %}
<input id="search_input" type="text" placeholder='{% trans "Search" %}' name="q">
{% else %}
<input id="search_input" type="text" placeholder='{% trans "Search" %}' name="title__icontains">
{% endif %}
</form>
</div>
</li>
{% if not user.is_authenticated and REGISTRATION_OPEN %}
<li><a href="{% url "account_signup" %}">{% trans "Register" %}</a></li>
{% endif %}
{% if user.is_authenticated %}
<li><a href="#" class="dropdown-toggle avatar" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% avatar user 42 %} {{ user.username }}<i class="fa fa-angle-down fa-lg"></i></a>
<ul class="dropdown-menu">
<li><a href="{{ user.get_absolute_url }}">{% trans "Profile" %}</a></li>
<li><a href="{% url "recent-activity" %}">{% trans "Recent Activity" %}</a></li>
<li><a href="{% url "messages_inbox" %}">{% trans "Inbox" %}</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url "admin:index" %}">Admin</a></li>
<li><a href="{{ GEOSERVER_BASE_URL }}">GeoServer</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url "help" %}">{% trans "Help" %}</li>
<li role="separator" class="divider"></li>
<li><a href="/account/logout/">Logout</a></li>
</ul>
</li>
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Maps" %}<i class="fa fa-angle-down fa-lg"></i></a>
<ul class="dropdown-menu">
<li><a href="{% url "maps_browse" %}">{% trans "Explore Maps" %}</a></li>
<li> <a href="{% url "new_map" %}">{% trans "Create Map" %}</a></li>
</ul>
{% else %}
<li><a href="#" data-toggle="modal" data-target="#SigninModal" role="button" >{% trans "Sign in" %} </a></li>
<a href="{% url "maps_browse" %}">{% trans "Maps" %}</a>
{% endif %}
</ul>
</div> <!--/.nav-collapse -->
</div>
</nav>
{% endblock header %}
<!-- End of Navbar -->
</li>
<li>
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "About" %}<i class="fa fa-angle-down fa-lg"></i></a>
<ul class="dropdown-menu">
<li><a href="{% url "profile_browse" %}">{% trans "People" %}</a></li>
<li><a href="{% url "group_list" %}">{% trans "Groups" %}</a></li>
{% if perms.announcements.can_manage %}
<li><a href="{% url "announcements_list" %}">{% trans "Announcements" %}</a></li>
{% endif %}
{% if user.is_superuser %}
<li role="separator" class="divider"></li>
<li><a href="/admin/people/profile/add/">{% trans "Add User" %}</a></li>
<li><a href="{% url "account_invite_user" %}">{% trans "Invite User" %}</a></li>
<li><a href="{% url "group_create" %}">{% trans "Create Group" %}</a></li>
{% endif %}
</ul>
</li>
{% block extra_tab %}
{% endblock %}
{% endblock %}
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<div class="search">
<form id="search" action="{% url "search" %}" >
<span class="fa fa-search"></span>
{% if HAYSTACK_SEARCH %}
<input id="search_input" type="text" placeholder='{% trans "Search" %}' name="q">
{% else %}
<input id="search_input" type="text" placeholder='{% trans "Search" %}' name="title__icontains">
{% endif %}
</form>
</div>
</li>
{% if not user.is_authenticated and REGISTRATION_OPEN %}
<li><a href="{% url "account_signup" %}">{% trans "Register" %}</a></li>
{% endif %}
{% if user.is_authenticated %}
<li><a href="#" class="dropdown-toggle avatar" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% avatar user 42 %} {{ user.username }}<i class="fa fa-angle-down fa-lg"></i></a>
<ul class="dropdown-menu">
<li><a href="{{ user.get_absolute_url }}">{% trans "Profile" %}</a></li>
<li><a href="{% url "recent-activity" %}">{% trans "Recent Activity" %}</a></li>
<li><a href="{% url "messages_inbox" %}">{% trans "Inbox" %}</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url "admin:index" %}">Admin</a></li>
<li><a href="{{ GEOSERVER_BASE_URL }}">GeoServer</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url "help" %}">{% trans "Help" %}</li>
<li role="separator" class="divider"></li>
<li><a href="/account/logout/">Logout</a></li>
</ul>
</li>
{% else %}
<li><a href="#" data-toggle="modal" data-target="#SigninModal" role="button" >{% trans "Sign in" %} </a></li>
{% endif %}
</ul>
</div> <!--/.nav-collapse -->
</div>
</nav>
{% endblock header %}
<!-- End of Navbar -->
{% block middle %}
{% block middle %}
<header>
<div class="container">
<h3>{% block page-title %}{% endblock %}</h3>
@@ -132,12 +133,12 @@
{% include "_messages.html" %}
{% block body_outer %}
<div class="row">
<div class="col-md-8">
{% block body %}{% endblock %}
</div>
<div class="col-md-4">
{% block sidebar %}{% endblock %}
</div>
<div class="col-md-12">
{% block body %}{% endblock %}
</div>
<div class="col-md-4">
{% block sidebar %}{% endblock %}
</div>
</div>
{% endblock %}
</div>
+4 -1
View File
@@ -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
View File
Whitespace-only changes.
+3
View File
@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.
View File
Whitespace-only changes.
+3
View File
@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.
@@ -0,0 +1,41 @@
<h1>Field choice</h1>
<p>Choose category field</p>
<form name="f">
<select name="category" onchange="update_link();update_add();">
{% for fieldname in fieldnames %}
<option value="{{ fieldname }}">{{ fieldname }}</option>
{% endfor %}
</select>
<p>Choose quantity field</p>
<select name="quantity" onchange="update_link();">
{% for fieldname in num_fieldnames %}
<option value="{{ fieldname }}">{{ fieldname }}</option>
{% endfor %}
</select>
<br>
<br>
<a href="/charts" id="link">Build Chart</a>
<a href="/charts" id="add_chart">Add Chart</a>
</form>
<script>
function update_link()
{
var url = '/chart/pie_chart_v2?lyrname={{ lyrname }}';
url = url + '&category=' + document.f.category.value;
url = url + '&quantity=' + document.f.quantity.value;
document.getElementById('link').href = url;
return;
}
update_link();
function update_add()
{
var url = '/chart/add?lyrname={{ lyrname }}';
url = url + '&category=' + document.f.category.value;
url = url + '&quantity=' + document.f.quantity.value;
document.getElementById('add_chart').href = url;
return;
}
update_add();
</script>
+20
View File
@@ -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)
+8
View File
@@ -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)
]
+56
View File
@@ -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'])
+80
View File
@@ -0,0 +1,80 @@
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
from utils import get_featno
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']
lyr = Layer.objects.get(typename=typename)
featno = get_featno(lyr.id)
if featno > settings.MAX_CSV_RECORDS:
response = HttpResponse('{0} has too many records; csv download'
' is not available for very large '
'datasets'.format(lyr.title))
response.status_code = 413
response.reason_phrase = 'REQUEST ENTITY TOO LARGE'
return response
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)
})
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