# NOTE: This file contains things that haven't already found a home
# NOTE: in the broken up version of iplot

########################################
# The contents of this file are subject to the MLX PUBLIC LICENSE version
# 1.0 (the "License"); you may not use this file except in
# compliance with the License.
# 
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
# the License for the specific language governing rights and limitations
# under the License.
# 
# The Original Source Code is "compClust", released 2003 September 03.
# 
# The Original Source Code was developed by the California Institute of
# Technology (Caltech).  Portions created by Caltech are Copyright (C)
# 2002-2003 California Institute of Technology. All Rights Reserved.
########################################

"""
Author: Christopher Hart
Date  : April, 2002

These classes provide a framework for visualization within the MLS
python schema.  Briefly the work horse of the toolset are two primary
plotting classes IPlot and DatasetPlot built on the BLT toolkit
(although other plotting backend could probably be subsituted) .

IPlot is a specialization of the Pmw.Blt.Graph widget that adds may
user interface enhancements (pull down menus, sortable legends, popup
information, etc).

DatasetPlot is a specialization of IPlot which adds the communication
pathway for the PlotViews framework.  A DatasetPlot instance can only
plot when given a PlotView and they communicate via the interface
defined in the IPlotView class.  Through this interface custom
PlotViews can be built and combined to allow for a fairly module
interactive and extensible plotting enviroment.

"""

import math
import types
import colorsys
import re
import types
import operator
import sys

from matplotlib import numerix
Numeric = numerix
import MLab
import Tkinter
import Pmw

try:
  from Scientific.Statistics import Histogram as sciHistogram
except:
  sys.stderr.write("Scientific Python not found, plotting of Scientific Python's Histograms disabled\n")
  sciHistogram = types.NoneType

## FIXME: replace all occurances of direct use of class to module.class usage
from compClust.mlx.views import CachedView, RowAggregateFunctionView
from compClust.mlx.views import RowPCAView
from compClust.mlx.views import SortedView
from compClust.mlx.views import RowSubsetView
from compClust.mlx.datasets import Dataset
from compClust.mlx.labelings import Labeling
from compClust.mlx.labelings import GlobalLabeling
from compClust.mlx.labelings import GlobalWrapper
from compClust.mlx.labelings import subsetByLabeling

from compClust.score import roc
from compClust.score import ConfusionMatrix

## soon this will replace roc

from compClust.util.InterpreterTools import safeStdDev
from compClust.util import DistanceMetrics
from compClust.util import Histogram
from compClust.util import listOps
from compClust.util import unique
from compClust.util import NaN

class IPlot:
  """
  This is simply a class that adds alot of user interface stuff to the standard BLT plot.

  """
  
  def __init__(self, master=None, cnf={}, **kw):
    
    self.__selectionMarker = self.marker_create('text', name='selection')
    
    # private variables to handle zooming issues.
    self.__zoomSelection = [None, None, None, None]
    self.__previousZooms =  []
    self.__dragging = None
   
#    # build some default menus and make some default bindings.
#    self.__defineMenus()
#    self.bind('<ButtonRelease-3>', lambda event, menu=self.graphMenu: self.popupMenu( event, menu))
#
#    self.element_bind ('all', '<Button-1>', self.displayValues)
#    self.element_bind('all', '<Control-Button-1>', self.displayValuesSummary)
#    self.element_bind ('all', '<Double-Button-1>', self.elementSetup)
#    self.legend_bind("all", "<Double-Button-1>", self.elementSetup)
#    self.legend_bind("all", "<Button-1>", self.raiseElement)
#    self.marker_bind('selection', '<Button-1>', self.hideSelection)
#    
#    self.bind(sequence="<Shift-ButtonPress-1>",   func=self.mouseDown)
#    self.bind(sequence="<Shift-ButtonRelease-1>", func=self.zoomIn)
#    self.bind(sequence="<Control-ButtonPress-1>", func=self.zoomOut)

  def popupMenu(self, event, menu):

    """
    popupMenu(self, event, menu)

    create a popup menu
    """
    
    sizex, sizey, x, y = map(int, re.split("[x+]", self.master.geometry ()))
    menu.tk_popup(event.x+x, event.y+y)
  
  def __cBox(self, f, label, items):
    """
     Make a customized Combobox (used in graphSetup)
    """
    box = Pmw.ComboBox(f, label_text = label, 
                       labelpos = 'w', scrolledlist_items = items)
                
    box.pack(fill = 'both', expand = 1, padx = 8, pady = 8)
    return box

  def displayValues(self, event):
    """
    getValues(self, event)

    display an element's name, and the current value

    """

    index = event.widget.element_closest(event.x, event.y)['index']
    x = event.widget.element_closest (event.x, event.y)['x']
    y = event.widget.element_closest (event.x, event.y)['y']
    name = event.widget.element_closest (event.x, event.y)['name']
    status = "%s: (%3.2f, %3.2f)"%(name, x, y)
    coords = (self.xaxis_limits()[0], self.yaxis_limits()[1])
    self.marker_configure('selection', coords= coords, background="lightblue", 
                          text=status, under=0, anchor='w', hide=0)

  def displayValuesSummary(self, event):
    """
    displayValuesSummary(self, event, plotView=None)

    display the SummaryView page for a data point
    """
    if self.currentPlotViews is None or len(self.currentPlotViews) == 0:
      return
    # FIXME: which one to grab?
    plotView = self.currentPlotViews[0]
    
    closest_widget = event.widget.element_closest(event.x, event.y)
    if closest_widget is None:
      return
    index = closest_widget['index']
    x = event.widget.element_closest (event.x, event.y)['x']
    y = event.widget.element_closest (event.x, event.y)['y']
    primaryName   = event.widget.element_closest (event.x, event.y)['name'].split('__')[0]

    primaryLabeling   = plotView.getAnnotationMapper().getPrimaryLabeling()
    secondaryLabeling = plotView.getAnnotationMapper().getSecondaryLabeling()
    if primaryLabeling is None:
      row = int(primaryName)
    else:
      row = primaryLabeling.getRowsByLabel(primaryName)[0]
    if secondaryLabeling is not None:
      try:
        secondaryName = reduce(lambda x,y: str(x)+str(y), secondaryLabeling.getLabelsByRow(row))
      except:
        pass
    else:
      secondaryName = ''

    SummaryWindow(plotView, row)

    
  def hideSelection(self, event):

    """
    hideDisplay(self, event)
    """

    # this hack makes the labels go away before a plot referesh
    self.marker_configure('selection',  coord=(-100000,-1000000))
    self.marker_configure('selection', under=1, hide=1)
    
  def raiseElement(self, event):

    """
    raiseElement(event)
    
    Brings the selected data point to the front
    
    """
    displayedElements = list(self.element_show())
  
    pos = "@" +str(event.x) +"," +str(event.y)
    selection = self.legend_get(pos)    # get the selected legend
    selectedIndex = displayedElements.index(selection)
    
    if displayedElements[-1] == selection:
      # selection is at top, bring to bottom
      displayedElements.pop(-1)
      displayedElements.insert(0, selection)
    else:
      # selection is somewhere other than the top
      displayedElements.pop(selectedIndex)
      displayedElements.insert(len(displayedElements), selection)
      self.element_show(tuple(displayedElements))

  def setTitles(self, event=None):
    """
    create a dialogue box to edit the Graph title, and axis labels
    """

    print "not yet implimented"

  def elementSetup(self, event):
    """
    Make a dialog, and ask for a specified graph's color, linewidth etc.
     This function is called when the graph is double-clicked.
    """

    def applyChanges(event):
      self.element_configure(elName, color=colBox.get(), symbol=symBox.get(), 
                             smooth=smtBox.get(), linewidth=linBox.get(),
                             fill=scoBox.get(), outline=solBox.get())

    try:
      el = self.element_closest(event.x, event.y, interpolate=1)
      elName = el["name"]
    except:
      pos = "@" +str(event.x) +"," +str(event.y)
      elName = self.legend_get(pos)    # get the selected legend

    dialog = Pmw.Dialog(self.master)

    applyButton = Tkinter.Button(dialog.interior(), text = 'Apply',
                                 command = applyChanges)
    
    dialog.configure(
      buttons = ('OK','Apply'),
      title = 'Edit Line Properites - %s'%(elName),
      command = dialog.deactivate)

    dialog.withdraw()
    f = Tkinter.Frame(dialog.interior())
    f.pack()
      
    colBox = self.__cBox(f, "Color:",
                         ('red', 'yellow', 'blue', 'green', 'black', 'grey', 'custom'))
    symBox = self.__cBox(f, 'Symbols:',
                         ("", "square", "circle", "diamond", "cross", "triangle"))
    scoBox = self.__cBox(f, 'Symbol color:',
                         ('defcolor', 'red', 'yellow', 'blue', 'green', 'black', 'custom'))
    solBox = self.__cBox(f, 'Symbol outline:',
                         ('defcolor', 'red', 'yellow', 'blue', 'green', 'black', 'custom'))
    smtBox = self.__cBox(f, 'Smootheness:',
                         ('step', 'linear', 'quadratic', 'natural'))
    linBox = self.__cBox(f, 'Line thickness:', (0, 1, 2, 3, 4, 5))
    
                    
    # Retrieve the current setup for the graph...
    #colBox.selectitem(self.element_cget(elName, "color"))
    symBox.selectitem(self.element_cget(elName, "symbol"))
    scoBox.selectitem(self.element_cget(elName, "fill"))
    solBox.selectitem(self.element_cget(elName, "outline"))
    smtBox.selectitem(self.element_cget(elName, "smooth"))
    linBox.selectitem(self.element_cget(elName, "linewidth"))
    
    # Let the user interact
    dialog.activate()
    
    # Update any changes
    self.element_configure(elName, color=colBox.get(), symbol=symBox.get(), 
                           smooth=smtBox.get(), linewidth=linBox.get(),
                           fill=scoBox.get(), outline=solBox.get())
   
  # these functions are for zoooming

  def zoom(self):
    x0, y0, x1, y1 = self.__zoomSelection
    self.xaxis_configure(min=x0, max=x1)
    self.yaxis_configure(min=y0, max=y1)

  def mouseDrag(self, event):
    x0, y0, x1, y1 = self.__zoomSelection
    (x1, y1) = self.invtransform(event.x, event.y)
    self.marker_configure("marking rectangle", 
                          coords = (x0, y0, x1, y0, x1, y1, x0, y1, x0, y0))
    self.__zoomSelection = [x0,y0,x1, y1]

  def zoomIn(self, event):

    x0, y0, x1, y1 = self.__zoomSelection
    
    if self.__dragging:
      self.unbind(sequence="<Motion>")
      self.marker_delete("marking rectangle")
      
      if x0 <> x1 and y0 <> y1:

        # make sure the coordinates are sorted
        if x0 > x1:
          x0, x1 = x1, x0
        if y0 > y1:
          y0, y1 = y1, y0

        print "zoom in"
        self.__zoomSelection = [x0,y0,x1, y1]
        self.__previousZooms.append([self.xaxis_limits()[0],
                                     self.yaxis_limits()[0],
                                     self.xaxis_limits()[1],
                                     self.yaxis_limits()[1]])
        
        self.zoom()

  def zoomOut(self, event):

    """
    zoomOut (self, event)

    zooms out to the previous zoom state - can only zoom as far as the first zoom state.
    """
    if len(self.__previousZooms) > 0:
      self.__zoomSelection = self.__previousZooms.pop()
      self.zoom()

  def mouseDown(self, event):
    x0, y0, x1, y1 = self.__zoomSelection
    self.__dragging = 0
    if self.inside(event.x, event.y):
      (x0, y0) = self.invtransform(event.x, event.y)
      self.__zoomSelection = [x0,y0,x1, y1]
      self.__dragging = 1

    self.__zoomSelection = [x0,y0,x1, y1]
    self.marker_create("line", name="marking rectangle", dashes=(1, 2))
    self.bind(sequence="<Motion>",  func=self.mouseDrag)

  def clear(self):

    """
    clears the plot
    """
    for element in self.element_show():
      self.element_delete(element)

