Mostly working

This commit is contained in:
Maxime Beauchemin 2015-09-29 21:41:49 -07:00
parent f3f807d042
commit 74e5a3e868
8 changed files with 252 additions and 192 deletions

View File

@ -61,6 +61,8 @@ def load_examples(sample):
Column("num", Integer), Column("num", Integer),
Column("ds", String(20)), Column("ds", String(20)),
Column("gender", String(10)), Column("gender", String(10)),
Column("sum_boys", Integer),
Column("sum_girls", Integer),
) )
try: try:
BirthNames.drop(db.engine) BirthNames.drop(db.engine)
@ -82,7 +84,10 @@ def load_examples(sample):
state=state, state=state,
year=year, year=year,
ds=ds, ds=ds,
name=name, num=num, gender=gender) name=name, num=num, gender=gender,
sum_boys=num if gender == 'boy' else 0,
sum_girls=num if gender == 'girl' else 0,
)
if i % 5000 == 0: if i % 5000 == 0:
print("{} loaded out of 82527 rows".format(i)) print("{} loaded out of 82527 rows".format(i))
session.commit() session.commit()
@ -109,8 +114,11 @@ def load_examples(sample):
obj.main_dttm_col = 'ds' obj.main_dttm_col = 'ds'
obj.default_endpoint = "/panoramix/datasource/table/1/?viz_type=table&granularity=one+day&since=100+years&until=now&row_limit=10&where=&flt_col_0=ds&flt_op_0=in&flt_eq_0=&flt_col_1=ds&flt_op_1=in&flt_eq_1=&slice_name=TEST&datasource_name=birth_names&datasource_id=1&datasource_type=table" obj.default_endpoint = "/panoramix/datasource/table/1/?viz_type=table&granularity=one+day&since=100+years&until=now&row_limit=10&where=&flt_col_0=ds&flt_op_0=in&flt_eq_0=&flt_col_1=ds&flt_op_1=in&flt_eq_1=&slice_name=TEST&datasource_name=birth_names&datasource_id=1&datasource_type=table"
obj.database = dbobj obj.database = dbobj
obj.columns = [models.TableColumn( obj.columns = [
column_name="num", sum=True, type="INTEGER")] models.TableColumn(column_name="num", sum=True, type="INTEGER"),
models.TableColumn(column_name="sum_boys", sum=True, type="INTEGER"),
models.TableColumn(column_name="sum_girls", sum=True, type="INTEGER"),
]
models.Table models.Table
session.add(obj) session.add(obj)
session.commit() session.commit()
@ -200,7 +208,7 @@ def load_examples(sample):
session.add(slc) session.add(slc)
slices.append(slc) slices.append(slc)
slice_name = "States" slice_name = "Gender by State"
slc = session.query(Slice).filter_by(slice_name=slice_name).first() slc = session.query(Slice).filter_by(slice_name=slice_name).first()
if not slc: if not slc:
slc = Slice( slc = Slice(
@ -210,6 +218,7 @@ def load_examples(sample):
table=tbl, table=tbl,
params=get_slice_json( params=get_slice_json(
slice_name, flt_eq_1="other", viz_type="dist_bar", slice_name, flt_eq_1="other", viz_type="dist_bar",
metrics=['sum__sum_girls', 'sum__sum_boys'],
groupby=['state'], flt_op_1='not in', flt_col_1='state')) groupby=['state'], flt_op_1='not in', flt_col_1='state'))
session.add(slc) session.add(slc)
slices.append(slc) slices.append(slc)

View File

