modularize the plotting system

Issue #7 resolved
Thomas Gilgenast created an issue

plotting functions should modify a target axis (or create new axes as a sane default) and return it

there may be an optional interface to allow plotting functions to set xlabels/ylabels/titles/etc., but manually setting plot attributes from the pyplot API after calling the function should be possible

some research may be done on supporting an API to specify or control the ordering of overlay plots - though most likely this can be done by simply requiring that all plotting functions accept OrderedDicts as well as native dicts, see the current implementation in the expected plotter

there should be a unified API for controlling figure aesthetics, namely to control seaborn themes (including figuring out when to turn off the seaborn styles, e.g. when plotting heatmaps), color cycles and mappings, etc.

plotting functions should support an outfile kwarg that will save the resulting plot automatically, recapitulating the old behavior

the heatmap plotter in particular should allocate a grid initially (via a function that creates and returns a "heatmap plotting context" to be used for coordinate conversions, etc.) and then accept a sequence of independent plotting commands to draw the desired pieces over each other one at a time - this will remove the current kwarg bloat on the plot_heatmap() function

zoom windows will become a parameter of the heatmap plotting context creation

Comments (6)

  1. Thomas Gilgenast reporter

    re overlay plots: it would be nice to standardize these

    in particular, the current implementation of the expected model overlay plotting contains a lot of typechecking and "fake overloading" (repurposing the same function name to serve two distinct ends) just to allow overlaying of multiple models - it should be refactored into separate functions

  2. Thomas Gilgenast reporter

    the new plotting system could also support an i5c-style annotation API, which would allow a plotting context to be created that tracks metadata for different replicates and allows simple mapping of colors to different attributes of the metadata

  3. Thomas Gilgenast reporter

    breaking up plot_heatmap() is a critical step in the modularization of the plotting system

    to do this, we propose leveraging axes_grid

    an example code snippet is:

    import matplotlib.pyplot as plt
    from mpl_toolkits.axes_grid1 import make_axes_locatable
    import numpy as np
    
    ax = plt.subplot(111)
    
    im = ax.imshow(np.arange(100).reshape((10,10)))
    
    divider = make_axes_locatable(ax) 
    
    def add_trig_things(divider, color):
        xs = np.arange(0, 10)
        htrack = divider.append_axes("bottom", size="10%", pad=0.0)
        vtrack = divider.append_axes("left", size="10%", pad=0.0)
        htrack.plot(xs, np.sin(xs), c=color)
        vtrack.plot(np.tan(xs), xs, c=color)
    
    add_trig_things(divider, 'b')
    add_trig_things(divider, 'r')
    add_trig_things(divider, 'g')
    add_trig_things(divider, 'purple')
    
    plt.savefig('test.png', bbox_inches='tight')
    

    we could capture a reference to the divider instance as well as named references to the component axes using a class structure as follows:

    class Heatmap(object):
        def __init__(self, array, **kwargs):
            self.axes = {}
            self.fig, (self.axes['heatmap'],) = plt.subplots()
            self.array = array
            self.im = self.axes['heatmap'].imshow(self.array, **kwargs)
            self.divider = make_axes_locatable(self.axes['heatmap'])
    
        def add_colorbar(self):
            self.axes['colorbar'] = self.divider.append_axes("right", size="10%", pad=0.1)
            self.fig.colorbar(self.im, cax=self.axes['colorbar'])
    
        def add_horizontal_track(self, name):
            self.axes[name] = self.divider.append_axes("bottom", size="10%", pad=0.1)
            return self.axes[name]
    
        def add_horizontal_gene_track(self, name='gene_track_h'):
            self.add_horizontal_track(name)
            plot_hgene(.., self.axes[name], ...)
    
        def save(self, filename):
            self.fig.savefig(filename, dpi=800, bbox_inches='tight')
    
    h = heatmap(counts['Sox2'])
    h.add_colorbar()
    h.add_horizontal_track('IS horizontal')
    h.axes['IS horizontal'].plot(...)
    h.save('somefile.png')
    draw_communities(h.axes['heatmap'], ...)
    outline_clusters(h.axes['heatmap'], ...)
    
  4. Thomas Gilgenast reporter

    as of 0a5b142, lib5c.plotters.extendable.ExtendableHeatmap recapitulates all features of the old heatmap plotter (lib5c.plotters.heatmap.plot_heatmap()) and this design goal, closing this issue

  5. Log in to comment