class SummaryWindow:

  """
  SummaryWindow

  A nice simple summary window for DatasetPlot graphs

  """

  def __init__(self, plotView, row, xdata=None, parent=None):

    self.dialog = Pmw.Dialog(parent, hull_width=640, hull_height=480)
    # set up subframes
    self.dataset = plotView.getDataset()
    self.plotView = plotView
    if xdata is None:
      self.xdata = tuple(range(self.dataset.getNumCols()))
    else:
      self.xdata = tuple(xdata)

    self.createPanes()
    self.addRow(row)
    self.parent = parent

  def createPanes(self):

    # setup all the information frames.

    # this is the base pane with 2 components (a top and bottom)
    self.panes = Pmw.PanedWidget(self.dialog.interior())
    self.panes.add('top')
    self.panes.add('bottom', min=100)

    # now we split the top into a left and right
    self.topPanes = Pmw.PanedWidget(self.panes.pane('top'),
                                    orient='horizontal')
    self.topPanes.add('left', size=300)
    self.topPanes.add('right')

    # now we split the bottom into a left and right
    self.bottomPanes = Pmw.PanedWidget(self.panes.pane('bottom'),
                                       orient='horizontal')
    self.bottomPanes.add('left', min=100, size=300)
    self.bottomPanes.add('right')
    
    # create the plot in the upper left corner
    self.plot = IPlot(self.topPanes.pane('left'))
    self.plot.pack(fill='both', expand=1)
    
    # create the labeling listing in the lower left corner
    self.listBox  = Pmw.ScrolledListBox(self.bottomPanes.pane('left'))
    self.listBox.pack(expand=1, fill='both')

    # this is a list of OptionMenus
    self.labelActions = []
    
    self.topPanes.pack(expand=1, fill='both')
    self.bottomPanes.pack(expand=1, fill='both')
    self.panes.pack(expand=1, fill='both')

  def __listSelection(self, row):

    """
    __listSelection(self, event)

    """
    sels = self.listBox.getcurselection()
    if len(sels) > 0:
      #sels[0].split(':')[1].split(',')[0].strip())
      labelings = self.dataset.getLabelings()
      index = map(str, labelings).index(sels[0])
      labeling = labelings[index]
      self.__populateLabelActions(labeling, row)
    
  def __populateLabelActions(self, labeling, row):

    """
    __populateCheckButtons(self, labeling, label)

    """

    map(lambda x: x.destroy(), self.labelActions)
    self.labelActions = []
    for lab in labeling.getLabelsByRow(row):   
      menu = Pmw.OptionMenu (self.bottomPanes.pane('right'),
                             labelpos = 'w',
                             label_text = str(lab),
                             items = ('default display',
                                      'Highlight This Group',
                                      'Show Only This Group',
                                      'Hide This Group'),
                             initialitem = 'default display',
                             menubutton_width = 10)
                             
      
      menu.configure(command=lambda selection, labeling=labeling, label=lab:
                     self._groupAction(selection, labeling, label))

      menu.pack(expand=1, fill='x')
      self.labelActions.append(menu)
      
    Pmw.alignlabels(self.labelActions)

  def _groupAction(self, sel, labeling, label):

    """
    _groupAction (self, sel, labeling, label)

    """
    primaryLabeling = self.plotView.getAnnotationMapper ().getPrimaryLabeling()
    plot = self.plotView._getPlot()

    if primaryLabeling is None:
      elements = map(str, labeling.getRowsByLabel(label))
    else:
      elements = map(lambda x: primaryLabeling.getLabelsByRow(x)[0],
                     labeling.getRowsByLabel(label))
      
    if sel ==  'Highlight This Group':
      map(plot.element_activate, elements)

    elif sel == 'Show Only This Group':
      map(lambda x: plot.element_configure(x, hide=1),
          (listOps.difference(plot.element_names(), elements).items()))
          

      
    elif sel == 'Hide This Group':
      map(lambda x: plot.element_configure(x, hide=1), elements)

    else:
      map(lambda x: plot.element_configure(x, hide=0), plot.element_names())
      map(plot.element_deactivate, plot.element_names())
      
  def __populateListBox(self, row):

    """
    fill the list box with all the applicable labelings.
    """

    labelings = tuple(map(str, filter(lambda x: len(x.getLabelsByRow(row))>0,
                                      self.dataset.getLabelings())))

    
    self.listBox.setlist(labelings)
    self.listBox.configure(selectioncommand=lambda row=row: self.__listSelection(row))

  def __cBox(self, f, label, items):

    """
    Make a customized Combobox 
    """

    box = Pmw.ComboBox(f, label_text = label, 
                       labelpos = 'w', scrolledlist_items = items)
    
    box.pack(fill = 'both', expand = 1, padx = 8, pady = 8)
    return box
    
  def addRow(self, row):

    """
    addRow(self,row)
    """
    primaryLabeling = self.plotView.getAnnotationMapper().getPrimaryLabeling()
    secondaryLabeling = self.plotView.getAnnotationMapper().getPrimaryLabeling()
    if primaryLabeling is None:
      label = str(row)
    else:
      label = primaryLabeling.getLabelsByRow(row)[0]
    
    self.plot.line_create(label,
                          ydata=tuple(self.dataset.getRowData(row)),
                          xdata=self.xdata)

    if secondaryLabeling is not None:
      self.plot.configure(title=secondaryLabeling.getLabelsByRow(row)[0])
    else:
      self.plot.configure(title=label)

    self.__populateListBox(row)

  def clear(self):
    map(self.plot.element_delete, self.plot.element_show())
    
