Source code for pandastable.plotting

#!/usr/bin/env python
"""
    Module for pandastable plotting classes .

    Created Jan 2014
    Copyright (C) Damien Farrell

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""

from __future__ import absolute_import, division, print_function
try:
    from tkinter import *
    from tkinter.ttk import *
except:
    from Tkinter import *
    from ttk import *
import types, time
import numpy as np
import pandas as pd
try:
    from pandas import plotting
except ImportError:
    from pandas.tools import plotting
import matplotlib as mpl
#mpl.use("TkAgg")
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.lines import Line2D
import matplotlib.transforms as mtrans
from collections import OrderedDict
import operator
from .dialogs import *
from . import util, images
import logging

colormaps = sorted(m for m in plt.cm.datad if not m.endswith("_r"))
markers = ['','o','.','^','v','>','<','s','+','x','p','d','h','*']
linestyles = ['-','--','-.',':','steps']
#valid kwds for each plot method
valid_kwds = {'line': ['alpha', 'colormap', 'grid', 'legend', 'linestyle','ms',
                  'linewidth', 'marker', 'subplots', 'rot', 'logx', 'logy',
                  'sharex','sharey', 'kind'],
            'scatter': ['alpha', 'grid', 'linewidth', 'marker', 'subplots', 'ms',
                    'legend', 'colormap','sharex','sharey', 'logx', 'logy', 'use_index',
                    'clrcol', 'cscale','colorbar','bw','labelcol','pointsizes'],
            'pie': ['colormap','legend'],
            'hexbin': ['alpha', 'colormap', 'grid', 'linewidth','subplots'],
            'bootstrap': ['grid'],
            'bar': ['alpha', 'colormap', 'grid', 'legend', 'linewidth', 'subplots',
                    'sharex','sharey', 'logy', 'stacked', 'rot', 'kind', 'edgecolor'],
            'barh': ['alpha', 'colormap', 'grid', 'legend', 'linewidth', 'subplots',
                    'sharex','sharey','stacked', 'rot', 'kind', 'logx', 'edgecolor'],
            'histogram': ['alpha', 'linewidth','grid','stacked','subplots','colormap',
                     'sharex','sharey','rot','bins', 'logx', 'logy', 'legend', 'edgecolor'],
            'heatmap': ['colormap','colorbar','rot', 'linewidth','linestyle',
                        'subplots','rot','cscale','bw','alpha','sharex','sharey'],
            'area': ['alpha','colormap','grid','linewidth','legend','stacked',
                     'kind','rot','logx','sharex','sharey','subplots'],
            'density': ['alpha', 'colormap', 'grid', 'legend', 'linestyle',
                         'linewidth', 'marker', 'subplots', 'rot', 'kind'],
            'boxplot': ['rot','grid','logy','colormap','alpha','linewidth','legend',
                        'subplots','edgecolor','sharex','sharey'],
            'violinplot': ['rot','grid','logy','colormap','alpha','linewidth','legend',
                        'subplots','edgecolor','sharex','sharey'],
            'dotplot': ['marker','edgecolor','linewidth','colormap','alpha','legend',
                        'subplots','ms','bw','logy','sharex','sharey'],
            'scatter_matrix':['alpha', 'linewidth', 'marker', 'grid', 's'],
            'contour': ['linewidth','colormap','alpha','subplots'],
            'imshow': ['colormap','alpha'],
            'venn': ['colormap','alpha'],
            'radviz': ['linewidth','marker','edgecolor','s','colormap','alpha']
            }

[docs]def get_defaults(name): if name == 'mplopts': return MPLBaseOptions().opts elif name == 'mplopts3d': return MPL3DOptions().opts elif name == 'labelopts': return AnnotationOptions().opts
[docs]class PlotViewer(Frame): """Provides a frame for figure canvas and MPL settings. Args: table: parent table, required parent: parent tkinter frame layout: 'horizontal' or 'vertical' """ def __init__(self, table, parent=None, showoptions=True): self.parent = parent self.table = table if table is not None: self.table.pf = self #opaque ref #self.mode = '2d' self.showoptions = showoptions self.multiviews = False if self.parent != None: Frame.__init__(self, parent) self.main = self.master else: self.main = Toplevel() self.master = self.main self.main.title('Plot Viewer') self.main.protocol("WM_DELETE_WINDOW", self.close) g = '800x700+900+200' self.main.geometry(g) #self.toolslayout = layout #if layout == 'horizontal': self.orient = VERTICAL #else: # self.orient = HORIZONTAL self.mplopts = MPLBaseOptions(parent=self) self.mplopts3d = MPL3DOptions(parent=self) self.labelopts = AnnotationOptions(parent=self) self.layoutopts = PlotLayoutOptions(parent=self) self.gridaxes = {} #reset style if it been set globally self.style = None self.setupGUI() self.updateStyle() self.currentdir = os.path.expanduser('~') return
[docs] def setupGUI(self): """Add GUI elements""" #import tkinter as tk #self.m = PanedWindow(self.main, orient=self.orient) self.m = Frame(self.main) self.m.pack(fill=BOTH,expand=1) #frame for figure self.plotfr = Frame(self.m) #add it to the panedwindow self.fig, self.canvas = addFigure(self.plotfr) self.ax = self.fig.add_subplot(111) #self.m.add(self.plotfr, weight=12) self.plotfr.pack(side=TOP,fill=BOTH, expand=1) #frame for controls self.ctrlfr = Frame(self.main) #self.m.add(self.ctrlfr, weight=4) self.ctrlfr.pack(side=BOTTOM,fill=BOTH) #button frame bf = Frame(self.ctrlfr, padding=2) bf.pack(side=TOP,fill=BOTH) side = LEFT #add button toolbar addButton(bf, 'Plot', self.replot, images.plot(), 'plot current data', side=side, compound="left", width=16) addButton(bf, 'Apply Options', self.updatePlot, images.refresh(), 'refresh plot with current options', side=side, width=20) addButton(bf, 'Zoom Out', lambda: self.zoom(False), images.zoom_out(), 'zoom out', side=side) addButton(bf, 'Zoom In', self.zoom, images.zoom_in(), 'zoom in', side=side) addButton(bf, 'Clear', self.clear, images.plot_clear(), 'clear plot', side=side) addButton(bf, 'Save', self.savePlot, images.save(), 'save plot', side=side) #dicts to store global options, can be saved with projects self.globalvars = {} self.globalopts = OrderedDict({ 'dpi': 80, 'grid layout': False,'3D plot':False }) from functools import partial for n in self.globalopts: val = self.globalopts[n] if type(val) is bool: v = self.globalvars[n] = BooleanVar() v.set(val) b = Checkbutton(bf,text=n, variable=v, command=partial(self.setGlobalOption, n)) else: v = self.globalvars[n] = IntVar() v.set(val) Label(bf, text=n).pack(side=LEFT,fill=X,padx=2) b = Entry(bf,textvariable=v, width=5) v.trace("w", partial(self.setGlobalOption, n)) b.pack(side=LEFT,padx=2) addButton(bf, 'Hide', self.toggle_options, images.prefs(), 'show/hide plot options', side=RIGHT) self.addWidgets() #def onpick(event): # print(event) #self.fig.canvas.mpl_connect('pick_event', onpick) #self.fig.canvas.mpl_connect('button_release_event', onpick) from . import handlers dr = handlers.DragHandler(self) dr.connect() return
[docs] def addWidgets(self): """Add option widgets to control panel""" self.nb = Notebook(self.ctrlfr, height=210) if self.showoptions == 1: self.nb.pack(side=TOP,fill=BOTH,expand=0) #add plotter tool dialogs w1 = self.mplopts.showDialog(self.nb) self.nb.add(w1, text='Base Options', sticky='news') #reload tkvars again from stored kwds variable self.mplopts.updateFromDict() self.styleopts = ExtraOptions(parent=self) self.animateopts = AnimateOptions(parent=self) w3 = self.labelopts.showDialog(self.nb) self.nb.add(w3, text='Annotation', sticky='news') self.labelopts.updateFromDict() w4 = self.layoutopts.showDialog(self.nb) self.nb.add(w4, text='Grid Layout', sticky='news') w2 = self.styleopts.showDialog(self.nb) self.nb.add(w2, text='Other Options', sticky='news') w5 = self.mplopts3d.showDialog(self.nb) self.nb.add(w5, text='3D Options', sticky='news') self.mplopts3d.updateFromDict() w6 = self.animateopts.showDialog(self.nb) self.nb.add(w6, text='Animate', sticky='news') return
[docs] def setGlobalOption(self, name='', *args): """Set global value from widgets""" try: self.globalopts[name] = self.globalvars[name].get() #print (self.globalopts) except: logging.error("Exception occurred", exc_info=True) return
[docs] def updateWidgets(self): """Set global widgets from values""" for n in self.globalopts: self.globalvars[n].set(self.globalopts[n])
[docs] def setOption(self, option, value): basewidgets = self.mplopts.tkvars labelwidgets = self.labelopts.tkvars try: basewidgets[option].set(value) except: labelwidgets[option].set(value) finally: pass return
[docs] def replot(self, data=None): """Re-plot using current parent table selection. Args: data: set current dataframe, otherwise use current table selection""" #print (self.table.getSelectedRows()) if data is None: self.data = self.table.getSelectedDataFrame() else: self.data = data self.updateStyle() self.applyPlotoptions() self.plotCurrent() return
[docs] def applyPlotoptions(self): """Apply the current plotter/options""" self.mplopts.applyOptions() self.mplopts3d.applyOptions() self.labelopts.applyOptions() self.styleopts.applyOptions() mpl.rcParams['savefig.dpi'] = self.globalopts['dpi'] #self.dpivar.get() return
[docs] def updatePlot(self): """Update the current plot with new options""" self.applyPlotoptions() self.plotCurrent() return
[docs] def plotCurrent(self, redraw=True): """Plot the current data""" layout = self.globalopts['grid layout'] gridmode = self.layoutopts.modevar.get() plot3d = self.globalopts['3D plot'] self._initFigure() if layout == 1 and gridmode == 'multiviews': self.plotMultiViews() elif layout == 1 and gridmode == 'splitdata': self.plotSplitData() elif plot3d == 1: self.plot3D(redraw=redraw) else: self.plot2D(redraw=redraw) return
[docs] def zoom(self, zoomin=True): """Zoom in/out to plot by changing size of elements""" if zoomin == False: val=-1.0 else: val=1.0 if len(self.mplopts.kwds) == 0: return self.mplopts.increment('linewidth',val/10) self.mplopts.increment('ms',val) self.mplopts.increment('fontsize',val) self.replot() return
[docs] def clear(self): """Clear plot""" self.fig.clear() self.ax = None self.canvas.draw() self.table.plotted=None self.gridaxes = {} return
def _checkNumeric(self, df): """Get only numeric data that can be plotted""" #x = df.convert_objects()._get_numeric_data() x = df.apply( lambda x: pd.to_numeric(x,errors='ignore',downcast='float') ) if x.empty==True: return False def _initFigure(self): """Clear figure or add a new axis to existing layout""" from matplotlib.gridspec import GridSpec layout = self.globalopts['grid layout'] plot3d = self.globalopts['3D plot'] #plot layout should be tracked by plotlayoutoptions gl = self.layoutopts if plot3d == 1: proj = '3d' else: proj = None if layout == 0: #default layout is just a single axis self.fig.clear() self.gridaxes={} self.ax = self.fig.add_subplot(111, projection=proj) else: #get grid layout from layout opt rows = gl.rows cols = gl.cols x = gl.selectedrows y = gl.selectedcols r=min(x); c=min(y) rowspan = gl.rowspan colspan = gl.colspan top = .92 bottom = .1 #print (rows,cols,r,c) #print (rowspan,colspan) ws = cols/10-.05 hs = rows/10-.05 gs = self.gridspec = GridSpec(rows,cols,top=top,bottom=bottom, left=0.1,right=0.9,wspace=ws,hspace=hs) name = str(r+1)+','+str(c+1) if name in self.gridaxes: ax = self.gridaxes[name] if ax in self.fig.axes: self.fig.delaxes(ax) self.ax = self.fig.add_subplot(gs[r:r+rowspan,c:c+colspan], projection=proj) self.gridaxes[name] = self.ax #update the axes widget self.layoutopts.updateAxesList() return
[docs] def removeSubplot(self): """Remove a specific axis from the grid layout""" axname = self.layoutopts.axeslistvar.get() ax = self.gridaxes[axname] if ax in self.fig.axes: self.fig.delaxes(ax) del self.gridaxes[axname] self.canvas.show() self.layoutopts.updateAxesList() self.layoutopts.axeslistvar.set('') return
[docs] def setSubplotTitle(self): """Set a subplot title if using grid layout""" axname = self.layoutopts.axeslistvar.get() if not axname in self.gridaxes: return ax = self.gridaxes[axname] label = simpledialog.askstring("Subplot title", "Title:",initialvalue='', parent=self.parent) if label: ax.set_title(label) self.canvas.show() return
[docs] def plotMultiViews(self, plot_types=['bar','scatter']): """Plot multiple views of the same data in a grid""" #plot_types=['bar','scatter','histogram','boxplot'] #self._initFigure() self.fig.clear() gs = self.gridspec gl = self.layoutopts plot_types = getListBoxSelection(gl.plottypeslistbox) kwds = self.mplopts.kwds rows = gl.rows cols = gl.cols c=0; i=0 for r in range(0,rows): for c in range(0,cols): if i>=len(plot_types): break self.ax = self.fig.add_subplot(gs[r:r+1,c:c+1]) #print (self.ax) kwds['kind'] = plot_types[i] kwds['legend'] = False self.plot2D(redraw=False) i+=1 #legend - put this as a normal option.. handles, labels = self.ax.get_legend_handles_labels() self.fig.legend(handles, labels) self.canvas.draw() return
[docs] def plotSplitData(self): """Splits selected data up into multiple plots in a grid""" self.fig.clear() gs = self.gridspec gl = self.layoutopts kwds = self.mplopts.kwds kwds['legend'] = False rows = gl.rows cols = gl.cols c=0; i=0 data = self.data n = rows * cols chunks = np.array_split(data, n) proj=None plot3d = self.globalopts['3D plot'] if plot3d == True: proj='3d' for r in range(0,rows): for c in range(0,cols): self.data = chunks[i] self.ax = self.fig.add_subplot(gs[r:r+1,c:c+1], projection=proj) if plot3d == True: self.plot3D() else: self.plot2D(redraw=False) i+=1 handles, labels = self.ax.get_legend_handles_labels() self.fig.legend(handles, labels) self.canvas.draw() return
[docs] def checkColumnNames(self, cols): """Check length of column names""" from textwrap import fill try: cols = [fill(l, 25) for l in cols] except: logging.error("Exception occurred", exc_info=True) return cols
[docs] def plot2D(self, redraw=True): """Plot method for current data. Relies on pandas plot functionality if possible. There is some temporary code here to make sure only the valid plot options are passed for each plot kind.""" if not hasattr(self, 'data'): return data = self.data data.columns = self.checkColumnNames(data.columns) #print (data) #get all options from the mpl options object kwds = self.mplopts.kwds lkwds = self.labelopts.kwds.copy() kind = kwds['kind'] by = kwds['by'] by2 = kwds['by2'] errorbars = kwds['errorbars'] useindex = kwds['use_index'] bw = kwds['bw'] #print (kwds) if self._checkNumeric(data) == False and kind != 'venn': self.showWarning('no numeric data to plot') return kwds['edgecolor'] = 'black' #valid kwd args for this plot type kwargs = dict((k, kwds[k]) for k in valid_kwds[kind] if k in kwds) #initialise the figure #self._initFigure() ax = self.ax if by != '': #groupby needs to be handled per group so we can create the axes #for our figure and add them outside the pandas logic if by not in data.columns: self.showWarning('the grouping column must be in selected data') return if by2 != '' and by2 in data.columns: by = [by,by2] g = data.groupby(by) if kwargs['subplots'] == True: i=1 if len(g) > 30: self.showWarning('%s is too many subplots' %len(g)) return size = len(g) nrows = int(round(np.sqrt(size),0)) ncols = int(np.ceil(size/nrows)) self.ax.set_visible(False) del kwargs['subplots'] for n,df in g: if ncols==1 and nrows==1: ax = self.fig.add_subplot(111) self.ax.set_visible(True) else: ax = self.fig.add_subplot(nrows,ncols,i) kwargs['legend'] = False #remove axis legends d = df.drop(by,axis=1) #remove grouping columns axs = self._doplot(d, ax, kind, False, errorbars, useindex, bw=bw, yerr=None, kwargs=kwargs) ax.set_title(n) handles, labels = ax.get_legend_handles_labels() i+=1 if 'sharey' in kwargs and kwargs['sharey'] == True: self.autoscale() if 'sharex' in kwargs and kwargs['sharex'] == True: self.autoscale('x') self.fig.legend(handles, labels, loc='center right', #bbox_to_anchor=(0.9, 0), bbox_transform=self.fig.transFigure ) axs = self.fig.get_axes() else: #single plot grouped only apply to some plot kinds #the remainder are not supported axs = self.ax labels = []; handles=[] cmap = plt.cm.get_cmap(kwargs['colormap']) #handle as pivoted data for some line, bar if kind in ['line','bar','barh']: df = pd.pivot_table(data,index=by) errs = data.groupby(by).std() self._doplot(df, axs, kind, False, errorbars, useindex=None, yerr=errs, bw=bw, kwargs=kwargs) elif kind == 'scatter': #we plot multiple groups and series in different colors #this logic could be placed in the scatter method? d = data.drop(by,1) d = d._get_numeric_data() xcol = d.columns[0] ycols = d.columns[1:] c=0 legnames = [] handles = [] slen = len(g)*len(ycols) clrs = [cmap(float(i)/slen) for i in range(slen)] for n, df in g: for y in ycols: kwargs['color'] = clrs[c] currax, sc = self.scatter(df[[xcol,y]], ax=axs, **kwargs) if type(n) is tuple: n = ','.join(n) legnames.append(','.join([n,y])) handles.append(sc[0]) c+=1 if kwargs['legend'] == True: if slen>6: lc = int(np.round(slen/10)) else: lc = 1 axs.legend([]) axs.legend(handles, legnames, ncol=lc) else: self.showWarning('single grouped plots not supported for %s\n' 'try using multiple subplots' %kind) else: #non-grouped plot try: axs = self._doplot(data, ax, kind, kwds['subplots'], errorbars, useindex, bw=bw, yerr=None, kwargs=kwargs) except Exception as e: self.showWarning(e) logging.error("Exception occurred", exc_info=True) return #set options general for all plot types #annotation optons are separate lkwds.update(kwds) #table = lkwds['table'] '''if table == True: #from pandas.tools.plotting import table from pandas.plotting import table if self.table.child != None: tabledata = self.table.child.model.df table(axs, np.round(tabledata, 2), loc='upper left', colWidths=[0.1 for i in tabledata.columns])''' self.setFigureOptions(axs, lkwds) scf = 12/kwds['fontsize'] try: self.fig.tight_layout() self.fig.subplots_adjust(top=0.9) if by != '': self.fig.subplots_adjust(right=0.9) except: self.fig.subplots_adjust(left=0.1, right=0.9, top=0.89, bottom=0.1, hspace=.4/scf, wspace=.2/scf) print ('tight_layout failed') #redraw annotations self.labelopts.redraw() if self.style == 'dark_background': self.fig.set_facecolor('black') else: self.fig.set_facecolor('white') if redraw == True: self.canvas.draw() return
[docs] def setFigureOptions(self, axs, kwds): """Set axis wide options such as ylabels, title""" if type(axs) is np.ndarray: self.ax = axs.flat[0] elif type(axs) is list: self.ax = axs[0] self.fig.suptitle(kwds['title'], fontsize=kwds['fontsize']*1.2) layout = self.globalopts['grid layout'] if layout == 0: for ax in self.fig.axes: self.setAxisLabels(ax, kwds) else: self.setAxisLabels(self.ax, kwds) return
[docs] def setAxisLabels(self, ax, kwds): """Set a plots axis labels""" if kwds['xlabel'] != '': ax.set_xlabel(kwds['xlabel']) if kwds['ylabel'] != '': ax.set_ylabel(kwds['ylabel']) ax.xaxis.set_visible(kwds['showxlabels']) ax.yaxis.set_visible(kwds['showylabels']) try: ax.tick_params(labelrotation=kwds['rot']) except: logging.error("Exception occurred", exc_info=True) return
[docs] def autoscale(self, axis='y'): """Set all subplots to limits of largest range""" l=None u=None for ax in self.fig.axes: if axis=='y': a, b = ax.get_ylim() else: a, b = ax.get_xlim() if l == None or a<l: l=a if u == None or b>u: u=b lims = (l, u) print (lims) for a in self.fig.axes: if axis=='y': a.set_ylim(lims) else: a.set_xlim(lims) return
def _clearArgs(self, kwargs): """Clear kwargs of formatting options so that a style can be used""" keys = ['colormap','grid'] for k in keys: if k in kwargs: kwargs[k] = None return kwargs def _doplot(self, data, ax, kind, subplots, errorbars, useindex, bw, yerr, kwargs): """Core plotting method where the individual plot functions are called""" kwargs = kwargs.copy() if self.style != None: keargs = self._clearArgs(kwargs) cols = data.columns if kind == 'line': data = data.sort_index() rows = int(round(np.sqrt(len(data.columns)),0)) if len(data.columns) == 1 and kind not in ['pie']: kwargs['subplots'] = False if 'colormap' in kwargs: cmap = plt.cm.get_cmap(kwargs['colormap']) else: cmap = None #change some things if we are plotting in b&w styles = [] if bw == True and kind not in ['pie','heatmap']: cmap = None kwargs['color'] = 'k' kwargs['colormap'] = None styles = ["-","--","-.",":"] if 'linestyle' in kwargs: del kwargs['linestyle'] if subplots == 0: layout = None else: layout=(rows,-1) if errorbars == True and yerr == None: yerr = data[data.columns[1::2]] data = data[data.columns[0::2]] yerr.columns = data.columns plt.rcParams['errorbar.capsize']=4 #kwargs['elinewidth'] = 1 if kind == 'bar' or kind == 'barh': if len(data) > 50: ax.get_xaxis().set_visible(False) if len(data) > 300: self.showWarning('too many bars to plot') return if kind == 'scatter': axs, sc = self.scatter(data, ax, **kwargs) if kwargs['sharey'] == 1: lims = self.fig.axes[0].get_ylim() for a in self.fig.axes: a.set_ylim(lims) elif kind == 'boxplot': axs = data.boxplot(ax=ax, grid=kwargs['grid'], patch_artist=True, return_type='dict') lw = kwargs['linewidth'] plt.setp(axs['boxes'], color='black', lw=lw) plt.setp(axs['whiskers'], color='black', lw=lw) plt.setp(axs['fliers'], color='black', marker='+', lw=lw) clr = cmap(0.5) for patch in axs['boxes']: patch.set_facecolor(clr) if kwargs['logy'] == 1: ax.set_yscale('log') elif kind == 'violinplot': axs = self.violinplot(data, ax, kwargs) elif kind == 'dotplot': axs = self.dotplot(data, ax, kwargs) elif kind == 'histogram': #bins = int(kwargs['bins']) axs = data.plot(kind='hist',layout=layout, ax=ax, **kwargs) elif kind == 'heatmap': if len(data) > 1000: self.showWarning('too many rows to plot') return axs = self.heatmap(data, ax, kwargs) elif kind == 'bootstrap': axs = plotting.bootstrap_plot(data) elif kind == 'scatter_matrix': axs = pd.scatter_matrix(data, ax=ax, **kwargs) elif kind == 'hexbin': x = cols[0] y = cols[1] axs = data.plot(x,y,ax=ax,kind='hexbin',gridsize=20,**kwargs) elif kind == 'contour': xi,yi,zi = self.contourData(data) cs = ax.contour(xi,yi,zi,15,linewidths=.5,colors='k') #plt.clabel(cs,fontsize=9) cs = ax.contourf(xi,yi,zi,15,cmap=cmap) #ax.scatter(x,y,marker='o',c='b',s=5) self.fig.colorbar(cs,ax=ax) axs = ax elif kind == 'imshow': xi,yi,zi = self.contourData(data) im = ax.imshow(zi, interpolation="nearest", cmap=cmap, alpha=kwargs['alpha']) self.fig.colorbar(im,ax=ax) axs = ax elif kind == 'pie': if useindex == False: x=data.columns[0] data.set_index(x,inplace=True) if kwargs['legend'] == True: lbls=None else: lbls = list(data.index) axs = data.plot(ax=ax,kind='pie', labels=lbls, layout=layout, autopct='%1.1f%%', subplots=True, **kwargs) if lbls == None: axs[0].legend(labels=data.index, loc='best') elif kind == 'venn': axs = self.venn(data, ax, **kwargs) elif kind == 'radviz': if kwargs['marker'] == '': kwargs['marker'] = 'o' col = data.columns[-1] axs = pd.plotting.radviz(data, col, ax=ax, **kwargs) else: #line, bar and area plots if useindex == False: x=data.columns[0] data.set_index(x,inplace=True) if len(data.columns) == 0: msg = "Not enough data.\nIf 'use index' is off select at least 2 columns" self.showWarning(msg) return #adjust colormap to avoid white lines if cmap != None: cmap = util.adjustColorMap(cmap, 0.15,1.0) del kwargs['colormap'] if kind == 'barh': kwargs['xerr']=yerr yerr=None axs = data.plot(ax=ax, layout=layout, yerr=yerr, style=styles, cmap=cmap, **kwargs) self._setAxisRanges() self._setAxisTickFormat() return axs def _setAxisRanges(self): kwds = self.styleopts.kwds ax = self.ax try: xmin=float(kwds['xmin']) xmax=float(kwds['xmax']) ax.set_xlim((xmin,xmax)) except: pass try: ymin=float(kwds['ymin']) ymax=float(kwds['ymax']) ax.set_ylim((ymin,ymax)) except: pass return def _setAxisTickFormat(self): """Set axis tick format""" import matplotlib.ticker as mticker kwds = self.styleopts.kwds ax = self.ax data = self.data cols = list(data.columns) x = data[cols[0]] xt = kwds['major x-ticks'] yt = kwds['major y-ticks'] xmt = kwds['minor x-ticks'] ymt = kwds['minor y-ticks'] symbol = kwds['symbol'] places = kwds['precision'] dateformat = kwds['date format'] if xt != 0: ax.xaxis.set_major_locator(mticker.MaxNLocator(nbins=xt)) if yt != 0: ax.yaxis.set_major_locator(mticker.MaxNLocator(nbins=yt)) if xmt != 0: ax.xaxis.set_minor_locator(mticker.AutoMinorLocator(n=xmt)) ax.grid(b=True, which='minor', linestyle='--', linewidth=.5) if ymt != 0: ax.yaxis.set_minor_locator(mticker.AutoMinorLocator(n=ymt)) ax.grid(b=True, which='minor', linestyle='--', linewidth=.5) formatter = kwds['formatter'] if formatter == 'percent': ax.xaxis.set_major_formatter(mticker.PercentFormatter()) elif formatter == 'eng': ax.xaxis.set_major_formatter(mticker.EngFormatter(unit=symbol,places=places)) elif formatter == 'sci notation': ax.xaxis.set_major_formatter(mticker.LogFormatterSciNotation()) if dateformat != '': print (x.dtype) import matplotlib.dates as mdates ax.xaxis.set_major_formatter(mdates.DateFormatter(dateformat)) return
[docs] def scatter(self, df, ax, alpha=0.8, marker='o', color=None, **kwds): """A custom scatter plot rather than the pandas one. By default this plots the first column selected versus the others""" if len(df.columns)<2: return data = df df = df.copy()._get_numeric_data() cols = list(df.columns) x = df[cols[0]] s=1 cmap = plt.cm.get_cmap(kwds['colormap']) lw = kwds['linewidth'] clrcol = kwds['clrcol'] #color by values in a column cscale = kwds['cscale'] grid = kwds['grid'] bw = kwds['bw'] if cscale == 'log': norm = mpl.colors.LogNorm() else: norm = None if color != None: c = color elif clrcol != '': if clrcol in df.columns: if len(cols)>2: cols.remove(clrcol) c = data[clrcol] if c.dtype.kind not in 'bifc': c = pd.factorize(c)[0] else: c = None plots = len(cols) if marker == '': marker = 'o' if kwds['subplots'] == True: size = plots-1 nrows = int(round(np.sqrt(size),0)) ncols = int(np.ceil(size/nrows)) self.fig.clear() if c is not None: colormap = kwds['colormap'] else: colormap = None c=None #print (kwds) labelcol = kwds['labelcol'] pointsizes = kwds['pointsizes'] handles = [] for i in range(s,plots): y = df[cols[i]] ec = 'black' if bw == True: clr = 'white' colormap = None else: clr = cmap(float(i)/(plots)) if colormap != None: clr=None if marker in ['x','+'] and bw == False: ec = clr if kwds['subplots'] == True: ax = self.fig.add_subplot(nrows,ncols,i) if pointsizes != '' and pointsizes in df.columns: ms = df[pointsizes] s=kwds['ms'] getsizes = lambda x : (((x-x.min())/float(x.max()-x.min())+1)*s)**2.3 ms = getsizes(ms) #print (ms) else: ms = kwds['ms'] * 12 sc = ax.scatter(x, y, marker=marker, alpha=alpha, linewidth=lw, c=c, s=ms, edgecolors=ec, facecolor=clr, cmap=colormap, norm=norm, label=cols[i], picker=True) #create proxy artist for markers so we can return these handles if needed mkr = Line2D([0], [0], marker=marker, alpha=alpha, ms=10, markerfacecolor=c, markeredgewidth=lw, markeredgecolor=ec, linewidth=0) handles.append(mkr) ax.set_xlabel(cols[0]) if kwds['logx'] == 1: ax.set_xscale('log') if kwds['logy'] == 1: ax.set_yscale('log') ax.set_ylim((x.min()+.1,x.max())) if grid == 1: ax.grid(True) if kwds['subplots'] == True: ax.set_title(cols[i]) if colormap is not None and kwds['colorbar'] == True: self.fig.colorbar(sc, ax=ax) if labelcol != '': if not labelcol in data.columns: self.showWarning('label column %s not in selected data' %labelcol) elif len(data)<1500: for i, r in data.iterrows(): txt = r[labelcol] if pd.isnull(txt) is True: continue ax.annotate(txt, (x[i],y[i]), xycoords='data', xytext=(5, 5), textcoords='offset points',) if kwds['legend'] == 1 and kwds['subplots'] == False: ax.legend(cols[1:]) return ax, handles
[docs] def violinplot(self, df, ax, kwds): """violin plot""" data=[] clrs=[] cols = len(df.columns) cmap = plt.cm.get_cmap(kwds['colormap']) for i,d in enumerate(df): clrs.append(cmap(float(i)/cols)) data.append(df[d].values) lw = kwds['linewidth'] alpha = kwds['alpha'] parts = ax.violinplot(data, showextrema=False, showmeans=True) i=0 for pc in parts['bodies']: pc.set_facecolor(clrs[i]) pc.set_edgecolor('black') pc.set_alpha(alpha) pc.set_linewidth(lw) i+=1 labels = df.columns ax.set_xticks(np.arange(1, len(labels) + 1)) ax.set_xticklabels(labels) return
[docs] def dotplot(self, df, ax, kwds): """Dot plot""" marker = kwds['marker'] if marker == '': marker = 'o' cmap = plt.cm.get_cmap(kwds['colormap']) ms = kwds['ms'] lw = kwds['linewidth'] alpha = kwds['alpha'] cols = len(df.columns) axs = df.boxplot(ax=ax, grid=False, return_type='dict') plt.setp(axs['boxes'], color='white') plt.setp(axs['whiskers'], color='white') plt.setp(axs['caps'], color='black', lw=lw) plt.setp(axs['medians'], color='black', lw=lw) np.random.seed(42) for i,d in enumerate(df): clr = cmap(float(i)/cols) y = df[d] x = np.random.normal(i+1, 0.04, len(y)) ax.plot(x, y, c=clr, mec='k', ms=ms, marker=marker, alpha=alpha, mew=lw, linestyle="None") if kwds['logy'] == 1: ax.set_yscale('log') return ax
[docs] def heatmap(self, df, ax, kwds): """Plot heatmap""" X = df._get_numeric_data() clr='black' lw = kwds['linewidth'] if lw==0: clr=None lw=None if kwds['cscale']=='log': norm=mpl.colors.LogNorm() else: norm=None hm = ax.pcolor(X, cmap=kwds['colormap'], edgecolor=clr, linewidth=lw,alpha=kwds['alpha'],norm=norm) if kwds['colorbar'] == True: self.fig.colorbar(hm, ax=ax) ax.set_xticks(np.arange(0.5, len(X.columns))) ax.set_yticks(np.arange(0.5, len(X.index))) ax.set_xticklabels(X.columns, minor=False) ax.set_yticklabels(X.index, minor=False) ax.set_ylim(0, len(X.index)) ##if kwds['rot'] != 0: # for tick in ax.get_xticklabels(): # tick.set_rotation(kwds['rot']) #from mpl_toolkits.axes_grid1 import make_axes_locatable #divider = make_axes_locatable(ax) return
[docs] def venn(self, data, ax, colormap=None, alpha=0.8): """Plot venn diagram, requires matplotlb-venn""" try: from matplotlib_venn import venn2,venn3 except: self.showWarning('requires matplotlib_venn') return l = len(data.columns) if l<2: return x = data.values[:,0] y = data.values[:,1] if l==2: labels = list(data.columns[:2]) v = venn2([set(x), set(y)], set_labels=labels, ax=ax) else: labels = list(data.columns[:3]) z = data.values[:,2] v = venn3([set(x), set(y), set(z)], set_labels=labels, ax=ax) ax.axis('off') ax.set_axis_off() return ax
[docs] def contourData(self, data): """Get data for contour plot""" #from matplotlib.mlab import griddata from scipy.interpolate import griddata x = data.values[:,0] y = data.values[:,1] z = data.values[:,2] xi = np.linspace(x.min(), x.max()) yi = np.linspace(y.min(), y.max()) zi = griddata((x, y), z, (xi[None,:], yi[:,None]), method='cubic') return xi,yi,zi
[docs] def meshData(self, x,y,z): """Prepare 1D data for plotting in the form (x,y)->Z""" from scipy.interpolate import griddata xi = np.linspace(x.min(), x.max()) yi = np.linspace(y.min(), y.max()) #zi = griddata(x, y, z, xi, yi, interp='linear') zi = griddata((x, y), z, (xi[None,:], yi[:,None]), method='cubic') X, Y = np.meshgrid(xi, yi) return X,Y,zi
[docs] def getView(self): ax = self.ax if hasattr(ax,'azim'): azm=ax.azim ele=ax.elev dst=ax.dist else: return None,None,None return azm,ele,dst
[docs] def getcmap(self, name): try: return plt.cm.get_cmap(name) except: return plt.cm.get_cmap('Spectral')
[docs] def plot3D(self, redraw=True): """3D plot""" if not hasattr(self, 'data') or len(self.data.columns)<3: return kwds = self.mplopts.kwds.copy() #use base options by joining the dicts kwds.update(self.mplopts3d.kwds) kwds.update(self.labelopts.kwds) #print (kwds) data = self.data x = data.values[:,0] y = data.values[:,1] z = data.values[:,2] azm,ele,dst = self.getView() #self.fig.clear() ax = self.ax# = Axes3D(self.fig) kind = kwds['kind'] mode = kwds['mode'] rstride = kwds['rstride'] cstride = kwds['cstride'] lw = kwds['linewidth'] alpha = kwds['alpha'] cmap = kwds['colormap'] if kind == 'scatter': self.scatter3D(data, ax, kwds) elif kind == 'bar': self.bar3D(data, ax, kwds) elif kind == 'contour': from scipy.interpolate import griddata xi = np.linspace(x.min(), x.max()) yi = np.linspace(y.min(), y.max()) zi = griddata((x, y), z, (xi[None,:], yi[:,None]), method='cubic') #zi = np.meshgrid(x, y, z, xi, yi) surf = ax.contour(xi, yi, zi, rstride=rstride, cstride=cstride, cmap=kwds['colormap'], alpha=alpha, linewidth=lw, antialiased=True) elif kind == 'wireframe': if mode == '(x,y)->z': X,Y,zi = self.meshData(x,y,z) else: X,Y,zi = x,y,z w = ax.plot_wireframe(X, Y, zi, rstride=rstride, cstride=cstride, linewidth=lw) elif kind == 'surface': X,Y,zi = self.meshData(x,y,z) surf = ax.plot_surface(X, Y, zi, rstride=rstride, cstride=cstride, cmap=cmap, alpha=alpha, linewidth=lw) cb = self.fig.colorbar(surf, shrink=0.5, aspect=5) surf.set_clim(vmin=zi.min(), vmax=zi.max()) if kwds['points'] == True: self.scatter3D(data, ax, kwds) self.setFigureOptions(ax, kwds) if azm!=None: self.ax.azim = azm self.ax.elev = ele self.ax.dist = dst #handles, labels = self.ax.get_legend_handles_labels() #self.fig.legend(handles, labels) self.canvas.draw() return
[docs] def bar3D(self, data, ax, kwds): """3D bar plot""" i=0 plots=len(data.columns) cmap = plt.cm.get_cmap(kwds['colormap']) for c in data.columns: h = data[c] c = cmap(float(i)/(plots)) ax.bar(data.index, h, zs=i, zdir='y', color=c) i+=1
[docs] def scatter3D(self, data, ax, kwds): """3D scatter plot""" def doscatter(data, ax, color=None, pointlabels=None): data = data._get_numeric_data() l = len(data.columns) if l<3: return X = data.values x = X[:,0] y = X[:,1] handles=[] labels=data.columns[2:] for i in range(2,l): z = X[:,i] if color==None: c = cmap(float(i)/(l)) else: c = color h=ax.scatter(x, y, z, edgecolor='black', c=c, linewidth=lw, alpha=alpha, marker=marker, s=ms) handles.append(h) if pointlabels is not None: trans_offset = mtrans.offset_copy(ax.transData, fig=self.fig, x=0.05, y=0.10, units='inches') for i in zip(x,y,z,pointlabels): txt=i[3] ax.text(i[0],i[1],i[2], txt, None, transform=trans_offset) return handles,labels lw = kwds['linewidth'] alpha = kwds['alpha'] ms = kwds['ms']*6 marker = kwds['marker'] if marker=='': marker='o' by = kwds['by'] legend = kwds['legend'] cmap = self.getcmap(kwds['colormap']) labelcol = kwds['labelcol'] handles=[] pl=None if by != '': if by not in data.columns: self.showWarning('grouping column not in selection') return g = data.groupby(by) i=0 pl=None for n,df in g: c = cmap(float(i)/(len(g))) if labelcol != '': pl = df[labelcol] h,l = doscatter(df, ax, color=c, pointlabels=pl) handles.append(h[0]) i+=1 self.fig.legend(handles, g.groups) else: if labelcol != '': pl = data[labelcol] handles,lbls=doscatter(data, ax, pointlabels=pl) self.fig.legend(handles, lbls) return
[docs] def updateData(self): """Update data widgets""" if self.table is None: return df = self.table.model.df self.mplopts.update(df) return
[docs] def updateStyle(self): if self.style == None: mpl.rcParams.update(mpl.rcParamsDefault) else: plt.style.use(self.style) return
[docs] def savePlot(self, filename=None): """Save the current plot""" ftypes = [('png','*.png'),('jpg','*.jpg'),('tif','*.tif'),('pdf','*.pdf'), ('eps','*.eps'),('svg','*.svg')] if filename == None: filename = filedialog.asksaveasfilename(parent=self.master, initialdir = self.currentdir, filetypes=ftypes) if filename: self.currentdir = os.path.dirname(os.path.abspath(filename)) dpi = self.globalopts['dpi'] self.fig.savefig(filename, dpi=dpi) return
[docs] def showWarning(self, text='plot error', ax=None): """Show warning message in the plot window""" if ax==None: ax = self.fig.add_subplot(111) ax.clear() ax.text(.5, .5, text, transform=self.ax.transAxes, horizontalalignment='center', color='blue', fontsize=16) self.canvas.draw() return
[docs] def toggle_options(self): """Show/hide plot options""" if self.nb.winfo_ismapped() == 1: self.nb.pack_forget() else: self.nb.pack(fill=BOTH,expand=1) return
[docs] def close(self): """Close the window""" self.table.pf = None self.animateopts.stop() self.main.destroy() return
[docs]class TkOptions(object): """Class to generate tkinter widget dialog for dict of options""" def __init__(self, parent=None): """Setup variables""" self.parent = parent df = self.parent.table.model.df return
[docs] def applyOptions(self): """Set the plot kwd arguments from the tk variables""" kwds = {} for i in self.opts: if not i in self.tkvars: continue #print (i, self.opts[i]['type'], self.widgets[i]) if self.opts[i]['type'] == 'listbox': items = self.widgets[i].curselection() kwds[i] = [self.widgets[i].get(j) for j in items] elif self.opts[i]['type'] == 'scrolledtext': kwds[i] = self.widgets[i].get('1.0',END) elif self.opts[i]['type'] == 'checkbutton': kwds[i] = bool(self.tkvars[i].get()) else: kwds[i] = self.tkvars[i].get() self.kwds = kwds return
[docs] def setWidgetStyles(self): style = Style() bg = style.lookup('TLabel.label', 'background') for i in self.widgets: try: self.widgets[i].configure(fg='black', bg=bg) except: pass return
[docs] def apply(self): self.applyOptions() if self.callback != None: self.callback() return
[docs] def showDialog(self, parent, layout='horizontal'): """Auto create tk vars, widgets for corresponding options and and return the frame""" dialog, self.tkvars, self.widgets = dialogFromOptions(parent, self.opts, self.groups, layout=layout) self.setWidgetStyles() return dialog
[docs] def updateFromDict(self, kwds=None): """Update all widget tk vars using plot kwds dict""" if kwds != None: self.kwds = kwds elif hasattr(self, 'kwds'): kwds = self.kwds else: return if self.tkvars == None: return for i in kwds: if i in self.tkvars and self.tkvars[i]: self.tkvars[i].set(kwds[i]) return
[docs] def increment(self, key, inc): """Increase the value of a widget""" new = self.kwds[key]+inc self.tkvars[key].set(new) return
[docs]class MPLBaseOptions(TkOptions): """Class to provide a dialog for matplotlib options and returning the selected prefs""" kinds = ['line', 'scatter', 'bar', 'barh', 'pie', 'histogram', 'boxplot', 'violinplot', 'dotplot', 'heatmap', 'area', 'hexbin', 'contour', 'imshow', 'scatter_matrix', 'density', 'radviz', 'venn'] legendlocs = ['best','upper right','upper left','lower left','lower right','right','center left', 'center right','lower center','upper center','center'] defaultfont = 'monospace' def __init__(self, parent=None): """Setup variables""" self.parent = parent if self.parent is not None: df = self.parent.table.model.df datacols = list(df.columns) datacols.insert(0,'') else: datacols=[] fonts = util.getFonts() scales = ['linear','log'] grps = {'data':['by','by2','labelcol','pointsizes'], 'formats':['font','marker','linestyle','alpha'], 'sizes':['fontsize','ms','linewidth'], 'general':['kind','bins','stacked','subplots','use_index','errorbars'], 'axes':['grid','legend','showxlabels','showylabels','sharex','sharey','logx','logy'], 'colors':['colormap','bw','clrcol','cscale','colorbar']} order = ['general','data','axes','sizes','formats','colors'] self.groups = OrderedDict((key, grps[key]) for key in order) opts = self.opts = {'font':{'type':'combobox','default':self.defaultfont,'items':fonts}, 'fontsize':{'type':'scale','default':12,'range':(5,40),'interval':1,'label':'font size'}, 'marker':{'type':'combobox','default':'','items': markers}, 'linestyle':{'type':'combobox','default':'-','items': linestyles}, 'ms':{'type':'scale','default':5,'range':(1,80),'interval':1,'label':'marker size'}, 'grid':{'type':'checkbutton','default':0,'label':'show grid'}, 'logx':{'type':'checkbutton','default':0,'label':'log x'}, 'logy':{'type':'checkbutton','default':0,'label':'log y'}, #'rot':{'type':'entry','default':0, 'label':'xlabel angle'}, 'use_index':{'type':'checkbutton','default':1,'label':'use index'}, 'errorbars':{'type':'checkbutton','default':0,'label':'errorbar column'}, 'clrcol':{'type':'combobox','items':datacols,'label':'color by value','default':''}, 'cscale':{'type':'combobox','items':scales,'label':'color scale','default':'linear'}, 'colorbar':{'type':'checkbutton','default':0,'label':'show colorbar'}, 'bw':{'type':'checkbutton','default':0,'label':'black & white'}, 'showxlabels':{'type':'checkbutton','default':1,'label':'x tick labels'}, 'showylabels':{'type':'checkbutton','default':1,'label':'y tick labels'}, 'sharex':{'type':'checkbutton','default':0,'label':'share x'}, 'sharey':{'type':'checkbutton','default':0,'label':'share y'}, 'legend':{'type':'checkbutton','default':1,'label':'legend'}, #'loc':{'type':'combobox','default':'best','items':self.legendlocs,'label':'legend loc'}, 'kind':{'type':'combobox','default':'line','items':self.kinds,'label':'plot type'}, 'stacked':{'type':'checkbutton','default':0,'label':'stacked'}, 'linewidth':{'type':'scale','default':1.5,'range':(0,10),'interval':0.1,'label':'line width'}, 'alpha':{'type':'scale','default':0.9,'range':(0,1),'interval':0.1,'label':'alpha'}, 'subplots':{'type':'checkbutton','default':0,'label':'multiple subplots'}, 'colormap':{'type':'combobox','default':'Spectral','items':colormaps}, 'bins':{'type':'entry','default':20,'width':10}, 'by':{'type':'combobox','items':datacols,'label':'group by','default':''}, 'by2':{'type':'combobox','items':datacols,'label':'group by 2','default':''}, 'labelcol':{'type':'combobox','items':datacols,'label':'point labels','default':''}, 'pointsizes':{'type':'combobox','items':datacols,'label':'point sizes','default':''}, } self.kwds = {} return
[docs] def applyOptions(self): """Set the plot kwd arguments from the tk variables""" TkOptions.applyOptions(self) size = self.kwds['fontsize'] plt.rc("font", family=self.kwds['font'], size=size) plt.rc('legend', fontsize=size-1) return
[docs] def update(self, df): """Update data widget(s) when dataframe changes""" if util.check_multiindex(df.columns) == 1: cols = list(df.columns.get_level_values(0)) else: cols = list(df.columns) #add empty value cols = ['']+cols self.widgets['by']['values'] = cols self.widgets['by2']['values'] = cols self.widgets['labelcol']['values'] = cols self.widgets['clrcol']['values'] = cols return
[docs]class MPL3DOptions(MPLBaseOptions): """Class to provide 3D matplotlib options""" kinds = ['scatter', 'bar', 'contour', 'wireframe', 'surface'] defaultfont = 'monospace' def __init__(self, parent=None): """Setup variables""" self.parent = parent if self.parent is not None: df = self.parent.table.model.df datacols = list(df.columns) datacols.insert(0,'') else: datacols=[] fonts = util.getFonts() modes = ['parametric','(x,y)->z'] self.groups = grps = {'formats':['kind','mode','rstride','cstride','points'], } self.groups = OrderedDict(sorted(grps.items())) opts = self.opts = { 'kind':{'type':'combobox','default':'scatter','items':self.kinds,'label':'kind'}, 'rstride':{'type':'entry','default':2,'width':20}, 'cstride':{'type':'entry','default':2,'width':20}, 'points':{'type':'checkbutton','default':0,'label':'show points'}, 'mode':{'type':'combobox','default':'(x,y)->z','items': modes}, } self.kwds = {} return
[docs] def applyOptions(self): """Set the plot kwd arguments from the tk variables""" TkOptions.applyOptions(self) return
[docs]class PlotLayoutOptions(TkOptions): def __init__(self, parent=None): """Setup variables""" self.parent = parent self.rows = 2 self.cols = 2 self.top = .1 self.bottom =.9 return
[docs] def showDialog(self, parent, layout='horizontal'): """Override because we need to add custom bits""" self.tkvars = {} self.main = Frame(parent) self.plotgrid = c = PlotLayoutGrid(self.main) self.plotgrid.update_callback = self.updateFromGrid c.pack(side=LEFT,fill=Y,pady=2,padx=2) frame = Frame(self.main) frame.pack(side=LEFT,fill=Y) v = self.rowsvar = IntVar() v.set(self.rows) w = Scale(frame,label='rows', from_=1,to=6, orient='vertical', resolution=1, variable=v, command=self.resetGrid) w.pack(fill=X,pady=2) v = self.colsvar = IntVar() v.set(self.cols) w = Scale(frame,label='cols', from_=1,to=6, orient='horizontal', resolution=1, variable=v, command=self.resetGrid) w.pack(side=TOP,fill=X,pady=2) self.modevar = StringVar() self.modevar.set('normal') frame = LabelFrame(self.main, text='modes') Radiobutton(frame, text='normal', variable=self.modevar, value='normal').pack(fill=X) Radiobutton(frame, text='split data', variable=self.modevar, value='splitdata').pack(fill=X) Radiobutton(frame, text='multi views', variable=self.modevar, value='multiviews').pack(fill=X) frame.pack(side=LEFT,fill=Y) frame = LabelFrame(self.main, text='multi views') #v = self.multiviewsvar = BooleanVar() plot_types = ['histogram','line','scatter','boxplot','dotplot','area','density','bar','barh', 'heatmap','contour','hexbin','imshow'] Label(frame,text='plot types:').pack(fill=X) w,v = addListBox(frame, values=plot_types,width=12,height=8) w.pack(fill=X) self.plottypeslistbox = v frame.pack(side=LEFT,fill=Y) frame = LabelFrame(self.main, text='split data') frame.pack(side=LEFT,fill=Y) frame = LabelFrame(self.main, text='subplots') v = self.axeslistvar = StringVar() v.set('') axes = [] self.axeslist = Combobox(frame, values=axes, textvariable=v,width=14) Label(frame,text='plot list:').pack() self.axeslist.pack(fill=BOTH,pady=2) b = Button(frame, text='remove axis', command=self.parent.removeSubplot) b.pack(fill=X,pady=2) b = Button(frame, text='set title', command=self.parent.setSubplotTitle) b.pack(fill=X,pady=2) frame.pack(side=LEFT,fill=Y) self.updateFromGrid() return self.main
[docs] def setmultiviews(self, event=None): val=self.multiviewsvar.get() if val == 1: self.parent.multiviews = True self.parent.gridlayoutvar.set(1) if val == 0: self.parent.multiviews = False return
[docs] def resetGrid(self, event=None): """update grid and redraw""" pg = self.plotgrid self.rows = pg.rows = self.rowsvar.get() self.cols = pg.cols = self.colsvar.get() pg.selectedrows = [0] pg.selectedcols = [0] pg.redraw() self.updateFromGrid() return
[docs] def updateFromGrid(self): pg = self.plotgrid r = self.selectedrows = pg.selectedrows c = self.selectedcols = pg.selectedcols self.rowspan = len(r) self.colspan = len(c) return
[docs] def updateAxesList(self): """Update axes list""" axes = list(self.parent.gridaxes.keys()) self.axeslist['values'] = axes return
[docs]class PlotLayoutGrid(BaseTable): def __init__(self, parent=None, width=280, height=205, rows=2, cols=2, **kwargs): BaseTable.__init__(self, parent, bg='white', width=width, height=height ) return
[docs] def handle_left_click(self, event): BaseTable.handle_left_click(self, event)
[docs]class AnnotationOptions(TkOptions): """This class also provides custom tools for adding items to the plot""" def __init__(self, parent=None): """Setup variables""" from matplotlib import colors import six colors = list(six.iteritems(colors.cnames)) colors = sorted([c[0] for c in colors]) fillpatterns = ['-', '+', 'x', '\\', '*', 'o', 'O', '.'] bstyles = ['square','round','round4','circle','rarrow','larrow','sawtooth'] fonts = util.getFonts() defaultfont = 'monospace' fontweights = ['normal','bold','heavy','light','ultrabold','ultralight'] alignments = ['left','center','right'] self.parent = parent self.groups = grps = {'global labels':['title','xlabel','ylabel','rot'], 'textbox': ['boxstyle','facecolor','linecolor','rotate'], 'textbox format': ['fontsize','font','fontweight','align'], 'text to add': ['text'] } self.groups = OrderedDict(sorted(grps.items())) opts = self.opts = { 'title':{'type':'entry','default':'','width':20}, 'xlabel':{'type':'entry','default':'','width':20}, 'ylabel':{'type':'entry','default':'','width':20}, 'facecolor':{'type':'combobox','default':'white','items': colors}, 'linecolor':{'type':'combobox','default':'black','items': colors}, 'fill':{'type':'combobox','default':'-','items': fillpatterns}, 'rotate':{'type':'scale','default':0,'range':(-180,180),'interval':1,'label':'rotate'}, 'boxstyle':{'type':'combobox','default':'square','items': bstyles}, 'text':{'type':'scrolledtext','default':'','width':20}, 'align':{'type':'combobox','default':'center','items': alignments}, 'font':{'type':'combobox','default':defaultfont,'items':fonts}, 'fontsize':{'type':'scale','default':12,'range':(4,50),'interval':1,'label':'font size'}, 'fontweight':{'type':'combobox','default':'normal','items': fontweights}, 'rot':{'type':'entry','default':0, 'label':'ticklabel angle'} } self.kwds = {} #used to store annotations self.textboxes = {} return
[docs] def showDialog(self, parent, layout='horizontal'): """Override because we need to add custom widgets""" dialog, self.tkvars, self.widgets = dialogFromOptions(parent, self.opts, self.groups, layout=layout) self.main = dialog self.addWidgets() self.setWidgetStyles() return dialog
[docs] def addWidgets(self): """Custom dialogs for manually adding annotation items like text""" frame = LabelFrame(self.main, text='add objects') v = self.objectvar = StringVar() v.set('textbox') w = Combobox(frame, values=['textbox'],#'arrow'], textvariable=v,width=14) Label(frame,text='add object').pack() w.pack(fill=BOTH,pady=2) self.coordsvar = StringVar() self.coordsvar.set('data') w = Combobox(frame, values=['data','axes fraction','figure fraction'], textvariable=self.coordsvar,width=14) Label(frame,text='coord system').pack() w.pack(fill=BOTH,pady=2) b = Button(frame, text='Create', command=self.addObject) b.pack(fill=X,pady=2) b = Button(frame, text='Clear', command=self.clear) b.pack(fill=X,pady=2) frame.pack(side=LEFT,fill=Y) return
[docs] def clear(self): """Clear annotations""" self.textboxes = {} self.parent.replot() return
[docs] def addObject(self): """Add an annotation object""" o = self.objectvar.get() if o == 'textbox': self.addTextBox() elif o == 'arrow': self.addArrow() return
[docs] def addTextBox(self, kwds=None, key=None): """Add a text annotation and store it using key""" import matplotlib.patches as patches from matplotlib.text import OffsetFrom self.applyOptions() if kwds == None: kwds = self.kwds kwds['xycoords'] = self.coordsvar.get() fig = self.parent.fig #ax = self.parent.ax ax = fig.get_axes()[0] canvas = self.parent.canvas text = kwds['text'].strip('\n') fc = kwds['facecolor'] ec = kwds['linecolor'] bstyle = kwds['boxstyle'] style = "%s,pad=.2" %bstyle fontsize = kwds['fontsize'] font = mpl.font_manager.FontProperties(family=kwds['font'], weight=kwds['fontweight']) bbox_args = dict(boxstyle=bstyle, fc=fc, ec=ec, lw=1, alpha=0.9) arrowprops = dict(arrowstyle="-|>", connectionstyle="arc3") xycoords = kwds['xycoords'] #if previously drawn will have xy values if 'xy' in kwds: xy = kwds['xy'] #print (text, xycoords, xy) else: xy=(.1, .8) xycoords='axes fraction' an = ax.annotate(text, xy=xy, xycoords=xycoords, ha=kwds['align'], va="center", size=fontsize, fontproperties=font, rotation=kwds['rotate'], #arrowprops=arrowprops, #xytext=(xy[0]+.2,xy[1]), #textcoords='offset points', zorder=10, bbox=bbox_args) an.draggable() if key == None: import uuid key = str(uuid.uuid4().fields[0]) #need to add the unique id to the annotation #so it can be tracked in event handler an._id = key if key not in self.textboxes: self.textboxes[key] = kwds print(kwds) #canvas.show() canvas.draw() return
[docs] def addArrow(self, kwds=None, key=None): """Add line/arrow""" fig = self.parent.fig canvas = self.parent.canvas ax = fig.get_axes()[0] ax.arrow(0.2, 0.2, 0.5, 0.5, fc='k', ec='k', transform=ax.transAxes) canvas.draw() #self.lines.append(line) return
[docs] def redraw(self): """Redraw all stored annotations in the right places after a plot update""" #print (self.textboxes) for key in self.textboxes: self.addTextBox(self.textboxes[key], key) return
[docs]class ExtraOptions(TkOptions): """Class for additional formatting options like styles""" def __init__(self, parent=None): """Setup variables""" self.parent = parent self.styles = sorted(plt.style.available) formats = ['auto','percent','eng','sci notation'] datefmts = ['','%d','%b %d,''%Y-%m-%d','%d-%m-%Y',"%d-%m-%Y %H:%M"] self.groups = grps = {'axis ranges':['xmin','xmax','ymin','ymax'], 'axis tick positions':['major x-ticks','major y-ticks', 'minor x-ticks','minor y-ticks'], 'tick label format':['formatter','symbol','precision','date format'], #'tables':['table'] } self.groups = OrderedDict(sorted(grps.items())) opts = self.opts = {'xmin':{'type':'entry','default':'','label':'x min'}, 'xmax':{'type':'entry','default':'','label':'x max'}, 'ymin':{'type':'entry','default':'','label':'y min'}, 'ymax':{'type':'entry','default':'','label':'y max'}, 'major x-ticks':{'type':'entry','default':0}, 'major y-ticks':{'type':'entry','default':0}, 'minor x-ticks':{'type':'entry','default':0}, 'minor y-ticks':{'type':'entry','default':0}, 'formatter':{'type':'combobox','items':formats,'default':'auto'}, 'symbol':{'type':'entry','default':''}, 'precision':{'type':'entry','default':0}, 'date format':{'type':'combobox','items':datefmts,'default':''}, #'table':{'type':'checkbutton','default':0,'label':'show table'}, } self.kwds = {} return
[docs] def showDialog(self, parent, layout='horizontal'): """Create dialog widgets""" dialog, self.tkvars, self.widgets = dialogFromOptions(parent, self.opts, self.groups, layout=layout) self.main = dialog self.addWidgets() self.setWidgetStyles() return dialog
[docs] def addWidgets(self): """Custom dialogs for manually adding annotation items like text""" main = self.main frame = LabelFrame(main, text='styles') v = self.stylevar = StringVar() v.set('ggplot') w = Combobox(frame, values=self.styles, textvariable=v,width=14) w.pack(side=TOP,pady=2) addButton(frame, 'Apply', self.apply, None, 'apply', side=TOP, compound="left", width=20, padding=2) addButton(frame, 'Reset', self.reset, None, 'reset', side=TOP, compound="left", width=20, padding=2) frame.pack(side=LEFT,fill='y') return main
[docs] def apply(self): mpl.rcParams.update(mpl.rcParamsDefault) self.parent.style = self.stylevar.get() self.parent.replot() return
[docs] def reset(self): mpl.rcParams.update(mpl.rcParamsDefault) self.parent.style = None self.parent.replot() return
[docs]class AnimateOptions(TkOptions): """Class for live update/animation of plots.""" def __init__(self, parent=None): """Setup variables""" self.parent = parent df = self.parent.table.model.df datacols = list(df.columns) self.groups = grps = {'data window':['increment','window','startrow','delay'], 'display':['expand','usexrange','useyrange','tableupdate','smoothing','columntitle'], #'3d mode':['rotate axis','degrees'], 'record video':['savevideo','codec','fps','filename'] } self.groups = OrderedDict(sorted(grps.items())) codecs = ['default','h264'] opts = self.opts = {'increment':{'type':'entry','default':2}, 'window':{'type':'entry','default':10}, 'startrow':{'type':'entry','default':0,'label':'start row'}, 'delay':{'type':'entry','default':.05}, 'tableupdate':{'type':'checkbutton','default':0,'label':'update table'}, 'expand':{'type':'checkbutton','default':0,'label':'expanding view'}, 'usexrange':{'type':'checkbutton','default':0,'label':'use full x range'}, 'useyrange':{'type':'checkbutton','default':0,'label':'use full y range'}, 'smoothing':{'type':'checkbutton','default':0,'label':'smoothing'}, 'columntitle':{'type':'combobox','default':'','items':datacols,'label':'column data as title'}, #'source':{'type':'entry','default':'','width':30}, #'rotate axis':{'type':'combobox','default':'x','items':['x','y','z'],'label':'rotate axis'}, #'degrees':{'type':'entry','default':5}, 'savevideo':{'type':'checkbutton','default':0,'label':'save as video'}, 'codec':{'type':'combobox','default':'default','items': codecs}, 'fps':{'type':'entry','default':15}, 'filename':{'type':'entry','default':'myplot.mp4','width':20}, } self.kwds = {} self.running = False return
[docs] def showDialog(self, parent, layout='horizontal'): """Create dialog widgets""" dialog, self.tkvars, self.widgets = dialogFromOptions(parent, self.opts, self.groups, layout=layout) self.main = dialog self.addWidgets() return dialog
[docs] def addWidgets(self): """Custom dialogs for manually adding annotation items like text""" main = self.main frame = LabelFrame(main, text='Run') addButton(frame, 'START', self.start, None, 'apply', side=TOP, compound="left", width=20, padding=2) addButton(frame, 'STOP', self.stop, None, 'reset', side=TOP, compound="left", width=20, padding=2) frame.pack(side=LEFT,fill='y') return main
[docs] def getWriter(self): fps = self.kwds['fps'] import matplotlib.animation as manimation FFMpegWriter = manimation.writers['ffmpeg'] metadata = dict(title='My Plot', artist='Matplotlib', comment='Made using DataExplore') writer = FFMpegWriter(fps=fps, metadata=metadata) return writer
[docs] def update(self): """do live updating""" self.applyOptions() savevid = self.kwds['savevideo'] videofile = self.kwds['filename'] #if self.kwds['source'] != '': # self.stream() #else: if savevid == 1: writer = self.getWriter() with writer.saving(self.parent.fig, videofile, 100): self.updateCurrent(writer) else: self.updateCurrent() return
[docs] def updateCurrent(self, writer=None): """Iterate over current table and update plot""" kwds = self.kwds table = self.parent.table df = table.model.df titledata = None inc = kwds['increment'] w = kwds['window'] st = kwds['startrow'] delay = float(kwds['delay']) refresh = kwds['tableupdate'] expand = kwds['expand'] fullxrange = kwds['usexrange'] fullyrange = kwds['useyrange'] smooth = kwds['smoothing'] coltitle = kwds['columntitle'] if coltitle != '': titledata = df[coltitle] self.parent.clear() for i in range(st,len(df)-w,inc): cols = table.multiplecollist if expand == 1: rows = range(st,i+w) else: rows = range(i,i+w) data = df.iloc[list(rows),cols] if smooth == 1: w=int(len(data)/10.0) data = data.rolling(w).mean() self.parent.data = data #plot but don't redraw figure until the end self.parent.applyPlotoptions() self.parent.plotCurrent(redraw=False) if titledata is not None: l = titledata.iloc[i] self.parent.setOption('title',l) if fullxrange == 1: self.parent.ax.set_xlim(df.index.min(),df.index.max()) if fullyrange == 1: ymin = df.min(numeric_only=True).min() ymax = df.max(numeric_only=True).max() self.parent.ax.set_ylim(ymin,ymax) #finally draw the plot self.parent.canvas.draw() table.multiplerowlist = rows if refresh == 1: table.drawMultipleRows(rows) time.sleep(delay) if self.stopthread == True: return if writer is not None: writer.grab_frame() self.running = False return
[docs] def stream(self): """Stream data into table and plot - not implemented yet""" import requests, io kwds = self.kwds table = self.parent.table #endpoint = kwds['source'] base = "" endpoint = "" raw = requests.get(base + endpoint)#, params=payload) raw = io.BytesIO(raw.content) print ('got data source') print(raw) df = pd.read_csv(raw, sep=",") print (df) table.model.df = df for i in range(0,100): table.selectAll() self.parent.replot() return
[docs] def start(self): """start animation using a thread""" if self.running == True: return from threading import Thread self.stopthread = False self.running = True t = Thread(target=self.update) t.start() self.thread = t return
[docs] def stop(self): """Stop animation loop""" self.stopthread = True self.running = False time.sleep(.2) try: self.parent.update_idletasks() except: pass return
[docs]def addFigure(parent, figure=None, resize_callback=None): """Create a tk figure and canvas in the parent frame""" from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg#, NavigationToolbar2TkAgg from matplotlib.figure import Figure if figure == None: figure = Figure(figsize=(6,4), dpi=100, facecolor='white') canvas = FigureCanvasTkAgg(figure, master=parent) canvas.draw() canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1) canvas.get_tk_widget().configure(highlightcolor='gray75', highlightbackground='gray75') #canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=1) figure.subplots_adjust(bottom=0.15) return figure, canvas