@ -1,7 +1,9 @@
from wtforms import ( from wtforms import (
Field, Form, SelectMultipleField, SelectField, TextField, TextAreaField, Field, Form, SelectMultipleField, SelectField, TextField, TextAreaField,
BooleanField, IntegerField) BooleanField, IntegerField, HiddenField)
from copy import copy from copy import copy
from panoramix import app
config = app.config
class OmgWtForm(Form): class OmgWtForm(Form):
@ -27,157 +29,183 @@ class OmgWtForm(Form):
return "" return ""
def form_factory(viz): class FormFactory(object):
datasource = viz.datasource row_limits = [10, 50, 100, 500, 1000, 5000, 10000, 50000]
from panoramix.viz import viz_types
row_limits = [10, 50, 100, 500, 1000, 5000, 10000]
series_limits = [0, 5, 10, 25, 50, 100, 500] series_limits = [0, 5, 10, 25, 50, 100, 500]
group_by_choices = [(s, s) for s in datasource.groupby_column_names]
# Pool of all the fields that can be used in Panoramix def __init__(self, viz):
px_form_fields = { self.viz = viz
'viz_type': SelectField( from panoramix.viz import viz_types
'Viz', viz = self.viz
choices=[(k, v.verbose_name) for k, v in viz_types.items()], datasource = viz.datasource
description="The type of visualization to display"), group_by_choices = [(s, s) for s in datasource.groupby_column_names]
'metrics': SelectMultipleField( # Pool of all the fields that can be used in Panoramix
'Metrics', choices=datasource.metrics_combo, self.field_dict = {
description="One or many metrics to display"), 'viz_type': SelectField(
'metric': SelectField( 'Viz',
'Metric', choices=datasource.metrics_combo, default='table',
description="One or many metrics to display"), choices=[(k, v.verbose_name) for k, v in viz_types.items()],
'groupby': SelectMultipleField( description="The type of visualization to display"),
'Group by', 'metrics': SelectMultipleField(
choices=[(s, s) for s in datasource.groupby_column_names], 'Metrics', choices=datasource.metrics_combo,
description="One or many fields to group by"), default=[datasource.metrics_combo[0][0]],
'granularity': TextField( description="One or many metrics to display"),
'Time Granularity', default="one day", 'metric': SelectField(
description=( 'Metric', choices=datasource.metrics_combo,
"The time granularity for the visualization. Note that you " description="One or many metrics to display"),
"can type and use simple natural language as in '10 seconds', " 'groupby': SelectMultipleField(
"'1 day' or '56 weeks'")), 'Group by',
'since': TextField( choices=[(s, s) for s in datasource.groupby_column_names],
'Since', default="one day ago", description=( description="One or many fields to group by"),
"Timestamp from filter. This supports free form typing and " 'granularity': TextField(
"natural language as in '1 day ago', '28 days' or '3 years'")), 'Time Granularity', default="one day",
'until': TextField('Until', default="now"),
'row_limit':
SelectField(
'Row limit', choices=[(s, s) for s in row_limits]),
'limit':
SelectField(
'Series limit', choices=[(s, s) for s in series_limits],
description=( description=(
"Limits the number of time series that get displayed")), "The time granularity for the visualization. Note that you "
'rolling_type': SelectField( "can type and use simple natural language as in '10 seconds', "
'Rolling', "'1 day' or '56 weeks'")),
choices=[(s, s) for s in ['mean', 'sum', 'std']], 'since': TextField(
description=( 'Since', default="one day ago", description=(
"Defines a rolling window function to apply")), "Timestamp from filter. This supports free form typing and "
'rolling_periods': TextField('Periods', description=( "natural language as in '1 day ago', '28 days' or '3 years'")),
"Defines the size of the rolling window function, " 'until': TextField('Until', default="now"),
"relative to the 'granularity' field")), 'row_limit':
'series': SelectField( SelectField(
'Series', choices=group_by_choices, 'Row limit',
description=( default=config.get("ROW_LIMIT"),
"Defines the grouping of entities. " choices=[(s, s) for s in self.row_limits]),
"Each serie is shown as a specific color on the chart and " 'limit':
"has a legend toggle")), SelectField(
'entity': SelectField('Entity', choices=group_by_choices, 'Series limit', choices=[(s, s) for s in self.series_limits],
description="This define the element to be plotted on the chart"), default=50,
'x': SelectField( description=(
'X Axis', choices=datasource.metrics_combo, "Limits the number of time series that get displayed")),
description="Metric assigned to the [X] axis"), 'rolling_type': SelectField(
'y': SelectField('Y Axis', choices=datasource.metrics_combo, 'Rolling',
description="Metric assigned to the [Y] axis"), choices=[(s, s) for s in ['mean', 'sum', 'std']],
'size': SelectField('Bubble Size', choices=datasource.metrics_combo), description=(
'where': TextField('Custom WHERE clause'), "Defines a rolling window function to apply")),
'compare_lag': TextField('Comparison Period Lag', 'rolling_periods': TextField('Periods', description=(
description="Based on granularity, number of time periods to compare against"), "Defines the size of the rolling window function, "
'compare_suffix': TextField('Comparison suffix', "relative to the 'granularity' field")),
description="Suffix to apply after the percentage display"), 'series': SelectField(
'markup_type': SelectField( 'Series', choices=group_by_choices,
"Markup Type", description=(
choices=[(s, s) for s in ['markdown', 'html']], "Defines the grouping of entities. "
default="markdown", "Each serie is shown as a specific color on the chart and "
description="Pick your favorite markup language"), "has a legend toggle")),
'rotation': SelectField( 'entity': SelectField('Entity', choices=group_by_choices,
"Rotation", description="This define the element to be plotted on the chart"),
choices=[(s, s) for s in ['random', 'flat', 'square']], 'x': SelectField(
default="random", 'X Axis', choices=datasource.metrics_combo,
description="Rotation to apply to words in the cloud"), description="Metric assigned to the [X] axis"),
'code': TextAreaField("Code", description="Put your code here"), 'y': SelectField('Y Axis', choices=datasource.metrics_combo,
'size_from': TextField( description="Metric assigned to the [Y] axis"),
"Font Size From", 'size': SelectField('Bubble Size', choices=datasource.metrics_combo),
default="20", 'where': TextField('Custom WHERE clause', default=''),
description="Font size for the smallest value in the list"), 'compare_lag': TextField('Comparison Period Lag',
'size_to': TextField( description="Based on granularity, number of time periods to compare against"),
"Font Size To", 'compare_suffix': TextField('Comparison suffix',
default="150", description="Suffix to apply after the percentage display"),
description="Font size for the biggest value in the list"), 'markup_type': SelectField(
'show_brush': BooleanField( "Markup Type",
"Range Selector", default=True, choices=[(s, s) for s in ['markdown', 'html']],
description="Whether to display the time range interactive selector"), default="markdown",
'show_legend': BooleanField( description="Pick your favorite markup language"),
"Legend", default=True, false_values=["f"], 'rotation': SelectField(
description="Whether to display the legend (toggles)"), "Rotation",
'rich_tooltip': BooleanField( choices=[(s, s) for s in ['random', 'flat', 'square']],
"Rich Tooltip", default=True, default="random",
description="The rich tooltip shows a list of all series for that point in time"), description="Rotation to apply to words in the cloud"),
'y_axis_zero': BooleanField( 'code': TextAreaField("Code", description="Put your code here"),
"Y Axis Zero", default=False, 'size_from': TextField(
description="Force the Y axis to start at 0 instead of the minimum value"), "Font Size From",
'y_log_scale': BooleanField( default="20",
"Y Log", default=False, description="Font size for the smallest value in the list"),
description="Use a log scale for the Y axis"), 'size_to': TextField(
'x_log_scale': BooleanField( "Font Size To",
"X Log", default=False, default="150",
description="Use a log scale for the X axis"), description="Font size for the biggest value in the list"),
'donut': BooleanField( 'show_brush': BooleanField(
"Donut", default=False, "Range Selector", default=True,
description="Do you want a donut or a pie?"), description="Whether to display the time range interactive selector"),
'contribution': BooleanField( 'show_legend': BooleanField(
"Contribution", default=False, "Legend", default=True,
description="Compute the contribution to the total"), description="Whether to display the legend (toggles)"),
'num_period_compare': IntegerField( 'rich_tooltip': BooleanField(
"Period Ratio", default=None, "Rich Tooltip", default=True,
description=( description="The rich tooltip shows a list of all series for that point in time"),
"Number of period to compare against, " 'y_axis_zero': BooleanField(
"this is relative to the granularity selected")), "Y Axis Zero", default=False,
} description="Force the Y axis to start at 0 instead of the minimum value"),
field_css_classes = {k: ['form-control'] for k in px_form_fields.keys()} 'y_log_scale': BooleanField(
select2 = [ "Y Log", default=False,
'viz_type', 'metrics', 'groupby', description="Use a log scale for the Y axis"),
'row_limit', 'rolling_type', 'series', 'x_log_scale': BooleanField(
'entity', 'x', 'y', 'size', 'rotation', 'metric', 'limit', "X Log", default=False,
'markup_type',] description="Use a log scale for the X axis"),
field_css_classes['since'] += ['select2_free_since'] 'donut': BooleanField(
field_css_classes['until'] += ['select2_free_until'] "Donut", default=False,
field_css_classes['granularity'] += ['select2_free_granularity'] description="Do you want a donut or a pie?"),
for field in ('show_brush', 'show_legend', 'rich_tooltip'): 'contribution': BooleanField(
field_css_classes[field] += ['input-sm'] "Contribution", default=False,
for field in select2: description="Compute the contribution to the total"),
field_css_classes[field] += ['select2'] 'num_period_compare': IntegerField(
"Period Ratio", default=None,
description=(
"Number of period to compare against, "
"this is relative to the granularity selected")),
}
class QueryForm(OmgWtForm): def get_form(self, previous=False):
field_order = copy(viz.form_fields) px_form_fields = self.field_dict
css_classes = field_css_classes viz = self.viz
datasource = viz.datasource
field_css_classes = {k: ['form-control'] for k in px_form_fields.keys()}
select2 = [
'viz_type', 'metrics', 'groupby',
'row_limit', 'rolling_type', 'series',
'entity', 'x', 'y', 'size', 'rotation', 'metric', 'limit',
'markup_type',]
field_css_classes['since'] += ['select2_free_since']
field_css_classes['until'] += ['select2_free_until']
field_css_classes['granularity'] += ['select2_free_granularity']
for field in ('show_brush', 'show_legend', 'rich_tooltip'):
field_css_classes[field] += ['input-sm']
for field in select2:
field_css_classes[field] += ['select2']
for i in range(10):
setattr(QueryForm, 'flt_col_' + str(i), SelectField(
'Filter 1',
choices=[(s, s) for s in datasource.filterable_column_names]))
setattr(QueryForm, 'flt_op_' + str(i), SelectField(
'Filter 1', choices=[(m, m) for m in ['in', 'not in']]))
setattr(QueryForm, 'flt_eq_' + str(i), TextField("Super"))
for ff in viz.form_fields:
if isinstance(ff, basestring):
ff = [ff]
for s in ff:
if s:
setattr(QueryForm, s, px_form_fields[s])
# datasource type specific form elements class QueryForm(OmgWtForm):
if datasource.__class__.__name__ == 'Table': field_order = copy(viz.form_fields)
QueryForm.field_order += ['where'] css_classes = field_css_classes
setattr(QueryForm, 'where', px_form_fields['where']) standalone = HiddenField()
return QueryForm async = HiddenField()
json = HiddenField()
previous_viz_type = HiddenField()
standalone = HiddenField()
for i in range(10):
setattr(QueryForm, 'flt_col_' + str(i), SelectField(
'Filter 1',
default='',
choices=[(s, s) for s in datasource.filterable_column_names]))
setattr(QueryForm, 'flt_op_' + str(i), SelectField(
'Filter 1',
default='',
choices=[(m, m) for m in ['in', 'not in']]))
setattr(
QueryForm, 'flt_eq_' + str(i),
TextField("Super", default=''))
for ff in viz.form_fields:
if isinstance(ff, basestring):
ff = [ff]
for s in ff:
if s:
setattr(QueryForm, s, px_form_fields[s])
# datasource type specific form elements
if datasource.__class__.__name__ == 'Table':
QueryForm.field_order += ['where']
setattr(QueryForm, 'where', px_form_fields['where'])
return QueryForm