##
#
# Just a few module helper functions
#

def rgbToString(rgb):

  """
  rgbToString(rgb):

  turns an rgb tuple into a hex string
  """
  colorString = ''
  for hue in rgb:
    tmpString = hex(int(hue*255))[2:]
    if len(tmpString) <2:
      tmpString = "0%s"%(tmpString)
    colorString += tmpString
  color = "#%s"%(colorString)
  return(color)


def plot(values, yvalues=None,
         xerror=None, yerror=None,
         xmin=None, xmax=None, ymin=None, ymax=None,
         plotStyle="line", color=None, fileName=None,
         seriesName = None,  previousPlot=None , master=None, pack=1):

    """
    A wrapper around the IPlot class to provide a fast easy interface for plotting.
    
    Usage: plot(x,y, <options>)
              This creates a plot of the x-vector vs the
              y-vector.  x and y can be either numeric arrays
              r standard python lists

           plot(y , <options>)

              if y is a 1d array/list/tuple it creates a plot of the values vs thier index
              if y is a 2d array/list/tuple the values of each row is plotted as a data series vs their index
              if y is a dataset, it is treated like a 2d array, only a plotView and a datasetPlotter is returned.

        optional Parameters:
              plotStyles -> on of the following: 'line', 'points', 'bar'
              xmin -> float/int
              ymin -> float/int
              xmax -> float/int
              ymax -> float/int
              xerror -> NumericArray of len(values)/ if values is a 2d array, xerror should be the same size
              yerror -> NumericArray of len(values)/ if values is a 2d array, yerror should be the same size
              fileName -> name of postscript file to create
              color -> RGB string color (ie '#FFFFFF for black') or ['red', 'blue', 'green', 'orange', 'black', 'white', 'yellow'].  

    """
    # this little block calculates numColors then generates the ordered list such that simular colors are well seperated.
    # This is fairly good for arbitrary coloring of lines.

    if color is None:
      numColors = 7 
      #colors = map(rgbToString,
      #             [colorsys.hsv_to_rgb(h,s,v) for h,s,v in
      #              zip(Numeric.arange(0, .7, .7/numColors), [1]*numColors, [1]*numColors)])
      colors = ['blue', 'red', 'green', 'orange', 'yellow', 'cyan', 'purple',]
    
    if previousPlot:
      g = previousPlot
      numElements = len(g.element_names())
      if color is None:
        index = numElements%(numColors/3)+((numElements%3)*numColors/3)
        color = colors[index]
    else:
      g = IPlot(master=master)
  

    if isinstance(values, Dataset):
      dp = DatasetPlot()
      v = DatasetRowPlotView(values)
      dp.plot(v)
      return(dp,v)
    elif isinstance(values, sciHistogram.Histogram):
      plotStyle='bar'
      g.configure(barwidth=str(values.bin_width))
      yvalues = values.array[:,1]
      data = values.array[:,0]
    else:
      try:
        data = Numeric.array(values)
      except:
        sys.stderr.write("Not a supported plotting type\n")
        return

    if len(Numeric.shape(data)) == 1:
      #single vector to be plotted against its index

      if yvalues is None:
        yvalues = tuple(data)
        xvalues = tuple(range(len(yvalues)))
      else:
        xvalues = tuple(data)
        yvalues = tuple(yvalues)
      if seriesName is None:
        seriesName = str(len(g.element_names()))

      args = {}
      if plotStyle == 'line':
        g.line_create(seriesName, xdata = xvalues, ydata = yvalues, color=color)
      elif plotStyle == 'points':
        g.line_create(seriesName, xdata = xvalues, ydata = yvalues, color=color, linewidth=0)
      elif plotStyle == 'bar':
        g.bar_create(seriesName, xdata = xvalues, ydata = yvalues, background=color, foreground=color)
      else:
        sys.stderr.write("Unknown plot style (%s), try: 'line', 'bar', or 'points'\n"%(plotStyle))

    else:
      # plot each row in the datamatrix againts yvalues or the index
      for i in range(len(data)):
        if xerror:
          xerrTmp = xerror[i]
        else:
          xerrTmp = None
        if yerror:
          yerrTmp = yerror[i]
        else:
          yerrTmp = None
        if color:
          colorTmp = color[i]
        else:
          colorTmp = None
        plot(data[i],
             xerror=xerrTmp,
             yerror=yerrTmp,
             xmin = xmin, xmax= xmax,
             ymin = ymin, ymax = ymax,
             plotStyle = plotStyle,
             color = colorTmp,
             previousPlot=g)

    if pack: 
      g.pack(expand=1, fill='both')

    if fileName:
        g.postscript(fileName)
    return(g)
  
