Compare commits

...

4 Commits

13 changed files with 340 additions and 294 deletions

View File

@ -6,6 +6,8 @@ import json
import requests
import datetime
import flask
import numpy
import pandas
import poobrains
from flask import redirect
@ -183,13 +185,15 @@ class StockWeekly(poobrains.svg.plot.Dataset):
class ConstrainedRandom(poobrains.svg.plot.Dataset):
def fill(self, magnitude=2, length=16):
#app.debugger.set_trace()
magnitude, length = int(magnitude), int(length)
ranges = []
for i in range(0, magnitude):
ranges.append(sorted((random.random(), random.random())))
#rawdata = pandas.Series(name=self.__class__.__name__, index=[0])
rawdata = []
for i in range(0, length):
y = random.random()
@ -204,7 +208,31 @@ class ConstrainedRandom(poobrains.svg.plot.Dataset):
y *= math.sin(2 * math.pi * float(i) / length)
self.add_datapoint(i, y) #, error_lower=y - r[0], error_upper=r[1] - y)
#self.add_datapoint(i, y) #, error_lower=y - r[0], error_upper=r[1] - y)
#rawdata.iat[i] = y
rawdata.append(y)
#self.data.iat[0] = rawdata
self.data = pandas.DataFrame(data=rawdata)
class Sine(poobrains.svg.plot.Dataset):
def fill(self, length=10):
import pudb; pudb.set_trace()
inc = (2 * math.pi) / (length - 1)
#inc = 1.0 / (length - 1)
rawdata = {}
x = 0
for _ in range(0, length):
rawdata[x] = math.sin(x)
x += inc
self.data = pandas.DataFrame(columns=['sine'], index=rawdata.keys(), data=rawdata.values())
class RandomMap(poobrains.svg.geo.GeoData):

View File

@ -131,6 +131,12 @@ class DummySession(werkzeug.datastructures.CallbackDict, flask.sessions.SessionM
modified = False
accessed = False
new_session_funcs = set()
def new_session(f):
new_session_funcs.add(f)
return f
class Session(werkzeug.datastructures.CallbackDict, flask.sessions.SessionMixin):
@ -145,6 +151,10 @@ class Session(werkzeug.datastructures.CallbackDict, flask.sessions.SessionMixin)
self.key = nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)
self.crypto = nacl.secret.SecretBox(self.key)
# call all @new_session decorated functions with this session object
for func in new_session_funcs:
func(self)
else:
if not key:
@ -156,15 +166,21 @@ class Session(werkzeug.datastructures.CallbackDict, flask.sessions.SessionMixin)
self.key = key
self.crypto = nacl.secret.SecretBox(self.key)
plaintext = self.crypto.decrypt(self.sessiondata.data)
plaintext = self.crypto.decrypt(bytes(self.sessiondata.data))
for k, v in pickle.loads(plaintext).items():
self[k] = v
self.sid = sid
except storage.SessionData.DoesNotExist:
except storage.SessionData.DoesNotExist: # throw away unknown sids and create new sessiondata
self.sessiondata = storage.SessionData()
self.sid = None
self.key = nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)
self.crypto = nacl.secret.SecretBox(self.key)
# call all @new_session decorated functions with this session object
for func in new_session_funcs:
func(self)
def on_update(self):
self.modified = True

View File

@ -35,7 +35,6 @@ class BytesParamType(poobrains.form.types.ParamType):
if type(e) is ValueError:
raise
app.debugger.set_trace()
BYTES = BytesParamType()

View File

@ -11,12 +11,9 @@ from . import geo
SVG = base.SVG
Dataset = plot.Dataset
Datapoint = plot.Datapoint
StoredDataset = plot.StoredDataset
StoredDatapoint = plot.StoredDatapoint
#GeoData = geo.GeoData
GeoData = geo.GeoData
StoredGeoData = geo.StoredGeoData
@app.before_first_request

View File

@ -68,8 +68,6 @@ def merge_bboxes(a, b):
def find_bbox(data):
app.debugger.set_trace()
bbox = [180, -80, -180, 80]
if isinstance(data, geojson.FeatureCollection):
@ -448,7 +446,7 @@ class StoredGeoDataForm(poobrains.auth.AutoForm):
self.fields['data'].value = self.instance.data
else:
if self.fields['data'].value.mimetype in ('application/json', 'application/geo+json'):
fd = self.fields['data'].value.stream