View File

@ -123,6 +123,7 @@
<input type="hidden" name="datasource_name" value="{{ datasource.name }}"> <input type="hidden" name="datasource_name" value="{{ datasource.name }}">
<input type="hidden" name="datasource_id" value="{{ datasource.id }}"> <input type="hidden" name="datasource_id" value="{{ datasource.id }}">
<input type="hidden" name="datasource_type" value="{{ datasource.type }}"> <input type="hidden" name="datasource_type" value="{{ datasource.type }}">
{{ form.previous_viz_type() }}
</form><br> </form><br>
</div> </div>
@ -132,7 +133,7 @@
<span class="label label-success"> <span class="label label-success">
{{ "{0:0.4f}".format(results.duration.total_seconds()) }} s {{ "{0:0.4f}".format(results.duration.total_seconds()) }} s
</span> </span>
<span class="label label-info btn" <span class="label label-info btn"
data-toggle="modal" data-target="#query_modal">query</span> data-toggle="modal" data-target="#query_modal">query</span>
{% endif %} {% endif %}
</h3> </h3>

View File

@ -3,7 +3,7 @@
{% if viz.form_data.get("json") == "true" %} {% if viz.form_data.get("json") == "true" %}
{{ viz.get_json() }} {{ viz.get_json() }}
{% else %} {% else %}
{% if viz.form_data.get("standalone") == "true" %} {% if viz.request.args.get("standalone") == "true" %}
{% extends 'panoramix/viz_standalone.html' %} {% extends 'panoramix/viz_standalone.html' %}
{% else %} {% else %}
{% extends 'panoramix/datasource.html' %} {% extends 'panoramix/datasource.html' %}
@ -16,7 +16,7 @@
{% block head_css %} {% block head_css %}
{{super()}} {{super()}}
{% if viz.form_data.get("skip_libs") != "true" %} {% if viz.request.args.get("skip_libs") != "true" %}
{% for css in viz.css_files %} {% for css in viz.css_files %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename=css) }}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename=css) }}">
{% endfor %} {% endfor %}
@ -27,11 +27,11 @@
{% block tail %} {% block tail %}
{{super()}} {{super()}}
{% if viz.form_data.get("skip_libs") != "true" %} {% if viz.request.args.get("skip_libs") != "true" %}
{% for js in viz.js_files %} {% for js in viz.js_files %}
<script src="{{ url_for('static', filename=js) }}"></script> <script src="{{ url_for('static', filename=js) }}"></script>
{% endfor %} {% endfor %}
{{ viz_macros.viz_js(viz) }}
{% endif %} {% endif %}
{{ viz_macros.viz_js(viz) }}
{% endblock %} {% endblock %}
{% endif %} {% endif %}

View File

@ -37,7 +37,7 @@
nv.addGraph(function() { nv.addGraph(function() {
// chart_type is {{ viz.chart_type }} // chart_type is {{ viz.chart_type }}
{% if viz.chart_type == 'line' %} {% if viz.chart_type == 'line' %}
{% if viz.form_data.show_brush == 'y' %} {% if viz.form_data.show_brush %}
var chart = nv.models.lineWithFocusChart() var chart = nv.models.lineWithFocusChart()
var xext = chart.xAxis.scale().domain(); var xext = chart.xAxis.scale().domain();
chart chart
@ -52,10 +52,10 @@
chart.xAxis chart.xAxis
.showMaxMin(false) .showMaxMin(false)
.tickFormat(function (d) {return tickMultiFormat(new Date(d)); }); .tickFormat(function (d) {return tickMultiFormat(new Date(d)); });
chart.showLegend({{ "{}".format(viz.form_data.show_legend=='y')|lower }}); chart.showLegend({{ "{}".format(viz.form_data.show_legend)|lower }});
chart.yAxis.tickFormat(d3.format('.3s')); chart.yAxis.tickFormat(d3.format('.3s'));
{% if viz.form_data.contribution=='y' or viz.form_data.get("num_period_compare") %} {% if viz.form_data.contribution or viz.form_data.get("num_period_compare") %}
chart.yAxis.tickFormat(d3.format('.3p')); chart.yAxis.tickFormat(d3.format('.3p'));
chart.y2Axis.tickFormat(d3.format('.3p')); chart.y2Axis.tickFormat(d3.format('.3p'));
{% endif %} {% endif %}
@ -71,8 +71,8 @@
{% elif viz.chart_type == 'pie' %} {% elif viz.chart_type == 'pie' %}
var chart = nv.models.pieChart() var chart = nv.models.pieChart()
chart.showLegend({{ "{}".format(viz.form_data.show_legend=='y')|lower }}); chart.showLegend({{ "{}".format(viz.form_data.show_legend)|lower }});
{% if viz.form_data.donut=='y' %} {% if viz.form_data.donut %}
chart.donut(true); chart.donut(true);
chart.donutLabelsOutside(true); chart.donutLabelsOutside(true);
{% endif %} {% endif %}
@ -91,14 +91,14 @@
chart.xAxis chart.xAxis
.showMaxMin(false) .showMaxMin(false)
.tickFormat(function (d) {return tickMultiFormat(new Date(d)); }); .tickFormat(function (d) {return tickMultiFormat(new Date(d)); });
chart.showLegend({{ "{}".format(viz.form_data.show_legend=='y')|lower }}); chart.showLegend({{ "{}".format(viz.form_data.show_legend)|lower }});
chart.yAxis.tickFormat(d3.format('.3p')); chart.yAxis.tickFormat(d3.format('.3p'));
{% elif viz.chart_type == 'bubble' %} {% elif viz.chart_type == 'bubble' %}
var chart = nv.models.scatterChart(); var chart = nv.models.scatterChart();
chart.xAxis.tickFormat(d3.format('.3s')); chart.xAxis.tickFormat(d3.format('.3s'));
chart.yAxis.tickFormat(d3.format('.3s')); chart.yAxis.tickFormat(d3.format('.3s'));
chart.showLegend({{ "{}".format(viz.form_data.show_legend=='y')|lower }}); chart.showLegend({{ "{}".format(viz.form_data.show_legend)|lower }});
chart.pointRange([5, 5000]); chart.pointRange([5, 5000]);
{% elif viz.chart_type == 'stacked' %} {% elif viz.chart_type == 'stacked' %}
@ -107,21 +107,21 @@
chart.xAxis chart.xAxis
.showMaxMin(false) .showMaxMin(false)
.tickFormat(function (d) {return tickMultiFormat(new Date(d)); }); .tickFormat(function (d) {return tickMultiFormat(new Date(d)); });
chart.showLegend({{ "{}".format(viz.form_data.show_legend=='y')|lower }}); chart.showLegend({{ "{}".format(viz.form_data.show_legend)|lower }});
chart.yAxis.tickFormat(d3.format('.3s')); chart.yAxis.tickFormat(d3.format('.3s'));
{% endif %} {% endif %}
{% if viz.chart_type in ("line", "stacked") and viz.form_data.rich_tooltip == 'y' %} {% if viz.chart_type in ("line", "stacked") and viz.form_data.rich_tooltip %}
chart.useInteractiveGuideline(true); chart.useInteractiveGuideline(true);
{% endif %} {% endif %}
{% if viz.form_data.y_axis_zero == 'y' %} {% if viz.form_data.y_axis_zero %}
chart.forceY([0, 1]); chart.forceY([0, 1]);
{% elif viz.form_data.y_log_scale == 'y' %} {% elif viz.form_data.y_log_scale %}
chart.yScale(d3.scale.log()); chart.yScale(d3.scale.log());
{% endif %} {% endif %}
{% if viz.form_data.x_log_scale == 'y' %} {% if viz.form_data.x_log_scale %}
chart.xScale(d3.scale.log()); chart.xScale(d3.scale.log());
{% endif %} {% endif %}

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
{% if viz.form_data.get("skip_libs") != "true" %} {% if viz.request.args.get("skip_libs") != "true" %}
{% block head %} {% block head %}
<script src="{{url_for('appbuilder.static',filename='js/jquery-latest.js')}}"></script> <script src="{{url_for('appbuilder.static',filename='js/jquery-latest.js')}}"></script>
{% endblock %} {% endblock %}

View File

@ -1,5 +1,5 @@
{% macro viz_html(viz) %} {% macro viz_html(viz) %}
{% if viz.form_data.get("async") == "true" %} {% if viz.request.args.get("async") == "true" %}
{% set df = viz.get_df() %} {% set df = viz.get_df() %}
<table class="dataframe table table-striped table-bordered table-condensed"> <table class="dataframe table table-striped table-bordered table-condensed">
<thead> <thead>

View File

@ -3,23 +3,19 @@ from datetime import datetime
import json import json
import uuid import uuid
from flask import flash from flask import flash, request
from markdown import markdown from markdown import markdown
from pandas.io.json import dumps from pandas.io.json import dumps
from werkzeug.datastructures import MultiDict from werkzeug.datastructures import ImmutableMultiDict
from werkzeug.urls import Href from werkzeug.urls import Href
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from panoramix import app, utils from panoramix import app, utils
from panoramix.forms import form_factory from panoramix.forms import FormFactory
config = app.config config = app.config
CHART_ARGS = {
'title': None,
}
class BaseViz(object): class BaseViz(object):
verbose_name = "Base Viz" verbose_name = "Base Viz"
@ -32,12 +28,26 @@ class BaseViz(object):
def __init__(self, datasource, form_data): def __init__(self, datasource, form_data):
self.datasource = datasource self.datasource = datasource
form = self.form_class(form_data) self.request = request
form.validate()
raise ff = FormFactory(self)
self.form_data = form_data form_class = ff.get_form()
if isinstance(form_data, MultiDict): defaults = form_class().data.copy()
self.form_data = form_data.to_dict(flat=False) if isinstance(form_data, ImmutableMultiDict):
form = form_class(form_data)
else:
form = form_class(**form_data)
data = form.data.copy()
previous_viz_type = form_data.get('previous_viz_type')
if previous_viz_type in viz_types:
data = {
k: form.data[k]
for k in form_data.keys()
if k in viz_types[previous_viz_type].flat_form_fields() and k in form.data}
defaults.update(data)
self.form_data = defaults
self.form_data['previous_viz_type'] = form_data.get("viz_type")
self.token = self.form_data.get( self.token = self.form_data.get(
'token', 'token_' + uuid.uuid4().hex[:8]) 'token', 'token_' + uuid.uuid4().hex[:8])
@ -48,9 +58,19 @@ class BaseViz(object):
elif k not in as_list and isinstance(v, list) and v: elif k not in as_list and isinstance(v, list) and v:
self.form_data[k] = v[0] self.form_data[k] = v[0]
self.metrics = self.form_data.get('metrics') or ['count'] self.metrics = self.form_data.get('metrics') or []
self.groupby = self.form_data.get('groupby') or [] self.groupby = self.form_data.get('groupby') or []
@classmethod
def flat_form_fields(cls):
l = []
for obj in cls.form_fields:
if isinstance(obj, (tuple, list)):
l += [a for a in obj]
else:
l.append(obj)
return l
def get_url(self, **kwargs): def get_url(self, **kwargs):
d = self.form_data.copy() d = self.form_data.copy()
if 'action' in d: if 'action' in d:
@ -80,7 +100,7 @@ class BaseViz(object):
@property @property
def form_class(self): def form_class(self):
return form_factory(self) return FormFactory(self).get_form()
def query_filters(self): def query_filters(self):
form_data = self.form_data form_data = self.form_data
@ -334,11 +354,13 @@ class NVD3TimeSeriesViz(NVD3Viz):
form_data = self.form_data form_data = self.form_data
df = super(NVD3TimeSeriesViz, self).get_df() df = super(NVD3TimeSeriesViz, self).get_df()
df = df.fillna(0) df = df.fillna(0)
metrics = self.metrics if form_data.get("granularity") == "all":
raise Exception("Pick a time granularity for your time series")
df = df.pivot_table( df = df.pivot_table(
index="timestamp", index="timestamp",
columns=self.groupby, columns=self.form_data.get('groupby'),
values=metrics,) values=self.form_data.get('metrics'))
if self.sort_series: if self.sort_series:
dfs = df.sum() dfs = df.sum()
@ -379,7 +401,7 @@ class NVD3TimeSeriesViz(NVD3Viz):
series_title = name series_title = name
else: else:
name = ["{}".format(s) for s in name] name = ["{}".format(s) for s in name]
if len(self.metrics) > 1: if len(self.form_data.get('metrics')) > 1:
series_title = ", ".join(name) series_title = ", ".join(name)
else: else:
series_title = ", ".join(name[1:]) series_title = ", ".join(name[1:])