def boxPlot(ds, master=None, dimension=0):
  """
  Generate a box plot, dimension = 0 (column-wise) dimension=1 (row-wise) 
  ds can either be a dataset or an numeric array
  """
  if not isinstance(ds, Dataset):
    ds = Dataset(ds)
  
  data = ds.getData()  
  if dimension == 1:
    data = Numeric.transpose(data)
 
  ## compute the statistics
  
  rows, cols = Numeric.shape(data)
  medians = MLab.median(data)
  means = MLab.mean(data)
  sortedData = Numeric.sort(data,0)
  quartiles25 = sortedData[int(rows*.25),:]
  quartiles75 = sortedData[int(rows*.75),:]
  iqr = quartiles75-quartiles25
  
  upperOutliers = [filter(lambda x: x > quartiles75 + (iqr*1.5), sd) for sd in Numeric.transpose(sortedData)] 
  upperIQRextreme = []
  for set,sd in zip(upperOutliers, Numeric.transpose(sortedData)):
    if len(set)>0:
      upperIQRextreme.append(set[0])
    else:
      upperIQRextreme.append(sd[-1])
  
  lowerOutliers = [filter(lambda x: x < quartiles25 - (iqr*1.5), sd) for sd in Numeric.transpose(sortedData)]
  lowerIQRextreme = []
  for set, sd in zip(lowerOutliers, Numeric.transpose(sortedData)):
    if len(set)>0:
      lowerIQRextreme.append(set[-1])
    else:
      lowerIQRextreme.append(sd[0])
  
  ## render the plot
  g = IPlot(master=master)
  g.configure(title='Box Plot')
  g.legend_configure(hide=1)
  g.xaxis_configure(min=-1, max=cols+1)

  g.line_create('means',  xdata=tuple(range(cols)),
                          ydata = tuple(means),
                          linewidth=0,
                          pixels='0.04i',
                          color='black',
                          symbol='circle')
  
  g.line_create('25thQuarts', xdata=tuple(listOps.unravel([[x,x] for x in range(cols)])),
                              ydata=tuple(listOps.unravel(map(lambda x,y: [x,y], medians, quartiles25 ))),
                              linewidth=40,
                              pixels='0.0i',
                              color='lightblue',
                              trace='decreasing')
  g.line_create('75thQuarts', xdata=tuple(listOps.unravel([[x,x] for x in range(cols)])),
                              ydata=tuple(listOps.unravel(map(lambda x,y: [x,y], medians, quartiles75 ))),
                              linewidth=40,
                              pixels='0.0i',
                              color='blue',
                              trace='decreasing')
  
  g.line_create('iqrHigh', xdata=tuple(listOps.unravel([[x,x] for x in range(cols)])),
                           ydata=tuple(listOps.unravel(map(lambda x,y: [x,y], 
                                                           quartiles75, upperIQRextreme ))),
                           linewidth=3,
                           pixels='0.0',
                           color='black',
                           trace='decreasing')
  g.line_create('iqrLow', xdata=tuple(listOps.unravel([[x,x] for x in range(cols)])),
                          ydata=tuple(listOps.unravel(map(lambda x,y: [x,y], 
                                                          quartiles25, lowerIQRextreme ))),
                          linewidth=3,
                          pixels='0.0',
                          color='black',
                          trace='decreasing')
                           
  g.line_create('upperOutliers', xdata=tuple(listOps.unravel([[x]*len(yvalues) for x,yvalues in zip(range(cols),upperOutliers)])),
                                 ydata=tuple(listOps.unravel(upperOutliers)),
                                 linewidth=0,
                                 pixels ='.08i',
                                 symbol = 'scross',
                                 color  = 'red')

  g.line_create('lowerOutliers', xdata=tuple(listOps.unravel([[x]*len(yvalues) for x,yvalues in zip(range(cols),lowerOutliers)])),
                                 ydata=tuple(listOps.unravel(lowerOutliers)),
                                 linewidth=0,
                                 pixels ='.08i',
                                 symbol = 'scross',
                                 color  = 'red')
 
  g.pack(expand=1, fill='both')
  return(g)
 
