Compare commits
10 commits
e55b144771
...
3441570a5a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3441570a5a | ||
|
|
5b6a4d52ef | ||
|
|
2e9cdef714 | ||
|
|
96cc6abfa5 | ||
|
|
cf9c0abb7d | ||
|
|
b1a74737b9 | ||
|
|
3bd6c48120 | ||
|
|
06bf1fd582 | ||
|
|
e79f459534 | ||
|
|
536afe563e |
16 changed files with 5907 additions and 4606 deletions
|
|
@ -1,9 +1,7 @@
|
|||
flask
|
||||
flask-cors
|
||||
flask-restplus
|
||||
gunicorn
|
||||
markdown
|
||||
nltk
|
||||
pandas
|
||||
textblob
|
||||
-e git+https://github.com/WaylonWalker/iplotter.git@master#egg=iplotter
|
||||
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!')
|
||||
|
|
@ -14,20 +14,16 @@ from collections import Counter
|
|||
from flask import Flask
|
||||
from flask import request, render_template, make_response, jsonify, Blueprint, url_for
|
||||
from flask_restplus import Resource, Api, fields, reqparse
|
||||
from flask_cors import CORS, cross_origin
|
||||
import markdown
|
||||
from textblob import TextBlob
|
||||
|
||||
import settings
|
||||
import etl
|
||||
import palettes as pal
|
||||
|
||||
from iplotter import C3Plotter
|
||||
c3 = C3Plotter()
|
||||
|
||||
app = Flask(__name__)
|
||||
# disq = Disqus(app)
|
||||
CORS(app)
|
||||
api_blueprint = Blueprint('api', __name__, url_prefix='/api')
|
||||
api = Api(api_blueprint, title='pyDataVizday api',
|
||||
default='pyDataVizDay',
|
||||
|
|
|
|||
|
|
@ -46,12 +46,6 @@ pre {
|
|||
}
|
||||
|
||||
|
||||
img.blur {
|
||||
width:367;
|
||||
height:459px;
|
||||
-webkit-filter: blur(10px);
|
||||
filter: blur(10px);
|
||||
}
|
||||
|
||||
/*@keyframes fadein{
|
||||
0% { opacity:0; }
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -14,7 +14,9 @@
|
|||
<div id="bottom-image" style='min-height: 1200px'>
|
||||
<div id="middle-image" style='min-height: 1200px'>
|
||||
<div id="top-image" style='min-height: 1200px'>
|
||||
<div class='notebook-container' style='float:center; max-width: 800px; margin-left:auto; margin-right:auto; background-color:white' >
|
||||
<div class='notebook-container' style='float:center; max-width: 800px; margin-left:auto; margin-right:auto; background-color:white; color:black; padding:10px;' >
|
||||
|
||||
<div id='buttons' style='color:black'></div>
|
||||
{{ body | safe }}
|
||||
<div id="disqus_thread"></div>
|
||||
</div>
|
||||
|
|
@ -22,7 +24,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var button_html = '<button id="output-html-btn">toggle tables</button> <button id="show-code-btn">toggle code</button>'
|
||||
|
||||
var button_html = '<button id="output-html-btn">toggle data tables</button> <button id="show-code-btn">toggle code</button>'
|
||||
|
||||
$('#buttons').html(button_html)
|
||||
$('.prompt').hide()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/sunburst.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||||
<script>hljs.initHighlightingOnLoad();</script>
|
||||
<link rel='stylesheet' type='text/css' href='static/css/custom.css?v=4'>
|
||||
<link rel='stylesheet' type='text/css' href='static/css/custom.css?v=5'>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ This site is being used for a capability review of various visualization tools,
|
|||
|
||||
## Pages
|
||||
|
||||
### [Exploritory](/exploritory)
|
||||
### [Exploritory](/Exploritory)
|
||||
|
||||
This page is python's bread and butter in data science today. It is able to pull in nearly any type of data set imaginable, transorm, aggregate, and plot very quickly. This page was built using a [jupyter](jupyter.org) notebook. This a very powerful tool that allows us to do interactive reproducible data science with all of our data, agregations, visualizations, and slides all in one place.
|
||||
|
||||
|
|
@ -29,23 +29,50 @@ app = Flask(__name__)
|
|||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""
|
||||
Index page built by converting index.md to html, then inserting that into the index.html template
|
||||
|
||||
go to your base_url + / to view this page
|
||||
"""
|
||||
body = markdown.markdown(render_template('index.md'),
|
||||
extensions=['markdown.extensions.fenced_code'])
|
||||
return render_template('index.html', body=body)
|
||||
|
||||
if __name__ == '__main__':
|
||||
# if this is ran as the main program run the app
|
||||
# if the program is imported it will not run allowing us to reuse some components in other projects easily.
|
||||
app.run()
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### Javascript
|
||||
|
||||
For the Enthusiast page we need to implement some javascript in order to update the page dynamically without a page refresh We need to implement a bit of javascript that will talk to our api and change the information on the page. Currently javascript is the only client side scripting language for the browser. There are a number of languages that can be transpiled into browser ready javascript, but javascript is the only language that run in the browser.
|
||||
|
||||
I believe that modern (ES6) is a much better language. It looks much closer to python than older javascript. Currently ES6 does not run in many browsers natively and needs to be compiled down to browser ready javascript. I am trying to keep this project very simple, and did not want a complicated build tool chain. If you are using javascript more often, or have a more complicated use case do yourself a favor and look into setting up build tools that compile ES6 to browser ready javascript. For this reason I have chosen to use jquery. It provides a simple interface into the features that I need, and runs natively in the browser.
|
||||
|
||||
```javascript
|
||||
$('#top').change(function(){update_kpi()})
|
||||
// ties the update function to the top div, and triggers an update any time there is a change in the top div that contains our form data
|
||||
|
||||
function update_kpi()
|
||||
{
|
||||
var url = '/api/score_timeseries?' + $('top').val()
|
||||
var kpi_data = $.get(url); // gets the json response data from our python api
|
||||
kpi_data.done(function(results) // waits for the response to come back from python
|
||||
{
|
||||
$('#gross').html(results.responseJSON.gross) // updates the gross kpi that sits in the gross div
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### [Slides](/slides)
|
||||
|
||||
Since we are serving up a web app we can embrace the power and flexibility that this gives us. The slides for this event will be served along side of the visualization. This will make these slides available anywhere you have a connection to the web. These slides were written in markdown, and are very simple to write.
|
||||
<div class="col6 center"><br></div>
|
||||
#### Example Markdown Code for Slides
|
||||
This file wil be placed into a slides.html template that has been setup to render reveal slides from markdown. The reveal.js web page has examples of how to setup the html. It will render typical markdown code as html, and will create a new slide at every ```----``` and a new fragment at every ```---```
|
||||
``` markdown
|
||||
|
||||
# pyDataVizDay
|
||||
*a python implementation for Data Viz Day*
|
||||
|
||||
|
|
@ -62,7 +89,6 @@ Since we are serving up a web app we can embrace the power and flexibility that
|
|||
* Tools Used
|
||||
* Other Considerations
|
||||
* Pros/Cons
|
||||
|
||||
```
|
||||
|
||||
### [api](/api/doc/#/pyDataVizDay)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
# pyDataVizDay
|
||||
---
|
||||
*a python implementation for Data Viz Day*
|
||||
|
||||

|
||||
|
|
@ -6,10 +7,9 @@
|
|||
----
|
||||
|
||||
# Agenda
|
||||
|
||||
1. Viz Walk (3 Views)
|
||||
---
|
||||
1. Viz Walk (2 Views)
|
||||
1. Full Web App
|
||||
* Simple Web App
|
||||
* Exploritory Notebook
|
||||
* Tools Used
|
||||
* Other Considerations
|
||||
|
|
@ -17,20 +17,18 @@
|
|||
|
||||
----
|
||||
|
||||
## About Me
|
||||
|
||||
# About Me
|
||||
---
|
||||

|
||||
|
||||
Waylon Walker
|
||||
|
||||
Product Engineering
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
# Open The Viz
|
||||
|
||||
---
|
||||
[pydatavizday.herokuapp.com](pydatavizday.herokuapp.com)
|
||||
|
||||
---
|
||||
|
|
@ -41,27 +39,63 @@ Product Engineering
|
|||
|
||||
----
|
||||
|
||||
## Stack for this viz
|
||||
# External Resources
|
||||
|
||||
* Python
|
||||
* pandas
|
||||
* flask
|
||||
* javascript
|
||||
* C3
|
||||
* reveal
|
||||
* jquery
|
||||
* jqcloud
|
||||
* HTML
|
||||
* Bootstrap
|
||||
---
|
||||
|
||||
### Enthusiast
|
||||
<div class='container'>
|
||||
<div class='row' style='text-align:left; margin:50px'>
|
||||
<div class='col-sm-4' style='margin:0px'>
|
||||
|
||||
<h4>Python</h4>
|
||||
<ul>
|
||||
<li>pandas</li>
|
||||
<li>flask</li>
|
||||
<li>flask_restplus</li>
|
||||
<li>markdown</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class='col-sm-4' style='margin:0px'>
|
||||
|
||||
<h4>javascript</h4>
|
||||
<ul>
|
||||
<li>C3</li>
|
||||
<li>reveal</li>
|
||||
<li>jquery</li>
|
||||
<li>jqcloud</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class='col-sm-4' style='margin:0px'>
|
||||
|
||||
<h4>HTML</h4>
|
||||
<ul>
|
||||
<li>Bootstrap</li>
|
||||
<ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Exploritory
|
||||
|
||||
Python
|
||||
* jupyter
|
||||
* seaborn
|
||||
* numpy
|
||||
* pandas
|
||||
* sci-kit learn
|
||||
|
||||
----
|
||||
|
||||
## Other Considerations
|
||||
|
||||
---
|
||||
* Jupyter Notebooks
|
||||
* Jupyter Dashboards
|
||||
* DASH (just released in mid JUNE)
|
||||
* Bokeh
|
||||
* Data shader
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -93,7 +127,7 @@ Product Engineering
|
|||
----
|
||||
|
||||
## Pros of Python
|
||||
|
||||
---
|
||||
* Fast High Level Data Science
|
||||
* reusable
|
||||
* Powerful Web stack
|
||||
|
|
@ -110,20 +144,17 @@ python has a vast ecosysytem for data wrangling
|
|||
|
||||
``` python
|
||||
import pandas as pd
|
||||
import glob, os
|
||||
|
||||
path = "C:/reports"
|
||||
files = glob.glob(path + os.sep + '*_report.csv*')
|
||||
raw_example = pd.read_csv('example_data.csv')
|
||||
|
||||
frames = []
|
||||
for file in files:
|
||||
frames.append(pd.read_csv(file))
|
||||
|
||||
all_reports = (pd.concat(frames)
|
||||
.dropna()
|
||||
.query('DIVISION == ACCOUNTING')
|
||||
)
|
||||
example = (raw_example
|
||||
.groupby(['Date'])
|
||||
.sum()
|
||||
.resample('m')
|
||||
.fillna(0)
|
||||
)
|
||||
|
||||
example.plot()
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -144,19 +175,23 @@ data.update()
|
|||
|
||||
### Testing
|
||||
|
||||
The ability to easily reuse code/datasets/plot gives us the ability to spend time making large projects more .
|
||||
|
||||
Well written tests give us the confidence to push to production without manually spending our own time testing each feature of our end product.
|
||||
|
||||
``` python
|
||||
import unittest
|
||||
import etl
|
||||
|
||||
class Testdata(unittest.TestCase):
|
||||
"""
|
||||
Test suite for my dataframe data
|
||||
"""
|
||||
def setUp(self):
|
||||
self.data = etl.Data()
|
||||
important_cols = ['DATE', 'PRODUCT', 'QTY']
|
||||
|
||||
def test_cols(self):
|
||||
for col in important_cols:
|
||||
self.assertLess(len(data[data[col].isnull()]), 0, msg=f'column {col} has unexpected null values')
|
||||
self.assertIn(col, data.columns.tolist(), msg=f'column {col} is missing - check the /data/raw/shipments.csv file to ensure logistics has not changed the data format')
|
||||
self.assertIn(col, sdata.columns.tolist())
|
||||
|
||||
```
|
||||
|
||||
|
|
@ -195,10 +230,9 @@ else:
|
|||
----
|
||||
|
||||
## Cons on python
|
||||
|
||||
---
|
||||
* Code
|
||||
* Interactivity
|
||||
* Speed
|
||||
* ML research
|
||||
|
||||
---
|
||||
|
|
@ -212,12 +246,6 @@ else:
|
|||
|
||||
---
|
||||
|
||||
### Slow
|
||||
|
||||
*slow runtime compared to statically typed languages (c, java)*
|
||||
|
||||
---
|
||||
|
||||
### Latest ML aglorithms are typically developed in R
|
||||
|
||||
---
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue