Merge pull request #3 from mistercrunch/charts

Implementing my own highcharts wrapper
This commit is contained in:
Maxime Beauchemin 2015-07-22 23:12:44 -07:00
commit b038d5a7e5
6 changed files with 189 additions and 26 deletions

View File

@ -1,5 +1,8 @@
# TODO
* in/notin filters autocomplete
* Highstock, sort legend based on y value: ![stackoverflow](http://stackoverflow.com/questions/6867607/want-to-sort-highcharts-tooltip-results)
* compare time ranges
* Label
* Add verbose_name and label method to metrics and columns
* CSV
* Save / bookmark / url shortener
* on save, process metadata / generate metrics

BIN
app.db

Binary file not shown.

158
app/highchart.py Normal file
View File

@ -0,0 +1,158 @@
import pandas
import copy
from pandas.io.json import dumps
class Highchart(object):
def __init__(
self, df,
chart_type="spline",
target_div="#chart",
polar=False,
width=None,
height=None,
show_legend=True,
stockchart=False,
title=None,
tooltip=None,
sort_columns=False,
secondary_y=None,
mark_right=False,
compare=False,
stacked=False,
logx=False,
logy=False,
xlim=None,
ylim=None,
grid=False,
zoom=None):
self.df = df
self.chart_type = chart_type
self.chart = chart = {}
self.stockchart = stockchart
self.sort_columns = sort_columns
self.secondary_y = secondary_y or []
self.mark_right = mark_right
self.compare = compare
self.logx = logx
self.logy = logy
self.xlim = xlim
self.ylim = ylim
self.zoom = zoom
self.polar = polar
self.grid = grid
self.stacked = stacked
chart['chart'] = {}
chart['chart']["type"] = chart_type
chart['chart']['renderTo'] = target_div
if width:
chart['chart']["width"] = width
if height:
chart['chart']["height"] = height
chart['chart']['polar'] = polar
chart["legend"] = {
"enabled": show_legend
}
chart["title"] = {"text": title}
if tooltip:
chart['tooltip'] = tooltip
if self.zoom:
chart["zoomType"] = self.zoom
self.serialize_series()
self.serialize_xaxis()
self.serialize_yaxis()
self.chart = chart
def serialize_series(self):
df = self.df
chart = self.chart
if self.sort_columns:
df = df.sort_index()
series = df.to_dict('series')
chart["series"] = []
for name, data in series.items():
if df[name].dtype.kind not in "biufc":
continue
sec = name in self.secondary_y
d = {
"name": name if not sec or self.mark_right else name + " (right)",
"yAxis": int(sec),
"data": zip(df.index, data.tolist())
}
if self.polar:
d['data'] = [v for k, v in d['data']]
if self.compare:
d['compare'] = self.compare # either `value` or `percent`
if self.chart_type in ("area", "column", "bar") and self.stacked:
d["stacking"] = 'normal'
#if kwargs.get("style"):
# d["dashStyle"] = pd2hc_linestyle(kwargs["style"].get(name, "-"))
chart["series"].append(d)
def serialize_xaxis(self):
df = self.df
x_axis = {}
if df.index.name:
x_axis["title"] = {"text": df.index.name}
if df.index.dtype.kind in "M":
x_axis["type"] = "datetime"
if df.index.dtype.kind == 'O':
x_axis['categories'] = sorted(list(df.index)) if self.sort_columns else list(df.index)
print list(df.index)
if self.grid:
x_axis["gridLineWidth"] = 1
x_axis["gridLineDashStyle"] = "Dot"
if self.logx:
x_axis["type"] = 'logarithmic'
if self.xlim:
x_axis["min"] = self.xlim[0]
x_axis["max"] = self.xlim[1]
'''
if "rot" in kwargs:
x_axis["labels"] = {"rotation": kwargs["rot"]}
if "fontsize" in kwargs:
x_axis.setdefault("labels", {})["style"] = {"fontSize": kwargs["fontsize"]}
if "xticks" in kwargs:
x_axis["tickPositions"] = kwargs["xticks"]
'''
self.chart['xAxis'] = x_axis
def serialize_yaxis(self):
yAxis = {}
chart = self.chart
if self.grid:
yAxis["gridLineWidth"] = 1
yAxis["gridLineDashStyle"] = "Dot"
if self.logy:
yAxis["type"] = 'logarithmic'
if self.ylim:
yAxis["min"] = self.ylim[0]
yAxis["max"] = self.ylim[1]
'''
if "rot" in kwargs:
yAxis["labels"] = {"rotation": kwargs["rot"]}
if "fontsize" in kwargs:
yAxis.setdefault("labels", {})["style"] = {"fontSize": kwargs["fontsize"]}
if "yticks" in kwargs:
yAxis["tickPositions"] = kwargs["yticks"]
'''
chart["yAxis"] = [yAxis]
if self.secondary_y:
yAxis2 = copy.deepcopy(yAxis)
yAxis2["opposite"] = True
chart["yAxis"].append(yAxis2)
@property
def javascript_cmd(self):
js = dumps(self.chart)
if self.stockchart:
return "new Highcharts.StockChart(%s);" % js
return "new Highcharts.Chart(%s);" %js

View File

@ -17,7 +17,7 @@
{% block tail %}
{{ super() }}
{% if viz.chart_type == "stock" %}
{% if viz.stockchart %}
<script src="{{ url_for("static", filename="highstock.js") }}"></script>
{% else %}
<script src="{{ url_for("static", filename="highcharts.js") }}"></script>

View File

@ -1,19 +1,18 @@
from pydruid.utils.filters import Dimension, Filter
from datetime import datetime
from flask import render_template, flash, request
from flask import flash, request
import pandas as pd
from pandas_highcharts.core import serialize
from pydruid.utils import aggregators as agg
from collections import OrderedDict
from app import utils
from app.highchart import Highchart
from wtforms import Form, SelectMultipleField, SelectField, TextField
import config
CHART_ARGS = {
'figsize': (None, 700),
'height': 700,
'title': None,
'render_to': 'chart',
'target_div': 'chart',
}
class OmgWtForm(Form):
@ -220,8 +219,8 @@ class HighchartsViz(BaseViz):
class TimeSeriesViz(HighchartsViz):
verbose_name = "Time Series - Line Chart"
chart_kind = "spline"
chart_type = 'stock'
chart_type = "spline"
stockchart = True
def render(self):
metrics = self.metrics
@ -237,12 +236,14 @@ class TimeSeriesViz(HighchartsViz):
if rolling_type == 'mean':
df = pd.rolling_mean(df, int(rolling_periods))
chart_js = serialize(
df, kind=self.chart_kind,
viz=self,
chart = Highchart(
df,
compare=self.compare,
chart_type=self.chart_type, stacked=self.stacked, **CHART_ARGS)
return super(TimeSeriesViz, self).render(chart_js=chart_js)
chart_type=self.chart_type,
stacked=self.stacked,
stockchart=self.stockchart,
**CHART_ARGS)
return super(TimeSeriesViz, self).render(chart_js=chart.javascript_cmd)
def form_class(self):
return form_factory(self.datasource, request.args,
@ -297,23 +298,23 @@ class TimeSeriesCompareViz(TimeSeriesViz):
class TimeSeriesAreaViz(TimeSeriesViz):
verbose_name = "Time Series - Stacked Area Chart"
stacked=True
chart_kind = "area"
chart_type = "area"
class TimeSeriesBarViz(TimeSeriesViz):
verbose_name = "Time Series - Bar Chart"
chart_kind = "bar"
chart_type = "column"
class TimeSeriesStackedBarViz(TimeSeriesViz):
verbose_name = "Time Series - Stacked Bar Chart"
chart_kind = "bar"
chart_type = "column"
stacked = True
class DistributionBarViz(HighchartsViz):
verbose_name = "Distribution - Bar Chart"
chart_kind = "bar"
chart_type = "column"
def query_obj(self):
d = super(DistributionBarViz, self).query_obj()
@ -326,14 +327,15 @@ class DistributionBarViz(HighchartsViz):
index=self.groupby,
values=self.metrics)
df = df.sort(self.metrics[0], ascending=False)
chart_js = serialize(
df, kind=self.chart_kind, **CHART_ARGS)
return super(DistributionBarViz, self).render(chart_js=chart_js)
chart = Highchart(
df, chart_type=self.chart_type, **CHART_ARGS)
return super(DistributionBarViz, self).render(
chart_js=chart.javascript_cmd)
class DistributionPieViz(HighchartsViz):
verbose_name = "Distribution - Pie Chart"
chart_kind = "pie"
chart_type = "pie"
def query_obj(self):
d = super(DistributionPieViz, self).query_obj()
@ -346,9 +348,10 @@ class DistributionPieViz(HighchartsViz):
index=self.groupby,
values=[self.metrics[0]])
df = df.sort(self.metrics[0], ascending=False)
chart_js = serialize(
df, kind=self.chart_kind, **CHART_ARGS)
return super(DistributionPieViz, self).render(chart_js=chart_js)
chart = Highchart(
df, chart_type=self.chart_type, **CHART_ARGS)
return super(DistributionPieViz, self).render(
chart_js=chart.javascript_cmd)
viz_types = OrderedDict([
['table', TableViz],

View File

@ -2,7 +2,6 @@ flask
flask-alembic
flask-appbuilder
pandas
pandas-highcharts
parsedatetime
pydruid
python-dateutil