added iplotter
This commit is contained in:
parent
b1a74737b9
commit
cf9c0abb7d
8 changed files with 909 additions and 0 deletions
8
src/iplotter/__init__.py
Normal file
8
src/iplotter/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from .base_plotter import IPlotter
|
||||||
|
# from .export import VirtualBrowser
|
||||||
|
from .c3_plotter import C3Plotter
|
||||||
|
from .plotly_plotter import PlotlyPlotter
|
||||||
|
from .chartjs_plotter import ChartJSPlotter
|
||||||
|
from .chartist_plotter import ChartistPlotter
|
||||||
|
from .google_plotter import GCPlotter
|
||||||
|
__version__ = '0.4.3'
|
||||||
55
src/iplotter/base_plotter.py
Normal file
55
src/iplotter/base_plotter.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
# from selenium import webdriver
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class IPlotter(object):
|
||||||
|
"""Abstract IPlotter"""
|
||||||
|
|
||||||
|
__metaclass__ = ABCMeta
|
||||||
|
iframe = '<iframe srcdoc="{source}" src="" width="{w}" height="{h}" frameborder="0" sandbox="allow-scripts"></iframe>'
|
||||||
|
invalid_name_pattern = re.compile(r'[^a-zA-Z0-9_\-\. ]+')
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(IPlotter, self).__init__()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_valid_name(cls, name):
|
||||||
|
'''
|
||||||
|
check whether plot div id or filenname are valid
|
||||||
|
'''
|
||||||
|
if (cls.invalid_name_pattern.search(name)):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def render(self):
|
||||||
|
'''
|
||||||
|
render the data in HTML template
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def plot(self):
|
||||||
|
'''
|
||||||
|
output an iframe containing the plot in the notebook without saving
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def save(self):
|
||||||
|
'''
|
||||||
|
save the rendered html to a file in the same directory as the notebook
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def plot_and_save(self):
|
||||||
|
'''
|
||||||
|
save the rendered html to a file and return an IFrame to display the
|
||||||
|
plot in the notebook
|
||||||
|
'''
|
||||||
|
pass
|
||||||
386
src/iplotter/c3_plotter.py
Normal file
386
src/iplotter/c3_plotter.py
Normal file
|
|
@ -0,0 +1,386 @@
|
||||||
|
from jinja2 import Template
|
||||||
|
from IPython.display import IFrame, HTML
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from .base_plotter import IPlotter
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
class C3Plotter(IPlotter):
|
||||||
|
"""
|
||||||
|
Class for creating c3.js charts in ipython notebook
|
||||||
|
"""
|
||||||
|
|
||||||
|
head = '''
|
||||||
|
<!-- Load c3.css -->
|
||||||
|
<link href='https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.min.css' rel='stylesheet' type='text/css'/>
|
||||||
|
<!-- Load d3.js and c3.js -->
|
||||||
|
<script src='http://d3js.org/d3.v3.min.js' charset='utf-8'></script>
|
||||||
|
<script src='http://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.min.js'></script>
|
||||||
|
<style>{{custom_css}}</style>
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
template = '''
|
||||||
|
<h1>{{title}}</h1>
|
||||||
|
<div id={{div_id}} style='width: 100%; height: 100%'></div>
|
||||||
|
<script>
|
||||||
|
var {{div_id}} = document.getElementById('{{div_id}}');
|
||||||
|
var data = {{data}};
|
||||||
|
data['axis']['y']['tick']['format'] = d3.format('{{y_axis_tick_format}}')
|
||||||
|
data['axis']['y2']['tick']['format'] = d3.format('{{secondary_y_axis_tick_format}}')
|
||||||
|
data['bindto']='#{{div_id}}'
|
||||||
|
var {{div_id}} = c3.generate(data);
|
||||||
|
</script>
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(C3Plotter, self).__init__()
|
||||||
|
|
||||||
|
def render(self,
|
||||||
|
data,
|
||||||
|
div_id="chart",
|
||||||
|
custom_css='',
|
||||||
|
title="",
|
||||||
|
head="",
|
||||||
|
y_axis_tick_format='',
|
||||||
|
secondary_y_axis_tick_format=''
|
||||||
|
,
|
||||||
|
**kwargs):
|
||||||
|
'''
|
||||||
|
render the data in HTML template
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
data = self.pandas_data(data, **kwargs)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not self.is_valid_name(div_id):
|
||||||
|
raise ValueError(
|
||||||
|
"Name {} is invalid. Only letters, numbers, '_', and '-' are permitted ".format(
|
||||||
|
div_id))
|
||||||
|
|
||||||
|
return Template(head + self.template).render(
|
||||||
|
div_id=div_id.replace(" ", "_"),
|
||||||
|
custom_css=custom_css,
|
||||||
|
title=title,
|
||||||
|
y_axis_tick_format=y_axis_tick_format,
|
||||||
|
secondary_y_axis_tick_format=secondary_y_axis_tick_format,
|
||||||
|
data=json.dumps(
|
||||||
|
data, indent=4).replace("'", "\\'").replace('"', "'"))
|
||||||
|
|
||||||
|
def plot_and_save(self,
|
||||||
|
data,
|
||||||
|
w=800,
|
||||||
|
h=430,
|
||||||
|
filename='chart',
|
||||||
|
subplots=False,
|
||||||
|
subplot_groups=False,
|
||||||
|
title=False,
|
||||||
|
overwrite=True):
|
||||||
|
'''
|
||||||
|
save the rendered html to a file and returns an IFrame to display the plot in the notebook
|
||||||
|
'''
|
||||||
|
self.save(data, filename, overwrite,)
|
||||||
|
return IFrame(filename + '.html', w, h)
|
||||||
|
|
||||||
|
def plot(self,
|
||||||
|
data,
|
||||||
|
w=800,
|
||||||
|
h=430,
|
||||||
|
div_id='chart',
|
||||||
|
subplots=False,
|
||||||
|
subplot_groups=False,
|
||||||
|
title=False,
|
||||||
|
**kwargs):
|
||||||
|
'''
|
||||||
|
output an iframe containing the plot in the notebook without saving
|
||||||
|
'''
|
||||||
|
|
||||||
|
if subplots:
|
||||||
|
if title:
|
||||||
|
if len(title) > 0:
|
||||||
|
title = title + '<br>'
|
||||||
|
body = ''
|
||||||
|
|
||||||
|
if not subplot_groups:
|
||||||
|
subplot_groups = {col: [col] for col in data.columns}
|
||||||
|
|
||||||
|
for group in subplot_groups:
|
||||||
|
body = body + (self.render(data=data[subplot_groups[group]],
|
||||||
|
div_id=str(div_id) + str(group),
|
||||||
|
head=self.head,
|
||||||
|
title=str(title) + str(group),
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
)
|
||||||
|
title=''
|
||||||
|
else:
|
||||||
|
body = self.render(
|
||||||
|
data=data,
|
||||||
|
div_id=div_id,
|
||||||
|
head=self.head,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
return HTML(self.iframe.format(source=body, w=w, h=h*len(subplot_groups)))
|
||||||
|
|
||||||
|
def update():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def save(self, data, filename='chart', overwrite=True):
|
||||||
|
'''
|
||||||
|
save the rendered html to a file in the same directory as the notebook
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
data = self.pandas_data(data, **kwargs)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
html = self.render(data=data, div_id=filename, head=self.head)
|
||||||
|
if overwrite:
|
||||||
|
with open(filename.replace(" ", "_") + '.html', 'w') as f:
|
||||||
|
f.write(html)
|
||||||
|
else:
|
||||||
|
if not os.path.exists(filename.replace(" ", "_") + '.html'):
|
||||||
|
with open(filename.replace(" ", "_") + '.html', 'w') as f:
|
||||||
|
f.write(html)
|
||||||
|
else:
|
||||||
|
raise IOError('File Already Exists!')
|
||||||
|
|
||||||
|
def pandas_data(self,
|
||||||
|
df,
|
||||||
|
colors=False,
|
||||||
|
data_label_formats=False,
|
||||||
|
data_labels=False,
|
||||||
|
grid=False,
|
||||||
|
group=False,
|
||||||
|
height=300,
|
||||||
|
hue=False,
|
||||||
|
kind='line',
|
||||||
|
kinds=None,
|
||||||
|
legend=True,
|
||||||
|
mark_right=False,
|
||||||
|
point=False,
|
||||||
|
secondary_y=list(),
|
||||||
|
stacked=False,
|
||||||
|
subchart=False,
|
||||||
|
subplots=False,
|
||||||
|
tick_count=10,
|
||||||
|
value=False,
|
||||||
|
value_labels=False,
|
||||||
|
x_axis_tick_culling=False,
|
||||||
|
x_axis_type='auto',
|
||||||
|
x_tick_values=False,
|
||||||
|
xlabels=False,
|
||||||
|
xlim=False,
|
||||||
|
xregions=False,
|
||||||
|
xy_rotated=False,
|
||||||
|
ylabels=False,
|
||||||
|
ylim=False,
|
||||||
|
yregions=False,
|
||||||
|
zoom=False,
|
||||||
|
):
|
||||||
|
'''
|
||||||
|
create data dictionary from pandas DataFrame
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
## Pandas Features
|
||||||
|
* proper docstring
|
||||||
|
* subplots
|
||||||
|
* layout
|
||||||
|
* height -> figsize
|
||||||
|
* use_index
|
||||||
|
* legend
|
||||||
|
* xlim (axis.x.min, axis.x.max or axis.x.extent)
|
||||||
|
* ylim
|
||||||
|
* colorbar
|
||||||
|
* table
|
||||||
|
* axis-rotation
|
||||||
|
## Seaborn-esque features
|
||||||
|
* hue - ability to provide long form data
|
||||||
|
## C3 Features
|
||||||
|
* interaction: {enabled: false}
|
||||||
|
* transition: {duration: 500}
|
||||||
|
* onrendered: function() {...}
|
||||||
|
* onmouseover/out
|
||||||
|
* data.empty.label.text
|
||||||
|
* data.selection.enabled
|
||||||
|
* data.selection.grouped
|
||||||
|
* data.selection.multiple
|
||||||
|
* data.selection.draggable
|
||||||
|
* axis.x.tick.fit
|
||||||
|
* axis.x.tick.values
|
||||||
|
* axis.x.tick.rotate
|
||||||
|
* axis.x.label
|
||||||
|
* axis.x.show
|
||||||
|
* legend.hide
|
||||||
|
* legend.position
|
||||||
|
* tooltip.show
|
||||||
|
* tooltip.grouped
|
||||||
|
* point.focus.expand.enabled
|
||||||
|
* subchart.size.height
|
||||||
|
* point.focus.expand.r
|
||||||
|
* point.select.r
|
||||||
|
* line.connectNull
|
||||||
|
|
||||||
|
|
||||||
|
param kind: str
|
||||||
|
* line
|
||||||
|
* spline
|
||||||
|
* step
|
||||||
|
* areas
|
||||||
|
* area-spline
|
||||||
|
* area-step
|
||||||
|
* bar
|
||||||
|
* scatter
|
||||||
|
* pie
|
||||||
|
* donut
|
||||||
|
* gauge
|
||||||
|
|
||||||
|
param x_axis_type: str
|
||||||
|
* timeseries
|
||||||
|
* category
|
||||||
|
* numeric
|
||||||
|
|
||||||
|
'''
|
||||||
|
# kinds = ['line', 'spline', 'step', 'area','area-spline', 'area-step',
|
||||||
|
# 'bar', 'scatter', 'pie', 'donut', 'gauge']
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'size': {
|
||||||
|
'height': height,
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
'x': 'x',
|
||||||
|
'axes': dict()
|
||||||
|
},
|
||||||
|
'subchart': {
|
||||||
|
'show': subchart
|
||||||
|
},
|
||||||
|
'point': {
|
||||||
|
'show': point
|
||||||
|
},
|
||||||
|
'grid': {
|
||||||
|
'x': {
|
||||||
|
'show': grid
|
||||||
|
},
|
||||||
|
'y': {
|
||||||
|
'show': grid
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'axis': {
|
||||||
|
'rotated': xy_rotated,
|
||||||
|
'x': {'tick': {'count': tick_count,
|
||||||
|
'values': x_tick_values,
|
||||||
|
'culling': dict(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'y': {'tick': {'format': ''}},
|
||||||
|
'y2': {'tick': {}},
|
||||||
|
},
|
||||||
|
'zoom': {}
|
||||||
|
|
||||||
|
}
|
||||||
|
if kind:
|
||||||
|
data['data']['type'] = kind
|
||||||
|
if kinds:
|
||||||
|
data['data']['types'] = kinds
|
||||||
|
|
||||||
|
if mark_right:
|
||||||
|
df = df.rename(
|
||||||
|
columns={col: col + '(right)' for col in secondary_y})
|
||||||
|
secondary_y = [y + '(right)' for y in secondary_y]
|
||||||
|
if hue and value:
|
||||||
|
df = df.groupby([df.index.name, hue])[value].sum().unstack()
|
||||||
|
|
||||||
|
df = df.copy()
|
||||||
|
df['x'] = df.index
|
||||||
|
df['x'] = df['x'].astype('str').values.tolist()
|
||||||
|
|
||||||
|
data['data']['columns'] = [[col] + df[col].values.tolist()
|
||||||
|
for col in df.columns]
|
||||||
|
# data['data']['columns'].extend([['x'] + df.index.astype('str').values.tolist()])
|
||||||
|
for col in df.columns:
|
||||||
|
if col in secondary_y:
|
||||||
|
data['data']['axes'][col] = 'y2'
|
||||||
|
else:
|
||||||
|
data['data']['axes'][col] = 'y'
|
||||||
|
if len(secondary_y) > 0:
|
||||||
|
data['axis']['y2']['show'] = True
|
||||||
|
|
||||||
|
if colors:
|
||||||
|
# repeat color palette if not long enough
|
||||||
|
colors = colors*math.ceil(len(df.columns)/len(colors))
|
||||||
|
color_data = {}
|
||||||
|
for col, color in zip(df.columns, colors):
|
||||||
|
color_data[col] = color
|
||||||
|
data['data']['colors'] = color_data
|
||||||
|
|
||||||
|
if x_axis_type == 'auto':
|
||||||
|
index_type = str(df.index.dtype)
|
||||||
|
|
||||||
|
if 'date' in index_type:
|
||||||
|
data['axis']['x']['type'] = 'timeseries'
|
||||||
|
data['axis']['x']['tick']['format'] = '%Y-%m-%d'
|
||||||
|
|
||||||
|
if 'object' in index_type or 'category' in index_type:
|
||||||
|
data['axis']['x']['type'] = 'category'
|
||||||
|
data['axis']['x']['tick']['culling'][
|
||||||
|
'max'] = x_axis_tick_culling
|
||||||
|
else:
|
||||||
|
if 'date' in x_axis_type or 'time' in x_axis_type:
|
||||||
|
data['axis']['x']['type'] = 'timeseries'
|
||||||
|
data['axis']['x']['tick']['format'] = '%Y-%m-%d'
|
||||||
|
|
||||||
|
if 'categor' in x_axis_type or 'str' in x_axis_type:
|
||||||
|
data['axis']['x']['type'] = 'category'
|
||||||
|
data['axis']['x']['tick']['culling'][
|
||||||
|
'max'] = x_axis_tick_culling
|
||||||
|
|
||||||
|
if xlim:
|
||||||
|
data['axis']['x']['min'] = xlim[0]
|
||||||
|
data['axis']['x']['max'] = xlim[1]
|
||||||
|
|
||||||
|
if ylim:
|
||||||
|
data['axis']['y']['min'] = ylim[0]
|
||||||
|
data['axis']['y']['max'] = ylim[1]
|
||||||
|
|
||||||
|
if stacked:
|
||||||
|
group = df.columns.values.tolist()
|
||||||
|
group.pop(-1)
|
||||||
|
group = [group]
|
||||||
|
|
||||||
|
if group:
|
||||||
|
data['data']['groups'] = group
|
||||||
|
|
||||||
|
if zoom:
|
||||||
|
data['zoom']['enabled'] = True
|
||||||
|
data['zoom']['rescale'] = True
|
||||||
|
|
||||||
|
if xregions:
|
||||||
|
data['regions'] = [{'axis': 'x', 'start': region[
|
||||||
|
0], 'end':region[1]} for region in xregions]
|
||||||
|
|
||||||
|
if yregions:
|
||||||
|
data['regions'] = [{'axis': 'y', 'start': region[
|
||||||
|
0], 'end':region[1]} for region in yregions]
|
||||||
|
|
||||||
|
if xlabels:
|
||||||
|
data['grid']['x']['lines'] = [
|
||||||
|
{'value': label[0], 'text': label[1]} for label in xlabels]
|
||||||
|
|
||||||
|
if ylabels:
|
||||||
|
data['grid']['y']['lines'] = [
|
||||||
|
{'value': label[0], 'text': label[1]} for label in ylabels]
|
||||||
|
|
||||||
|
if data_labels:
|
||||||
|
if data_labels == True:
|
||||||
|
data_labels = df.drop('x', axis=1).columns
|
||||||
|
if data_label_formats:
|
||||||
|
data['data']['labels'] = {}
|
||||||
|
for column in data_label_formats:
|
||||||
|
data['data']['labels'][column] = data_label_formats[column]
|
||||||
|
else:
|
||||||
|
data['data']['labels'] = True
|
||||||
|
|
||||||
|
return data
|
||||||
98
src/iplotter/chartist_plotter.py
Normal file
98
src/iplotter/chartist_plotter.py
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
from jinja2 import Template
|
||||||
|
from IPython.display import IFrame, HTML
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from .base_plotter import IPlotter
|
||||||
|
|
||||||
|
|
||||||
|
class ChartistPlotter(IPlotter):
|
||||||
|
"""
|
||||||
|
Class for creating chartist.js charts in ipython notebook
|
||||||
|
"""
|
||||||
|
|
||||||
|
head = '''
|
||||||
|
<!-- Load Chartist.js -->
|
||||||
|
<link rel='stylesheet' href='https://cdn.jsdelivr.net/chartist.js/latest/chartist.min.css'>
|
||||||
|
<script src='https://cdn.jsdelivr.net/chartist.js/latest/chartist.min.js'></script>
|
||||||
|
'''
|
||||||
|
|
||||||
|
template = '''
|
||||||
|
<div id={{div_id}} class='ct-chart' style='width: 100%; height: 100%' ></div>
|
||||||
|
<script>
|
||||||
|
new Chartist.{{chart_type}}('#{{div_id}}', {{data}}, {{options}});
|
||||||
|
</script>
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(ChartistPlotter, self).__init__()
|
||||||
|
|
||||||
|
def render(self, data, chart_type, options=None, div_id="chart", head=""):
|
||||||
|
'''
|
||||||
|
render the data in HTML template
|
||||||
|
'''
|
||||||
|
if not self.is_valid_name(div_id):
|
||||||
|
raise ValueError(
|
||||||
|
"Name {} is invalid. Only letters, numbers, '_', and '-' are permitted ".format(
|
||||||
|
div_id))
|
||||||
|
|
||||||
|
return Template(head + self.template).render(
|
||||||
|
div_id=div_id.replace(" ", "_"),
|
||||||
|
data=json.dumps(
|
||||||
|
data, indent=4).replace("'", "\\'").replace('"', "'"),
|
||||||
|
chart_type=chart_type,
|
||||||
|
options=json.dumps(
|
||||||
|
options, indent=4).replace("'", "\\'").replace('"', "'"))
|
||||||
|
|
||||||
|
def plot_and_save(self,
|
||||||
|
data,
|
||||||
|
chart_type,
|
||||||
|
options=None,
|
||||||
|
w=800,
|
||||||
|
h=420,
|
||||||
|
filename='chart',
|
||||||
|
overwrite=True):
|
||||||
|
'''
|
||||||
|
save the rendered html to a file and return an IFrame to display the plot in the notebook
|
||||||
|
'''
|
||||||
|
self.save(data, chart_type, options, filename, overwrite)
|
||||||
|
return IFrame(filename + '.html', w, h)
|
||||||
|
|
||||||
|
def plot(self, data, chart_type, options=None, w=800, h=420):
|
||||||
|
'''
|
||||||
|
output an iframe containing the plot in the notebook without saving
|
||||||
|
'''
|
||||||
|
return HTML(
|
||||||
|
self.iframe.format(
|
||||||
|
source=self.render(
|
||||||
|
data=data,
|
||||||
|
options=options,
|
||||||
|
chart_type=chart_type,
|
||||||
|
head=self.head),
|
||||||
|
w=w,
|
||||||
|
h=h))
|
||||||
|
|
||||||
|
def save(self,
|
||||||
|
data,
|
||||||
|
chart_type,
|
||||||
|
options=None,
|
||||||
|
filename='chart',
|
||||||
|
overwrite=True):
|
||||||
|
'''
|
||||||
|
save the rendered html to a file in the same directory as the notebook
|
||||||
|
'''
|
||||||
|
html = self.render(
|
||||||
|
data=data,
|
||||||
|
chart_type=chart_type,
|
||||||
|
options=options,
|
||||||
|
div_id=filename,
|
||||||
|
head=self.head)
|
||||||
|
|
||||||
|
if overwrite:
|
||||||
|
with open(filename.replace(" ", "_") + '.html', 'w') as f:
|
||||||
|
f.write(html)
|
||||||
|
else:
|
||||||
|
if not os.path.exists(filename.replace(" ", "_") + '.html'):
|
||||||
|
with open(filename.replace(" ", "_") + '.html', 'w') as f:
|
||||||
|
f.write(html)
|
||||||
|
else:
|
||||||
|
raise IOError('File Already Exists!')
|
||||||
115
src/iplotter/chartjs_plotter.py
Normal file
115
src/iplotter/chartjs_plotter.py
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
from jinja2 import Template
|
||||||
|
from IPython.display import IFrame, HTML
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from .base_plotter import IPlotter
|
||||||
|
|
||||||
|
|
||||||
|
class ChartJSPlotter(IPlotter):
|
||||||
|
"""
|
||||||
|
Class for creating charts.js charts in ipython notebook
|
||||||
|
"""
|
||||||
|
|
||||||
|
head = '''
|
||||||
|
<!-- Load Charts.js -->
|
||||||
|
<script src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.bundle.min.js'></script>
|
||||||
|
'''
|
||||||
|
|
||||||
|
template = '''
|
||||||
|
<canvas id='{{div_id}}'></canvas>
|
||||||
|
<script>
|
||||||
|
var ctx = document.getElementById('{{div_id}}').getContext('2d');
|
||||||
|
ctx.canvas.width = {{w}} - (.1 * {{w}});
|
||||||
|
ctx.canvas.height = {{h}} - (.15 * {{h}});
|
||||||
|
var myNewChart = new Chart(ctx,{ type: '{{chart_type}}', data: {{data}}, options: {{options}} });
|
||||||
|
</script>
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(ChartJSPlotter, self).__init__()
|
||||||
|
|
||||||
|
def render(self,
|
||||||
|
data,
|
||||||
|
chart_type,
|
||||||
|
options=None,
|
||||||
|
div_id="chart",
|
||||||
|
head="",
|
||||||
|
w=800,
|
||||||
|
h=420):
|
||||||
|
'''
|
||||||
|
render the data in HTML template
|
||||||
|
'''
|
||||||
|
if not self.is_valid_name(div_id):
|
||||||
|
raise ValueError(
|
||||||
|
"Name {} is invalid. Only letters, numbers, '_', and '-' are permitted ".format(
|
||||||
|
div_id))
|
||||||
|
|
||||||
|
return Template(head + self.template).render(
|
||||||
|
div_id=div_id.replace(" ", "_"),
|
||||||
|
data=json.dumps(
|
||||||
|
data, indent=4).replace("'", "\\'").replace('"', "'"),
|
||||||
|
chart_type=chart_type,
|
||||||
|
options=json.dumps(
|
||||||
|
options, indent=4).replace("'", "\\'").replace('"', "'"),
|
||||||
|
w=w,
|
||||||
|
h=h)
|
||||||
|
|
||||||
|
def plot_and_save(self,
|
||||||
|
data,
|
||||||
|
chart_type,
|
||||||
|
options=None,
|
||||||
|
w=800,
|
||||||
|
h=420,
|
||||||
|
filename='chart',
|
||||||
|
overwrite=True):
|
||||||
|
'''
|
||||||
|
save the rendered html to a file and return an IFrame to display the plot in the notebook
|
||||||
|
'''
|
||||||
|
self.save(data, chart_type, options, filename, w, h, overwrite)
|
||||||
|
return IFrame(filename + '.html', w, h)
|
||||||
|
|
||||||
|
def plot(self, data, chart_type, options=None, w=800, h=420):
|
||||||
|
'''
|
||||||
|
output an iframe containing the plot in the notebook without saving
|
||||||
|
'''
|
||||||
|
return HTML(
|
||||||
|
self.iframe.format(
|
||||||
|
source=self.render(
|
||||||
|
data=data,
|
||||||
|
chart_type=chart_type,
|
||||||
|
options=options,
|
||||||
|
head=self.head,
|
||||||
|
w=w,
|
||||||
|
h=h),
|
||||||
|
w=w,
|
||||||
|
h=h))
|
||||||
|
|
||||||
|
def save(self,
|
||||||
|
data,
|
||||||
|
chart_type,
|
||||||
|
options=None,
|
||||||
|
filename='chart',
|
||||||
|
w=800,
|
||||||
|
h=420,
|
||||||
|
overwrite=True):
|
||||||
|
'''
|
||||||
|
save the rendered html to a file in the same directory as the notebook
|
||||||
|
'''
|
||||||
|
html = self.render(
|
||||||
|
data=data,
|
||||||
|
chart_type=chart_type,
|
||||||
|
options=options,
|
||||||
|
div_id=filename,
|
||||||
|
head=self.head,
|
||||||
|
w=w,
|
||||||
|
h=h)
|
||||||
|
|
||||||
|
if overwrite:
|
||||||
|
with open(filename.replace(" ", "_") + '.html', 'w') as f:
|
||||||
|
f.write(html)
|
||||||
|
else:
|
||||||
|
if not os.path.exists(filename.replace(" ", "_") + '.html'):
|
||||||
|
with open(filename.replace(" ", "_") + '.html', 'w') as f:
|
||||||
|
f.write(html)
|
||||||
|
else:
|
||||||
|
raise IOError('File Already Exists!')
|
||||||
35
src/iplotter/export.py
Normal file
35
src/iplotter/export.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import time
|
||||||
|
from selenium import webdriver
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualBrowser(object):
|
||||||
|
"""Helper class for converting html charts to png"""
|
||||||
|
|
||||||
|
def __init__(self, driver=webdriver.Chrome):
|
||||||
|
super(VirtualBrowser, self).__init__()
|
||||||
|
self.driver = driver()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def save_as_png(self, filename, width=300, height=250, render_time=1):
|
||||||
|
'''
|
||||||
|
open saved html file in an virtual browser and save a screen shot to PNG format
|
||||||
|
'''
|
||||||
|
self.driver.set_window_size(width, height)
|
||||||
|
self.driver.get('file://{path}/{filename}'.format(
|
||||||
|
path=os.getcwd(), filename=filename + ".html"))
|
||||||
|
time.sleep(render_time)
|
||||||
|
self.driver.save_screenshot(filename + ".png")
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
self.driver.quit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def quit(self):
|
||||||
|
'''
|
||||||
|
shutdown virtual browser when finished
|
||||||
|
'''
|
||||||
|
self.driver.quit()
|
||||||
|
return True
|
||||||
124
src/iplotter/google_plotter.py
Normal file
124
src/iplotter/google_plotter.py
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
from jinja2 import Template
|
||||||
|
from IPython.display import IFrame, HTML
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from .base_plotter import IPlotter
|
||||||
|
|
||||||
|
|
||||||
|
class GCPlotter(IPlotter):
|
||||||
|
"""
|
||||||
|
Class for creating Google Charts in ipython notebook
|
||||||
|
"""
|
||||||
|
head = '''
|
||||||
|
<!-- Load Google Charts -->
|
||||||
|
<script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script>
|
||||||
|
'''
|
||||||
|
|
||||||
|
template = '''
|
||||||
|
<div id={{div_id}} style='width: 100%; height: 100%' ></div>
|
||||||
|
<script type='text/javascript'>
|
||||||
|
google.charts.load('current', {'packages':['{{ chart_package}}']});
|
||||||
|
google.charts.setOnLoadCallback(drawChart);
|
||||||
|
|
||||||
|
function drawChart() {
|
||||||
|
var data = google.visualization.arrayToDataTable({{data}}
|
||||||
|
);
|
||||||
|
|
||||||
|
var chart = new google.visualization.{{chart_type}}(document.getElementById('{{div_id}}'));
|
||||||
|
|
||||||
|
chart.draw(data, {{options}});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(GCPlotter, self).__init__()
|
||||||
|
|
||||||
|
def render(self,
|
||||||
|
data,
|
||||||
|
chart_type,
|
||||||
|
chart_package='corechart',
|
||||||
|
options=None,
|
||||||
|
div_id="chart",
|
||||||
|
head=""):
|
||||||
|
'''
|
||||||
|
render the data in HTML template
|
||||||
|
'''
|
||||||
|
if not self.is_valid_name(div_id):
|
||||||
|
raise ValueError(
|
||||||
|
"Name {} is invalid. Only letters, numbers, '_', and '-' are permitted ".format(
|
||||||
|
div_id))
|
||||||
|
|
||||||
|
return Template(head + self.template).render(
|
||||||
|
div_id=div_id.replace(" ", "_"),
|
||||||
|
data=json.dumps(
|
||||||
|
data, indent=4).replace("'", "\\'").replace('"', "'"),
|
||||||
|
chart_type=chart_type,
|
||||||
|
chart_package=chart_package,
|
||||||
|
options=json.dumps(
|
||||||
|
options, indent=4).replace("'", "\\'").replace('"', "'"))
|
||||||
|
|
||||||
|
def plot_and_save(self,
|
||||||
|
data,
|
||||||
|
chart_type,
|
||||||
|
chart_package='corechart',
|
||||||
|
options=None,
|
||||||
|
w=800,
|
||||||
|
h=420,
|
||||||
|
filename='chart',
|
||||||
|
overwrite=True):
|
||||||
|
'''
|
||||||
|
save the rendered html to a file and return an IFrame to display the plot in the notebook
|
||||||
|
'''
|
||||||
|
self.save(data, chart_type, chart_package, options, filename,
|
||||||
|
overwrite)
|
||||||
|
return IFrame(filename + '.html', w, h)
|
||||||
|
|
||||||
|
def plot(self,
|
||||||
|
data,
|
||||||
|
chart_type,
|
||||||
|
chart_package='corechart',
|
||||||
|
options=None,
|
||||||
|
w=800,
|
||||||
|
h=420):
|
||||||
|
'''
|
||||||
|
output an iframe containing the plot in the notebook without saving
|
||||||
|
'''
|
||||||
|
return HTML(
|
||||||
|
self.iframe.format(
|
||||||
|
source=self.render(
|
||||||
|
data=data,
|
||||||
|
options=options,
|
||||||
|
chart_type=chart_type,
|
||||||
|
chart_package=chart_package,
|
||||||
|
head=self.head),
|
||||||
|
w=w,
|
||||||
|
h=h))
|
||||||
|
|
||||||
|
def save(self,
|
||||||
|
data,
|
||||||
|
chart_type,
|
||||||
|
chart_package='corechart',
|
||||||
|
options=None,
|
||||||
|
filename='chart',
|
||||||
|
overwrite=True):
|
||||||
|
'''
|
||||||
|
save the rendered html to a file in the same directory as the notebook
|
||||||
|
'''
|
||||||
|
html = self.render(
|
||||||
|
data=data,
|
||||||
|
chart_type=chart_type,
|
||||||
|
chart_package=chart_package,
|
||||||
|
options=options,
|
||||||
|
div_id=filename,
|
||||||
|
head=self.head)
|
||||||
|
|
||||||
|
if overwrite:
|
||||||
|
with open(filename.replace(" ", "_") + '.html', 'w') as f:
|
||||||
|
f.write(html)
|
||||||
|
else:
|
||||||
|
if not os.path.exists(filename.replace(" ", "_") + '.html'):
|
||||||
|
with open(filename.replace(" ", "_") + '.html', 'w') as f:
|
||||||
|
f.write(html)
|
||||||
|
else:
|
||||||
|
raise IOError('File Already Exists!')
|
||||||
88
src/iplotter/plotly_plotter.py
Normal file
88
src/iplotter/plotly_plotter.py
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
from jinja2 import Template
|
||||||
|
from IPython.display import IFrame, HTML
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from .base_plotter import IPlotter
|
||||||
|
|
||||||
|
|
||||||
|
class PlotlyPlotter(IPlotter):
|
||||||
|
"""
|
||||||
|
Class for creating plotly.js charts in ipython notebook
|
||||||
|
"""
|
||||||
|
|
||||||
|
head = '''
|
||||||
|
<!-- Load d3.js and plotly.js -->
|
||||||
|
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js'></script>
|
||||||
|
<script src='https://code.jquery.com/jquery-2.1.4.min.js'></script>
|
||||||
|
<script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
|
||||||
|
'''
|
||||||
|
|
||||||
|
template = '''
|
||||||
|
<div id={{div_id}} style='width: 100%; height: 100%' ></div>
|
||||||
|
<script>
|
||||||
|
var {{div_id}} = document.getElementById('{{div_id}}');
|
||||||
|
Plotly.plot({{div_id}}, {{data}}, {{layout}});
|
||||||
|
</script>
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(PlotlyPlotter, self).__init__()
|
||||||
|
|
||||||
|
def render(self, data, layout=None, div_id="chart", head=""):
|
||||||
|
'''
|
||||||
|
render the data in HTML template
|
||||||
|
'''
|
||||||
|
if not self.is_valid_name(div_id):
|
||||||
|
raise ValueError(
|
||||||
|
"Name {} is invalid. Only letters, numbers, '_', and '-' are permitted ".format(
|
||||||
|
div_id))
|
||||||
|
|
||||||
|
return Template(head + self.template).render(
|
||||||
|
div_id=div_id.replace(" ", "_"),
|
||||||
|
data=json.dumps(
|
||||||
|
data, indent=4).replace("'", "\\'").replace('"', "'"),
|
||||||
|
layout=json.dumps(
|
||||||
|
layout, indent=4).replace("'", "\\'").replace('"', "'"))
|
||||||
|
|
||||||
|
def plot_and_save(self,
|
||||||
|
data,
|
||||||
|
layout=None,
|
||||||
|
w=800,
|
||||||
|
h=420,
|
||||||
|
filename='chart',
|
||||||
|
overwrite=True):
|
||||||
|
'''
|
||||||
|
save the rendered html to a file and return an IFrame to display the plot in the notebook
|
||||||
|
'''
|
||||||
|
self.save(data, layout, filename, overwrite)
|
||||||
|
return IFrame(filename + '.html', w, h)
|
||||||
|
|
||||||
|
def plot(self, data, layout=None, w=800, h=420):
|
||||||
|
'''
|
||||||
|
output an iframe containing the plot in the notebook without saving
|
||||||
|
'''
|
||||||
|
return HTML(
|
||||||
|
self.iframe.format(
|
||||||
|
source=self.render(
|
||||||
|
data=data,
|
||||||
|
layout=layout,
|
||||||
|
head=self.head, ),
|
||||||
|
w=w,
|
||||||
|
h=h))
|
||||||
|
|
||||||
|
def save(self, data, layout=None, filename='chart', overwrite=True):
|
||||||
|
'''
|
||||||
|
save the rendered html to a file in the same directory as the notebook
|
||||||
|
'''
|
||||||
|
html = self.render(
|
||||||
|
data=data, layout=layout, div_id=filename, head=self.head)
|
||||||
|
|
||||||
|
if overwrite:
|
||||||
|
with open(filename.replace(" ", "_") + '.html', 'w') as f:
|
||||||
|
f.write(html)
|
||||||
|
else:
|
||||||
|
if not os.path.exists(filename.replace(" ", "_") + '.html'):
|
||||||
|
with open(filename.replace(" ", "_") + '.html', 'w') as f:
|
||||||
|
f.write(html)
|
||||||
|
else:
|
||||||
|
raise IOError('File Already Exists!')
|
||||||
Loading…
Add table
Add a link
Reference in a new issue