View File

@ -3,8 +3,9 @@ import math
import time
import datetime
import json
import pandas
from poobrains import Markup, app, request, abort, redirect, g, session, locked_cached_property
from poobrains import Markup, app, request, abort, redirect, g, session, locked_cached_property, new_session
import poobrains.helpers
import poobrains.errors
@ -58,6 +59,7 @@ class Dataset(poobrains.auth.Protected):
title = None # to override Renderable's @property
description = poobrains.md.MarkdownString()
def __init__(self, name=None, title=None, description=None, label_x=None, label_y=None):
self._name = name
@ -66,13 +68,16 @@ class Dataset(poobrains.auth.Protected):
self.label_x = label_x or 'X'
self.label_y = label_y or 'Y'
self.datapoints = poobrains.helpers.TypedList(Datapoint)
self.data = pandas.DataFrame()
def __iadd__(self, value): # +=
for datapoint in value.datapoints:
self.datapoints.append(datapoint)
self.data += value # just let pandas do its thing
def __len__(self):
return len(self.data)
@property
@ -85,88 +90,31 @@ class Dataset(poobrains.auth.Protected):
self._name = value
@property
def authorized_datapoints(self):
return self.datapoints
@property
def ref_id(self):
return "dataset-%s" % self.name
def datapoint_id(self, datapoint):
return "dataset-%s-%s" % (self.name, datapoint.x)
def datapoint_id(self, x):
return "dataset-%s-%s" % (self.name, x)
def render(self, mode=None):
if mode == 'json':
return self.data.to_json()
return super(Dataset, self).render(mode=mode)
def append(self, datapoint):
self.datapoints.append(datapoint)
def add_datapoint(self, x, y, error_lower=None, error_upper=None):
"""
convenience function to append a datapoint.
"""
datapoint = Datapoint(x, y, error_lower=error_lower, error_upper=error_upper)
self.append(datapoint)
def plot(self):
return Plot(datasets=[self]).render('raw')
def fill(self, *args):
raise NotImplementedError("%s.fill not implemented" % type(self).__name__)
def to_dict(self):
d = {
'name': self.name,
'title': self.title,
'description': self.description,
'label_x': self.label_x,
'label_y': self.label_y
}
datapoints = []
for datapoint in self.authorized_datapoints:
datapoints.append({
'x': datapoint.x,
'y': datapoint.y,
'error_lower': datapoint.error_lower,
'error_upper': datapoint.error_upper
})
d['datapoints'] = datapoints
return d
@classmethod
def from_dict(cls, d):
kwargs = {
'name': d['name'],
'title': d['title'],
'description': d['description'],
'label_x': d['label_x'],
'label_y': d['label_y']
}
ds = cls(**kwargs)
for datapoint in d['datapoints']:
ds.add_datapoint(datapoint['x'], datapoint['y'], error_lower=datapoint['error_lower'], error_upper=datapoint['error_upper'])
return ds
def save(self, name=None, owner=None):
"""
Convert this Dataset and all its Datapoints into StoredDataset and StoredDatapoints and save them all.
Convert this Dataset into a StoredDataset and save it.
"""
ds = StoredDataset()
@ -180,27 +128,19 @@ class Dataset(poobrains.auth.Protected):
ds.label_x = self.label_x
ds.label_y = self.label_y
ds.description = self.description
ds.data = self.data
ds.save(force_insert=True)
for datapoint in self.datapoints:
ds.add_datapoint( # StoredDatapoint.add_datapoint implicitly saves the added datapoint
datapoint.x,
datapoint.y,
error_lower=datapoint.error_lower,
error_upper=datapoint.error_upper
)
return ds
class Datapoint(poobrains.auth.Protected):
class StoredDatasetForm(poobrains.auth.AutoForm):
def __init__(self, x, y, error_lower=None, error_upper=None):
data = poobrains.form.fields.File()
self.x = x
self.y = y
self.error_lower = error_lower
self.error_upper = error_upper
def process(self, submit, exceptions=False):
self.instance.data = pandas.from_json(self.fields['data'].value)
class StoredDataset(Dataset, poobrains.commenting.Commentable):
@ -209,94 +149,37 @@ class StoredDataset(Dataset, poobrains.commenting.Commentable):
description = poobrains.md.MarkdownField(null=True)
label_x = poobrains.storage.fields.CharField(verbose_name="Label for the x-axis")
label_y = poobrains.storage.fields.CharField(verbose_name="Label for the y-axis")
data = poobrains.storage.fields.TextField()
form_add = StoredDatasetForm
def __init__(self, *args, **kwargs):
self._data = None
return super(Dataset, self).__init__(*args, **kwargs)
def __getattribute__(self, name):
if name == 'data':
if self._data is None:
#self._data = pandas.DataFrame.from_dict(json.loads(self.data))
self._data = pandas.DataFrame.from_dict(super(StoredDataset, self).__getattribute__('data'))
return self._data
return super(StoredDataset, self).__getattribute__(name)
@property
def ref_id(self):
return "dataset-%s" % self.name
@locked_cached_property
def authorized_datapoints(self):
return StoredDatapoint.list('read', g.user).where(StoredDatapoint.dataset == self)
def datapoint_id(self, datapoint):
return "dataset-%s-%s" % (self.name, datapoint.x)
def append(self, datapoint):
datapoint.dataset = self
if not datapoint.owner_id:
datapoint.owner = self.owner
return datapoint.save(force_insert=True)
def add_datapoint(self, x, y, error_lower=None, error_upper=None):
"""
convenience function to append a datapoint.
"""
datapoint = StoredDatapoint()
datapoint.x = x
datapoint.y = y
datapoint.error_lower=error_lower
datapoint.error_upper=error_upper
self.append(datapoint)
def save(self, **kwargs):
return super(Dataset, self).save(**kwargs)
class StoredDatapointFieldset(poobrains.form.Fieldset):
def __init__(self, datapoint, **kwargs):
super(StoredDatapointFieldset, self).__init__(**kwargs)
self.datapoint = datapoint
self.x = poobrains.form.fields.Text(type=poobrains.form.types.FLOAT, value=self.datapoint.x, placeholder=StoredDatapoint.x.verbose_name, help_text=StoredDatapoint.x.help_text)
self.y = poobrains.form.fields.Text(type=poobrains.form.types.FLOAT, value=self.datapoint.y, placeholder=StoredDatapoint.y.verbose_name, help_text=StoredDatapoint.y.help_text)
self.error_lower = poobrains.form.fields.Text(type=poobrains.form.types.FLOAT, value=self.datapoint.error_lower, placeholder=StoredDatapoint.error_lower.verbose_name, help_text=StoredDatapoint.error_lower.help_text)
self.error_upper = poobrains.form.fields.Text(type=poobrains.form.types.FLOAT, value=self.datapoint.error_upper, placeholder=StoredDatapoint.error_upper.verbose_name, help_text=StoredDatapoint.error_upper.help_text)
def process(self, submit, dataset):
if self.datapoint in dataset.datapoints:
pass # florp
else:
self.datapoint.dataset = dataset
self.datapoint.x = self.fields['x'].value
self.datapoint.y = self.fields['y'].value
self.datapoint.error_lower = self.fields['error_lower'].value
self.datapoint.error_upper = self.fields['error_upper'].value
self.datapoint.save(force_insert=True)
class StoredDatapoint(poobrains.auth.Owned, Datapoint):
class Meta:
order_by = ['dataset', 'x']
primary_key = poobrains.storage.CompositeKey('dataset', 'x')
related_use_form = True
dataset = poobrains.storage.fields.ForeignKeyField(StoredDataset, backref='datapoints')
x = poobrains.storage.fields.DoubleField()
y = poobrains.storage.fields.DoubleField()
error_lower = poobrains.storage.fields.FloatField(help_text="Lower margin of error", null=True)
error_upper = poobrains.storage.fields.FloatField(help_text="Upper margin of error", null=True)
dataobj = self.data
self.data = dataobj.to_json()
r = super(Dataset, self).save(**kwargs)
self.data = dataobj
return r
@app.expose('/svg/plot')
@ -313,13 +196,16 @@ class Plot(base.SVG):
datasets = None
length = None
# TODO: deprecate all this by just statically computed bounds?
min_x = None
max_x = None
min_y = None
max_y = None
span_x = None
span_y = None
bounds = None
class Meta:
@ -366,67 +252,90 @@ class Plot(base.SVG):
#for datapoint in Datapoint.list('read', g.user).where(Datapoint.dataset << self.datasets):
for dataset in self.datasets:
l = len(dataset.authorized_datapoints)
l = len(dataset)
if l > self.length:
self.length = l
for datapoint in dataset.authorized_datapoints:
datapoint_count += 1
y_lower = datapoint.y
if datapoint.error_lower:
y_lower -= datapoint.error_lower
y_upper = datapoint.y
if datapoint.error_upper:
y_upper += datapoint.error_upper
if self.min_x is None or datapoint.x < self.min_x:
self.min_x = datapoint.x
if self.max_x is None or datapoint.x > self.max_x:
self.max_x = datapoint.x
if self.min_y is None or y_lower < self.min_y:
self.min_y = y_lower
if self.max_y is None or y_upper > self.max_y:
self.max_y = y_upper
if datapoint_count > 0:
self.span_x = self.max_x - self.min_x
self.span_y = self.max_y - self.min_y
else:
self.min_x = 0
self.max_x = 0
self.min_y = 0
self.max_y = 0
self.span_x = 0
self.span_y = 0
def is_dataframe(self, x):
return type(x) is pandas.DataFrame
def render(self, mode=None):
@property
def min_x(self):
if mode == 'json':
mins = []
for dataset in self.datasets:
if isinstance(dataset.data, pandas.DataFrame):
submins = []
for subset_index in dataset.data:
submins.append(min(dataset.data[subset_index].index))
data = {}
mins.append(min(submins))
for dataset in self.datasets:
data[dataset.name] = []
else:
mins.append(min(dataset.data.index))
for datapoint in dataset.authorized_datapoints:
data[dataset.name].append({
'x': datapoint.x,
'y': datapoint.y,
'error_lower': datapoint.error_lower,
'error_upper': datapoint.error_upper
})
return min(mins)
return Markup(json.dumps(data))
return super(Plot, self).render(mode=mode)
@property
def max_x(self):
maxes = []
for dataset in self.datasets:
if isinstance(dataset.data, pandas.DataFrame):
submaxes = []
for subset_index in dataset.data:
submaxes.append(max(dataset.data[subset_index].index))
maxes.append(max(submaxes))
else:
maxes.append(max(dataset.data.index))
return max(maxes)
@property
def min_y(self):
mins = []
for dataset in self.datasets:
if isinstance(dataset.data, pandas.DataFrame):
submins = []
for subset_index in dataset.data:
submins.append(min(dataset.data[subset_index]))
mins.append(min(submins))
else:
mins.append(min(dataset.data))
return min(mins)
@property
def max_y(self):
maxes = []
for dataset in self.datasets:
if isinstance(dataset.data, pandas.DataFrame):
submaxes = []
for subset_index in dataset.data:
submaxes.append(max(dataset.data[subset_index]))
maxes.append(max(submaxes))
else:
maxes.append(max(dataset))
return max(maxes)
def normalize_x(self, value):
@ -457,6 +366,88 @@ class Plot(base.SVG):
return u' / '.join([dataset.label_y for dataset in self.datasets])
def dataset_griddable(self, dataset):
valid_dtypes = [
int,
float
]
for dtype in valid_dtypes:
x = False
y = False
if dataset.data.index.dtype in valid_dtypes:
x = True # x axis is numerical, can be gridded
if dtype in dataset.data.dtypes.values:
y = True # y axais is numeric, can be gridded
if x and y:
return True
return False
@property
def griddable_datasets(self):
datasets = []
for ds in self.datasets:
if self.dataset_griddable(ds):
datasets.append(ds)
return datasets
@property
def has_grid(self):
for ds in self.datasets:
if self.dataset_griddable(ds):
return True
return False
@property
def span_x(self):
absolute_minimum = None
absolute_maximum = None
for ds in self.griddable_datasets:
local_minimum = min(ds.data.index)
local_maximum = max(ds.data.index)
if absolute_minimum is None or local_minimum < absolute_minimum:
absolute_minimum = local_minimum
if absolute_maximum is None or local_maximum > absolute_maximum:
absolute_maximum = local_maximum
return absolute_maximum - absolute_minimum
@property
def span_y(self):
absolute_minimum = None
absolute_maximum = None
for ds in self.griddable_datasets:
for column in ds.data.columns:
for value in ds.data[column].values:
if absolute_minimum is None or value < absolute_minimum:
absolute_minimum = value
if absolute_maximum is None or value > absolute_maximum:
absolute_maximum = value
if not absolute_minimum is None:
return absolute_maximum - absolute_minimum
@property
def grid_x(self):
@ -479,7 +470,7 @@ class Plot(base.SVG):
@property
def grid_y(self):
#app.debugger.set_trace()
if self.span_y == 0:
return [self.min_y]
@ -511,6 +502,11 @@ class BarChart(Plot):
return (self.plot_width - self.padding) / self.length - 1
@new_session
def editor_session(session):
session['editor-sessions'] = {}
def new_editor_handle():
handle = poobrains.helpers.random_string_light()
@ -523,7 +519,6 @@ def new_editor_handle():
def dataset_choices():
dynamic = []
for name, cls in dynamic_datasets().items():
@ -549,10 +544,9 @@ class EditorLoadFieldset(poobrains.form.Fieldset):
def process(self, submit, instance):
if submit == '%s.%s' % (self.name, 'load_dataset'):
instance.data = load_dataset(self.fields['dataset'].value)
session[instance.handle_string] = instance.data.to_dict()
instance.dataset = load_dataset(self.fields['dataset'].value)
session['editor-sessions'][instance.handle_string] = instance.dataset.data.to_dict()
@app.expose('/svg/plot/editor/', force_secure=True)
@ -565,8 +559,7 @@ class DataEditor(poobrains.auth.ProtectedForm):
super(DataEditor, self).__init__(handle=handle, mode=mode, **kwargs)
self.data = None
self.dataset = None
#self.fields['load_fieldset'] = EditorLoadFieldset(prefix=self.name, name='load_fieldset')
self.add_field(EditorLoadFieldset())
@ -578,22 +571,21 @@ class DataEditor(poobrains.auth.ProtectedForm):
return redirect(type(self)(handle=new_editor_handle()).url('full'))
if self.handle_string in session:
self.data = Dataset.from_dict(session[self.handle_string])
#self.data = session[self.handle_string]
#self.dataset = session[self.handle_string]
self.dataset = Dataset()
self.dataset.data = pandas.DataFrame.from_dict(session['editor-sessions'][self.handle_string])
return poobrains.helpers.ThemedPassthrough(super(DataEditor, self).view(mode=mode, **kwargs))
def process(self, submit):
app.debugger.set_trace()
if submit.startswith('EditorLoadFieldset.'):
self.fields['EditorLoadFieldset'].process(submit, self)
#session[self.handle_string] = self.data
elif submit == 'save_dataset':
stored_ds = self.data.save()
stored_ds = self.dataset.save()
return redirect(stored_ds.url('edit'))
return self

View File

@ -1,8 +1,10 @@
{% extends 'form/form.jinja' %}
{% block pre %}
{% if content.data %}
{{ content.data.plot() }}
{% if content.dataset %}
<div class="dataeditor-plot">
{{ content.dataset.plot() }}
</div>
{% endif %}
{% endblock %}

View File

@ -3,6 +3,7 @@
<form
id="{{ content.ref_id }}"
class="content-type-{{ content.__class__.__name__.lower() }} mode-{{ mode }} {{ content.css_class }}"
method="{{ content.method }}"
{% if content.action %}action="{{ content.action }}"{% endif %}
enctype="multipart/form-data" >

View File

@ -1317,5 +1317,10 @@ object svg.plot {
}
.dataeditor-plot {
background: $color_background_dark;
}
.content-type-dataeditor {}
@import 'form';
@import 'custom';

View File

@ -0,0 +1,2 @@
{# this just exists, because otherwise commentable.jinja is used #}
{% extends "geodata-raw.jinja" %}

View File

@ -0,0 +1,5 @@
{% extends "commentable.jinja" %}
{% block content %}
<object class="svg-wrapper" data="{{ content.map_url }}"></object>
{% endblock %}

View File

@ -11,7 +11,7 @@
>
<!-- a json representation of all visualized data, not used since there's a strict nojs policy, but included for your convenience -->
{{ content.render('json') }}
{# content.render('json') #}
<style>
{{ content.style }}
@ -23,42 +23,45 @@
<text class="axis-label axis-y" transform="rotate(-90 0 {{ content.plot_height + content.padding }}) translate(5 15)" x="0" y="{{ content.plot_height + content.padding }}">{{ content.label_y }}</text>
<svg class="plot-inner" x="{{ content.padding }}" y="{{ content.padding }}" width="{{ content.inner_width }}" height="{{ content.inner_height }}" viewBox="0 0 {{ content.inner_width }} {{ content.inner_height }}" preserveAspectRatio="xMinYMin meet">
<g class="grid">
{% block grid %}
{% for i in range(0, content.grid_x|length) %}
{% set x = content.grid_x[i] %}
{% if content.has_grid %}
<g class="grid">
{% block grid %}
{% for i in range(0, content.grid_x|length) %}
{% if content.grid_x|length >= 4 %}
{% set highlighted = i % (content.grid_x|length / 4.0)|int == 0 %} {# whether this line should be highlighted and have a value shown next to it #}
{% else %}
{% set highlighted = True %}
{% endif %}
{% set x = content.grid_x[i] %}
<line class="grid-x{% if highlighted %} highlighted{% endif %}" x1="{{ content.normalize_x(x) }}" y1="0%" x2="{{ content.normalize_x(x) }}" y2="{{ content.plot_height }}" />
{% if highlighted %}
<text class="grid-label axis-x" x="{{ content.normalize_x(x) }}" y="{{ content.plot_height }}" dominant-baseline="hanging">{{ "%.3f"|format(x) }}</text>
{% endif %}
{% endfor %}
{% if content.grid_x|length >= 4 %}
{% set highlighted = i % (content.grid_x|length / 4.0)|int == 0 %} {# whether this line should be highlighted and have a value shown next to it #}
{% else %}
{% set highlighted = True %}
{% endif %}
{% for i in range(0, content.grid_y|length) %}
{% set y = content.grid_y[i] %}
{% if content.grid_y|length >= 4 %}
{% set highlighted = i % (content.grid_y|length / 4)|int == 0 %} {# whether this line should be highlighted and have a value shown next to it #}
{% else %}
{% set highlighted = True %}
{% endif %}
<line class="grid-x{% if highlighted %} highlighted{% endif %}" x1="{{ content.normalize_x(x) }}" y1="0%" x2="{{ content.normalize_x(x) }}" y2="{{ content.plot_height }}" />
{% if highlighted %}
<text class="grid-label axis-x" x="{{ content.normalize_x(x) }}" y="{{ content.plot_height }}" dominant-baseline="hanging">{{ "%.3f"|format(x) }}</text>
{% endif %}
{% endfor %}
{% for i in range(0, content.grid_y|length) %}
{% set y = content.grid_y[i] %}
{% if content.grid_y|length >= 4 %}
{% set highlighted = i % (content.grid_y|length / 4)|int == 0 %} {# whether this line should be highlighted and have a value shown next to it #}
{% else %}
{% set highlighted = True %}
{% endif %}
<line class="grid-y{% if highlighted %} highlighted{% endif %}" x1="0%" y1="{{ content.normalize_y(y) }}" x2="100%" y2="{{ content.normalize_y(y) }}" />
{% if highlighted %}
<text class="grid-label axis-y" transform="rotate(-90 0 {{ content.normalize_y(y) }}) translate(5 -15)" x="0" y="{{ content.normalize_y(y) }}" dominant-baseline="hanging">{{ "%.3f"|format(y) }}</text>
{% endif %}
{% endfor %}
{% endblock %}
</g>
<line class="grid-y{% if highlighted %} highlighted{% endif %}" x1="0%" y1="{{ content.normalize_y(y) }}" x2="100%" y2="{{ content.normalize_y(y) }}" />
{% if highlighted %}
<text class="grid-label axis-y" transform="rotate(-90 0 {{ content.normalize_y(y) }}) translate(5 -15)" x="0" y="{{ content.normalize_y(y) }}" dominant-baseline="hanging">{{ "%.3f"|format(y) }}</text>
{% endif %}
{% endfor %}
{% endblock %}
</g>
{% endif %}
<g class="datasets">
{% block datasets scoped %}
@ -70,40 +73,39 @@
{% block dataset scoped %}
{% for datapoint in dataset.authorized_datapoints %}
{% if content.is_dataframe(dataset.data) %}
{% for series_index in dataset.data %}
{% set series = dataset.data[series_index] %}
{% for x, y in series.iteritems() %}
<a href="#{{ dataset.datapoint_id(datapoint) }}">
<g id="{{ dataset.datapoint_id(datapoint) }}" class="datapoint">
{% block datapoint scoped %}
<a href="#{{ dataset.datapoint_id(x) }}">
<g id="{{ dataset.datapoint_id(x) }}" class="datapoint">
{% block datapoint scoped %}
<g class="error">
<use href="#marker" class="marker" x="{{ content.normalize_x(x) }}" y="{{ content.normalize_y(y) }}" />
{% if datapoint.error_upper %}
<g class="upper">
<line x1="{{ content.normalize_x(datapoint.x) }}" y1="{{ content.normalize_y(datapoint.y) }}" x2="{{ content.normalize_x(datapoint.x) }}" y2="{{ content.normalize_y(datapoint.y + datapoint.error_upper) }}" />
<line x1="{{ content.normalize_x(datapoint.x) - 5 }}" y1="{{ content.normalize_y(datapoint.y + datapoint.error_upper) }}" x2="{{ content.normalize_x(datapoint.x) + 5 }}" y2="{{ content.normalize_y(datapoint.y + datapoint.error_upper) }}" />
<text class="datapoint-value" x="{{ content.normalize_x(x) + 5 }}" y="{{ content.normalize_y(y) - 20 }}">{{ "%.5f"|format(y) }}</text>
{% endblock %}
</g>
{% endif %}
</a>
{% if datapoint.error_lower %}
<g class="lower">
<line x1="{{ content.normalize_x(datapoint.x) }}" y1="{{ content.normalize_y(datapoint.y) }}" x2="{{ content.normalize_x(datapoint.x) }}" y2="{{ content.normalize_y(datapoint.y - datapoint.error_lower) }}" />
<line x1="{{ content.normalize_x(datapoint.x) - 5 }}" y1="{{ content.normalize_y(datapoint.y - datapoint.error_lower) }}" x2="{{ content.normalize_x(datapoint.x) + 5 }}" y2="{{ content.normalize_y(datapoint.y - datapoint.error_lower) }}" />
</g>
{% endif %}
</g>
<use href="#marker" class="marker" x="{{ content.normalize_x(datapoint.x) }}" y="{{ content.normalize_y(datapoint.y) }}" />
<text class="datapoint-value" x="{{ content.normalize_x(datapoint.x) + 5 }}" y="{{ content.normalize_y(datapoint.y) - 20 }}">{{ "%.5f"|format(datapoint.y) }}</text>
{% endblock %}
</g>
</a>
{% endfor %}
{% endfor %}
{% endfor %}
{% else %}
{% for datapoint in dataset %}
{% endfor %}
{% endif %}
<a class="dataset-link" href="#{{ dataset.ref_id }}">
<text class="dataset-name" x="{{ content.inner_width }}" y="{{ 10 + (idx * 20) }}" text-anchor="end">{{ dataset.title }}</text>

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -1,9 +1,8 @@
{% extends 'renderable.jinja' %}
{% block content %}
{# <object class="svg-wrapper" data="{{ content.url('raw') }}" ></object> #}
{{ content.render('raw') }}
<object class="svg-wrapper" data="{{ content.url('raw') }}" ></object>
<a href="{{ content.url('raw') }}" target="_blank">Save</a>
{% endblock %}