Commit 4f1527e3 authored by LE GAC Renaud's avatar LE GAC Renaud
Browse files

Add the report class Graph.

parent b1dfbc7c
......@@ -3,21 +3,12 @@
"""
import urllib
import json
import matplotlib
import re
from gluon.storage import Storage
from plugin_dbui import get_id, INLINE_ALERT
from report_objects import do_title, List, Metric1D, Metric2D
from plugin_dbui import INLINE_ALERT
from report_objects import do_title, Graph, List, Metric1D, Metric2D
from selector import MySelector
from StringIO import StringIO
# A specific set of keys use to to tune matplotlib rendering
# These keys are in the plot configuration but are not arguments
# of the DataFrame.plot method
PLT_KEYS = ('index', 'transpose', 'xlabel', 'ylabel')
def graph_extjs():
......@@ -55,103 +46,21 @@ def graph_mpl():
"""Plot list or metric using the matplotlib library
"""
# set the matplotlib back end
#
# NOTE: the X11 back end is not needed on the server side. In addition
# Tkinter crash with the message "out of stack space" when the 2nd plot
# is generated.
# The documentation recommend to limit the matplotlib back end to Agg
# which is tuned to render high quality PNG image. But, it is also
# design to render PDF and SVG image without the X11 interface.
#
matplotlib.use('Agg')
# instantiate the selector
# selector and configuration
ui_table = virtdb.graph_selector
selector = MySelector(ui_table)
# get the lists/metrics configuration
graph = db.graphs[request.vars.id_graphs]
tablename = graph.report_type
id = get_id(db[tablename], name=graph.report_name)
if not id:
msg = "No %s named %s" % (tablename, graph.report_name)
return INLINE_ALERT % ("Error", msg)
try:
# instantiate the lists/metrics
config = db[tablename][id]
if tablename == "lists":
report = List(config, selector)
elif tablename == "metrics1d":
report = Metric1D(config, selector)
elif tablename == "metrics2d":
report = Metric2D(config, selector)
df = report.to_df()
# decode the graph configuration
kwargs = json.loads(graph.plot)
# extract keyword which are not arguments of the DataFrame.plot method
plt_cfg = Storage()
for k in PLT_KEYS:
plt_cfg[k] = kwargs.pop(k, None)
# transpose
if plt_cfg.transpose:
df = df.T
# generate the plot using a specific set of columns
if plt_cfg.index and len(plt_cfg.index) <= len(df.columns):
ax = df.ix[:,plt_cfg.index].plot(**kwargs)
# generate the plot using all columns
else:
ax = df.ix[:,:].plot(**kwargs)
except (IndexError, TypeError, ValueError) as e:
return INLINE_ALERT % ("Error", e)
# customize ticks
ax.minorticks_on()
ax.tick_params(which='major', length=8)
ax.tick_params(which='minor', length=4)
# set x / y label(s)
if plt_cfg.xlabel:
ax.set_xlabel(plt_cfg.xlabel, x=1, horizontalalignment='right')
if plt_cfg.ylabel:
ax.set_ylabel(plt_cfg.ylabel, y=1, horizontalalignment='right')
# push legend on the top outside the plot
# from http://stackoverflow.com/questions/4700614/how-to-put-the-legend-out-of-the-plot
if ax.get_legend():
box = ax.get_position()
ax.set_position([box.x0, box.y0, box.width, box.height * 0.9])
ax.legend(loc='lower right',
bbox_to_anchor=(1.01, 1.),
fontsize=10,
ncol=3)
# save the figure into a string
fi = StringIO()
config = db.graphs[request.vars.id_graphs]
fig = ax.get_figure()
fig.savefig(fi, format='svg')
fig.clear()
matplotlib.pyplot.close(fig)
# build the graph
graph = Graph(config, selector)
# try:
# graph = Graph(config, selector)
#
# except (IndexError, TypeError, ValueError) as e:
# return INLINE_ALERT % ("Error", e)
data = fi.getvalue()
fi.close()
# delegate the rendering of the figure to the view
return dict(data=data)
# delegate the rendering to the view
return dict(data=graph.to_svg())
def grid():
......
......@@ -2,6 +2,7 @@
"""
import json
import matplotlib
import pandas as pd
import re
......@@ -10,7 +11,8 @@ from datetime import date, timedelta
from gluon import current
from gluon.dal import Field, smart_query
from gluon.storage import Storage
from plugin_dbui import Store
from plugin_dbui import get_id, Store
from StringIO import StringIO
def do_title(config, selector):
......@@ -152,7 +154,7 @@ class BaseReport(object):
self.selector = selector
# apply the condition criteria used to filter the history records
if config.conditions:
if "conditions" in config:
q_conditions = smart_query(db.history, config.conditions)
selector.append_query(q_conditions)
......@@ -166,6 +168,167 @@ class BaseReport(object):
return self.df
class Graph(BaseReport):
"""Any data encapsulated in list, 1-dim or 2-dim metrics
can be displayed as a graph. The rendering is performed by
the matplotlib library. Therefore, many representations of
the data are possible: plot, histogram, bar charts, errorcharts,
scaterplots, etc.
@type ax: matplotlib.AxesSubplot
@ivar ax:
"""
def __init__(self, config, selector, backend="Agg"):
BaseReport.__init__(self, config, selector)
# set the matplotlib back end
#
# NOTE: the X11 back end is not needed on the server side. In addition
# Tkinter crash with the message "out of stack space" when the 2nd plot
# is generated.
# The documentation recommend to limit the matplotlib back end to Agg
# which is tuned to render high quality PNG image. But, it is also
# design to render PDF and SVG image without the X11 interface.
#
matplotlib.use(backend)
# split the plot configuration in two parts:
# 1) keywords for the DataFrame.plot method
# 2) steering parameter for this class
config.plot = json.loads(config.plot)
config.steer = Storage()
for k in ('index', 'transpose', 'xlabel', 'ylabel'):
v = config.plot.pop(k, None)
config.steer[k] = v
# instantiate the DataFrame for the report
db = self.db
report_type = config.report_type
report_name = config.report_name
report_id = get_id(db[report_type], name=report_name)
report_config = db[report_type][report_id]
if report_type == "lists":
report = List(report_config, selector)
elif report_type == "metrics1d":
report = Metric1D(report_config, selector)
elif report_type == "metrics2d":
report = Metric2D(report_config, selector)
self.df = report.to_df()
# build the graph from the DataFrame
self._do_graph()
self._do_labels()
self._do_legend()
self._do_tick()
def _do_graph(self):
"""Build the graph from the C{DataFrame} structure.
"""
config = self.config
df = self.df
plot, steer = config.plot, config.steer
# transpose
if steer.transpose:
df = df.T
# generate the plot using a specific set of columns
if steer.index and len(steer.index) <= len(df.columns):
ax = df.ix[:,steer.index].plot(**plot)
# generate the plot using all columns
else:
ax = df.ix[:,:].plot(**plot)
# persistence
self.ax = ax
def _do_labels(self):
"""Deal with axes label.
"""
ax = self.ax
steer = self.config.steer
if steer.xlabel:
ax.set_xlabel(steer.xlabel, x=1, horizontalalignment='right')
if steer.ylabel:
ax.set_ylabel(steer.ylabel, y=1, horizontalalignment='right')
def _do_legend(self):
"""Deal with legend.
"""
ax = self.ax
if ax.get_legend():
box = ax.get_position()
ax.set_position([box.x0, box.y0, box.width, box.height * 0.9])
ax.legend(loc='lower right',
bbox_to_anchor=(1.01, 1.),
fontsize=10,
ncol=3)
def _do_tick(self):
"""Polish the tick mark
"""
ax = self.ax
ax.minorticks_on()
ax.tick_params(which='major', length=8)
ax.tick_params(which='minor', length=4)
def to_pdf(self):
"""
@rtype: string
@return: encode the graph with the PDF format.
"""
def to_png(self):
"""
@rtype: string
@return: encode the graph with the PNG format.
"""
def to_svg(self):
"""
@rtype: string
@return: encode the graph with the SVG format.
"""
fig = self.ax.get_figure()
fi = StringIO()
fig.savefig(fi, format='svg')
data = fi.getvalue()
fi.close()
fig.clear()
matplotlib.pyplot.close(fig)
return data
class List(BaseReport):
"""A list is a table in which each column contains the values of
one database field. The rows can be grouped per value of a given column.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment