示例#1
0
 def _serve(self, blocking=True):
     """
     start up a server to serve the files for this vis.
     """
     msgparams = (self.port, self.name)
     url = "http://localhost:%s/%s.html"%msgparams
     if self._server_thread is None or self._server_thread.active_count() == 0:
         Handler = CustomHTTPRequestHandler
         Handler.filemap = self.filemap
         Handler.logging = self.logging
         try:
             self.httpd = ThreadedHTTPServer(("", self.port), Handler)
         except Exception, e:
             print "Exception %s"%e
             return False
         if blocking:
             logging.info('serving forever on port: %s'%msgparams[0])
             msg = "You can find your chart at " + url
             print msg
             print "Ctrl-C to stop serving the chart and quit!"
             self._server_thread = None
             self.httpd.serve_forever()
         else:
             logging.info('serving asynchronously on port %s'%msgparams[0])
             self._server_thread = threading.Thread(
                 target=self.httpd.serve_forever
             )
             self._server_thread.daemon = True
             self._server_thread.start()
             msg = "You can find your chart at " + url
             print msg
示例#2
0
class Figure(object):
    def __init__(self, name, width, height, interactive, font, logging,  template, port, **kwargs):

        # store data
        self.name = '_'.join(name.split())
        d3py_path = os.path.abspath(os.path.dirname(__file__))
        self.filemap = {
            "static/d3.js":{
                "fd":open(d3py_path+"/d3.js","r"), 
                "timestamp":time.time()
            },
        }

        # Networking stuff
        self.port = port
        self._server_thread = None
        self.httpd = None

        # interactive is True by default as this is designed to be a command line tool
        # we do not want to block interaction after plotting.
        self.interactive = interactive
        self.logging = logging

        # initialise strings
        self.js = JS.JavaScript()
        self.margins = {
            "top": 10, 
            "right": 20, 
            "bottom": 25, 
            "left": 60, 
            "height":height, 
            "width":width
        }
        
        # we use bostock's scheme http://bl.ocks.org/1624660
        self.css = CSS()
        self.html = ""
        self.template = template or "".join(open(d3py_path+'/d3py_template.html').readlines())
        self.js_geoms = JS.JavaScript()
        self.css_geoms = CSS()
        self.geoms = []
        # misc arguments - these go into the css!
        self.font = font
        self.args = {
            "width": width - self.margins["left"] - self.margins["right"],
            "height": height - self.margins["top"] - self.margins["bottom"],
            "font-family": "'%s'; sans-serif"%self.font
        }
        kwargs = dict([(k[0].replace('_','-'), k[1]) for k in kwargs.items()])
        self.args.update(kwargs)

    def _build(self):
        logging.debug('building chart')
        self._build_js()
        self._build_css()
        self._build_html()
        self._build_geoms()

    def update(self):
        logging.debug('updating chart')
        self._build()
        self.save()

    def save(self):
        logging.debug('saving chart')
        self._save_data()
        self._save_css()
        self._save_js()
        self._save_html()

    def _clanup(self):
        raise NotImplementedError


    def __enter__(self):
        self.interactive = False
        return self

    def __exit__(self, ex_type, ex_value, ex_tb):
        if ex_tb is not None:
            print "Cleanup after exception: %s: %s"%(ex_type, ex_value)
        self._cleanup()

    def __del__(self):
        self._cleanup()

    def ion(self):
        """
        Turns interactive mode on ala pylab
        """
        self.interactive = True
    
    def ioff(self):
        """
        Turns interactive mode off
        """
        self.interactive = False

    def _set_data(self):
        self.update()

    def _add_geom(self, geom):
        self.geoms.append(geom)
        self.save()
    
    def _build_css(self):
        #._build up the basic css
        chart = {}
        chart.update(self.args)
        self.css["#chart"] = chart

    def _build_html(self):
        # we start the html using a template - it's pretty simple
        self.html = self.template
        self.html = self.html.replace("{{ name }}", self.name)
        self.html = self.html.replace("{{ font }}", self.font)
        self._save_html()

    def _build_geoms(self):
        self.js_geoms = JS.JavaScript()
        self.css_geoms = CSS()
        for geom in self.geoms:
            self.js_geoms.merge(geom._build_js())
            self.css_geoms += geom._build_css()

    def __add__(self, geom):
        self._add_geom(geom)

    def __iadd__(self, geom):
        self._add_geom(geom)
        return self

    def _data_to_json(self):
        raise NotImplementedError
        
    def _build_js(self):
        draw = JS.Function("draw", ("data",))
        draw += "var margin = %s;"%json.dumps(self.margins).replace('""','')
        draw += "    width = %s - margin.left - margin.right"%self.margins["width"]
        draw += "    height = %s - margin.top - margin.bottom;"%self.margins["height"]
        # this approach to laying out the graph is from Bostock: http://bl.ocks.org/1624660
        draw += "var g = " + JS.Selection("d3").select("'#chart'") \
            .append("'svg'") \
            .attr("'width'", 'width + margin.left + margin.right + 25') \
            .attr("'height'", 'height + margin.top + margin.bottom + 25') \
            .append("'g'") \
            .attr("'transform'", "'translate(' + margin.left + ',' + margin.top + ')'")

        self.js = JS.JavaScript() + draw + JS.Function("init")

    def _save_data(self,directory=None):
        """
        save a json representation of the figure's data frame
        
        Parameters
        ==========
        directory : str
            specify a directory to store the data in (optional)
        """
        # write data
        filename = "%s.json"%self.name
        self.filemap[filename] = {"fd":StringIO(self._data_to_json()),
                "timestamp":time.time()}

    def _save_css(self):
        # write css
        filename = "%s.css"%self.name
        css = "%s\n%s"%(self.css, self.css_geoms)
        self.filemap[filename] = {"fd":StringIO(css),
                "timestamp":time.time()}

    def _save_js(self):
        # write javascript
        final_js = JS.JavaScript()
        final_js.merge(self.js)
        final_js.merge(self.js_geoms)

        filename = "%s.js"%self.name
        js = "%s"%final_js
        self.filemap[filename] = {"fd":StringIO(js),
                "timestamp":time.time()}

    def _save_html(self):
        # update the html with the correct port number
        self.html = self.html.replace("{{ port }}", str(self.port))
        # write html
        filename = "%s.html"%self.name
        self.filemap[filename] = {"fd":StringIO(self.html),
                "timestamp":time.time()}

    def show(self, interactive=None):
        self.update()
        self.save()
        if interactive is not None:
            blocking = not interactive
        else:
            blocking = not self.interactive

        if blocking:
            self._serve(blocking=True)
        else:
            # if not blocking, we serve the 
            self._serve(blocking=False)
            # fire up a browser
            webbrowser.open_new_tab("http://localhost:%s/%s.html"%(self.port, self.name))

    def _serve(self, blocking=True):
        """
        start up a server to serve the files for this vis.
        """
        msgparams = (self.port, self.name)
        url = "http://localhost:%s/%s.html"%msgparams
        if self._server_thread is None or self._server_thread.active_count() == 0:
            Handler = CustomHTTPRequestHandler
            Handler.filemap = self.filemap
            Handler.logging = self.logging
            try:
                self.httpd = ThreadedHTTPServer(("", self.port), Handler)
            except Exception, e:
                print "Exception %s"%e
                return False
            if blocking:
                logging.info('serving forever on port: %s'%msgparams[0])
                msg = "You can find your chart at " + url
                print msg
                print "Ctrl-C to stop serving the chart and quit!"
                self._server_thread = None
                self.httpd.serve_forever()
            else:
                logging.info('serving asynchronously on port %s'%msgparams[0])
                self._server_thread = threading.Thread(
                    target=self.httpd.serve_forever
                )
                self._server_thread.daemon = True
                self._server_thread.start()
                msg = "You can find your chart at " + url
                print msg
示例#3
0
class SimpleServer(displayable):
    """
    Use Python's SimpleHTTPServer class to present this resulting d3 output
    to the user. 
    """
    def __init__(self, host="localhost", port=8000, 
        interactive=False, logging=False):

        self._fig = None
        self._host = host
        self._port = port
        self._server_thread = None
        self._httpd = None
        # interactive is True by default as this is designed to be a command line tool
        # we do not want to block interaction after plotting.
        self._interactive = interactive

    @property
    def fig(self):
        return self._fig

    @fig.setter
    def fig(self, fig):
        self._fig = fig

    @property
    def host(self):
        return self._host 
    
    @property
    def port(self):
        return self._port

    def ion(self):
        """
        Turns interactive mode on ala pylab
        """
        self._interactive = True
    
    def ioff(self):
        """
        Turns interactive mode off
        """
        self._interactive = False

    def show(self, interactive=None):
        if interactive is not None:
            blocking = not interactive
        else:
            blocking = not self._interactive

        if blocking:
            self._serve(blocking=True)
        else:
            # if not blocking, we serve the 
            self._serve(blocking=False)
            # fire up a browser
            webbrowser.open_new_tab("http://%s:%s/%s.html"%(self.host,self.port, self.name))

    def _serve(self, blocking=True):
        """
        start up a server to serve the files for this vis.
        """
        msgparams = (self.host, self.port, self._fig.name)
        url = "http://%s:%s/%s.html"%msgparams
        if self._server_thread is None or self._server_thread.active_count() == 0:
            Handler = CustomHTTPRequestHandler
            Handler.filemap = self._fig.filemap
            Handler.logging = self._fig.logging
            try:
                self._httpd = ThreadedHTTPServer(("", self.port), Handler)
            except Exception, e:
                print "Exception %s"%e
                return False
            if blocking:
                logging.info('serving forever on port: %s'%msgparams[1])
                msg = "You can find your chart at " + url
                print msg
                print "Ctrl-C to stop serving the chart and quit!"
                self._server_thread = None
                self._httpd.serve_forever()
            else:
                logging.info('serving asynchronously on port %s'%msgparams[1])
                self._server_thread = threading.Thread(
                    target=self._httpd.serve_forever
                )
                self._server_thread.daemon = True
                self._server_thread.start()
                msg = "You can find your chart at " + url
                print msg
示例#4
0
class Figure(object):
    '''Abstract Base Class for all figures'''
    def __init__(self, name, width, height, interactive, font, logging,
                 template, host, port, **kwargs):
        '''
        Figure is the abstract base class for all figures. Currently 
        subclassed by pandas_figure and networkx_figure. 
        
        Parameters:
        -----------
        name: string
            Name of visualization; will appear in title bar of the webpage, 
            and in the folder where files are stored. 
        width : int 
            Width of the figure in pixels
        height : int 
            Height of the figure in pixels
        interactive : boolean
            Set to false if you are drawing the graph using a script and
            not in the command line. 
        font : string
            Name of the font you'd like to use. See     
            http://www.google.com/webfonts for options
        logging: 
            Logging via the sandard Python loggin library
        template: string
            HTML template for figure. Defaults to /d3py_template (Also, when 
            building your own HTML, please see the default template for 
            correct usage of {{ name }}, {{ host }}, {{ port }}, and 
            {{ font }}
        host: string
            Generally default to 'localhost' for local plotting
        port: int
            Generally defaults to 8000 for local plotting
        
        '''

        # store data
        self.name = '_'.join(name.split())
        d3py_path = os.path.abspath(os.path.dirname(__file__))
        self.filemap = {
            "static/d3.js": {
                "fd": open(d3py_path + "/d3.js", "r"),
                "timestamp": time.time()
            },
        }

        # Networking stuff
        self.host = host
        self.port = port
        self._server_thread = None
        self.httpd = None
        '''Interactive is true by default, as this is designed to be a command
        line tool. We do not want to block interaction after plotting.'''
        self.interactive = interactive
        self.logging = logging

        # initialise strings
        self.js = JS.JavaScript()
        self.margins = {
            "top": 10,
            "right": 20,
            "bottom": 25,
            "left": 60,
            "height": height,
            "width": width
        }

        # we use bostock's scheme http://bl.ocks.org/1624660
        self.css = CSS()
        self.html = ""
        self.template = template or resource_string('d3py',
                                                    'd3py_template.html')
        self.js_geoms = JS.JavaScript()
        self.css_geoms = CSS()
        self.geoms = []
        # misc arguments - these go into the css!
        self.font = font
        self.args = {
            "width": width - self.margins["left"] - self.margins["right"],
            "height": height - self.margins["top"] - self.margins["bottom"],
            "font-family": "'%s'; sans-serif" % self.font
        }

        kwargs = dict([(k[0].replace('_', '-'), k[1]) for k in kwargs.items()])
        self.args.update(kwargs)

    def update(self):
        '''Build or update JS, CSS, & HTML, and save all data'''
        logging.debug('updating chart')
        self._build()
        self.save()

    def _build(self):
        '''Build all JS, CSS, HTML, and Geometries'''
        logging.debug('building chart')
        if hasattr(self, 'vega'):
            self.vega.build_vega()
        self._build_js()
        self._build_css()
        self._build_html()
        self._build_geoms()

    def _build_css(self):
        '''Build basic CSS'''
        chart = {}
        chart.update(self.args)
        self.css["#chart"] = chart

    def _build_html(self):
        '''Build HTML, either via 'template' argument or default template 
        at /d3py_template.html.'''
        self.html = self.template
        self.html = self.html.replace("{{ name }}", self.name)
        self.html = self.html.replace("{{ font }}", self.font)
        self._save_html()

    def _build_geoms(self):
        '''Build D3py CSS/JS geometries. See /geoms for more details'''
        self.js_geoms = JS.JavaScript()
        self.css_geoms = CSS()
        for geom in self.geoms:
            self.js_geoms.merge(geom._build_js())
            self.css_geoms += geom._build_css()

    def _build_js(self):
        '''Build Javascript for Figure'''
        draw = JS.Function("draw", ("data", ))
        draw += "var margin = %s;" % json.dumps(self.margins).replace('""', '')
        draw += "    width = %s - margin.left - margin.right" % self.margins[
            "width"]
        draw += "    height = %s - margin.top - margin.bottom;" % self.margins[
            "height"]
        # this approach to laying out the graph is from Bostock: http://bl.ocks.org/1624660
        draw += "var g = " + JS.Selection("d3").select("'#chart'") \
            .append("'svg'") \
            .attr("'width'", 'width + margin.left + margin.right + 25') \
            .attr("'height'", 'height + margin.top + margin.bottom + 25') \
            .append("'g'") \
            .attr("'transform'", "'translate(' + margin.left + ',' + margin.top + ')'")

        self.js = JS.JavaScript() + draw + JS.Function("init")

    def _cleanup(self):
        raise NotImplementedError

    def __enter__(self):
        self.interactive = False
        return self

    def __exit__(self, ex_type, ex_value, ex_tb):
        if ex_tb is not None:
            print "Cleanup after exception: %s: %s" % (ex_type, ex_value)
        self._cleanup()

    def __del__(self):
        self._cleanup()

    def ion(self):
        """
        Turns interactive mode on ala pylab
        """
        self.interactive = True

    def ioff(self):
        """
        Turns interactive mode off
        """
        self.interactive = False

    def _set_data(self):
        '''Update JS, CSS, HTML, save all'''
        self.update()

    def __add__(self, geom):
        '''Add d3py.geom object to the Figure'''
        if isinstance(figure, vega.Vega):
            self._add_vega(figure)
        else:
            self._add_geom(figure)

    def __iadd__(self, figure):
        '''Add d3py.geom or d3py.vega object to the Figure'''
        if isinstance(figure, vega.Vega):
            self._add_vega(figure)
        else:
            self._add_geom(figure)
        return self

    def _add_vega(self, figure):
        '''Add D3py.Vega Figure'''
        self.vega = figure
        self.vega.tabular_data(self.data,
                               columns=self.columns,
                               use_index=self.use_index)
        self.template = resource_string('d3py', 'vega_template.html')
        self._save_vega()

    def _add_geom(self, geom):
        '''Append D3py.geom to existing D3py geoms'''
        self.geoms.append(geom)
        self.save()

    def save(self):
        '''Save data and all Figure components: JS, CSS, and HTML'''
        logging.debug('saving chart')
        if hasattr(self, 'vega'):
            self._save_vega()
        self._save_data()
        self._save_css()
        self._save_js()
        self._save_html()

    def _save_data(self, directory=None):
        """
        Build file map (dir path and StringIO for output) of data
        
        Parameters:
        -----------
        directory : str
            Specify a directory to store the data in (optional)
        """
        # write data
        filename = "%s.json" % self.name
        self.filemap[filename] = {
            "fd": StringIO(self._data_to_json()),
            "timestamp": time.time()
        }

    def _save_vega(self):
        '''Build file map (dir path and StringIO for output) of Vega'''
        vega = json.dumps(self.vega.vega, sort_keys=True, indent=4)
        self.filemap['vega.json'] = {
            "fd": StringIO(vega),
            "timestamp": time.time()
        }

    def _save_css(self):
        '''Build file map (dir path and StringIO for output) of CSS'''
        filename = "%s.css" % self.name
        css = "%s\n%s" % (self.css, self.css_geoms)
        self.filemap[filename] = {
            "fd": StringIO(css),
            "timestamp": time.time()
        }

    def _save_js(self):
        '''Build file map (dir path and StringIO for output) of data'''
        final_js = JS.JavaScript()
        final_js.merge(self.js)
        final_js.merge(self.js_geoms)

        filename = "%s.js" % self.name
        js = "%s" % final_js
        self.filemap[filename] = {"fd": StringIO(js), "timestamp": time.time()}

    def _save_html(self):
        '''Save HTML data. Will save Figure name to 'name.html'. Will also
        replace {{ port }} and {{ host }} fields in template with
        Figure.port and Figure.host '''
        self.html = self.html.replace("{{ port }}", str(self.port))
        self.html = self.html.replace("{{ host }}", str(self.host))
        # write html
        filename = "%s.html" % self.name
        self.filemap[filename] = {
            "fd": StringIO(self.html),
            "timestamp": time.time()
        }

    def _data_to_json(self):
        raise NotImplementedError

    def show(self, interactive=None):
        self.update()
        self.save()
        if interactive is not None:
            blocking = not interactive
        else:
            blocking = not self.interactive

        if blocking:
            self._serve(blocking=True)
        else:
            # if not blocking, we serve the
            self._serve(blocking=False)
            # fire up a browser
            webbrowser.open_new_tab("http://%s:%s/%s.html" %
                                    (self.host, self.port, self.name))

    def display(self, width=700, height=400):
        html = "<iframe src=http://%s:%s/%s.html width=%s height=%s>" % (
            self.host, self.port, self.name, width, height)
        IPython.core.display.HTML(html)

    def _serve(self, blocking=True):
        """
        start up a server to serve the files for this vis.
        """
        msgparams = (self.host, self.port, self.name)
        url = "http://%s:%s/%s.html" % msgparams
        if self._server_thread is None or self._server_thread.active_count(
        ) == 0:
            Handler = CustomHTTPRequestHandler
            Handler.filemap = self.filemap
            Handler.logging = self.logging
            try:
                self.httpd = ThreadedHTTPServer(("", self.port), Handler)
            except Exception, e:
                print "Exception %s" % e
                return False
            if blocking:
                logging.info('serving forever on port: %s' % msgparams[1])
                msg = "You can find your chart at " + url
                print msg
                print "Ctrl-C to stop serving the chart and quit!"
                self._server_thread = None
                self.httpd.serve_forever()
            else:
                logging.info('serving asynchronously on port %s' %
                             msgparams[1])
                self._server_thread = threading.Thread(
                    target=self.httpd.serve_forever)
                self._server_thread.daemon = True
                self._server_thread.start()
                msg = "You can find your chart at " + url
                print msg
示例#5
0
文件: figure.py 项目: ABcDexter/d3py
class Figure(object):
    '''Abstract Base Class for all figures'''
    
    def __init__(self, name, width, height, interactive, font, logging, 
                 template, host, port, **kwargs):
        '''
        Figure is the abstract base class for all figures. Currently 
        subclassed by pandas_figure and networkx_figure. 
        
        Parameters:
        -----------
        name: string
            Name of visualization; will appear in title bar of the webpage, 
            and in the folder where files are stored. 
        width : int 
            Width of the figure in pixels
        height : int 
            Height of the figure in pixels
        interactive : boolean
            Set to false if you are drawing the graph using a script and
            not in the command line. 
        font : string
            Name of the font you'd like to use. See     
            http://www.google.com/webfonts for options
        logging: 
            Logging via the sandard Python loggin library
        template: string
            HTML template for figure. Defaults to /d3py_template (Also, when 
            building your own HTML, please see the default template for 
            correct usage of {{ name }}, {{ host }}, {{ port }}, and 
            {{ font }}
        host: string
            Generally default to 'localhost' for local plotting
        port: int
            Generally defaults to 8000 for local plotting
        
        '''

        # store data
        self.name = '_'.join(name.split())
        d3py_path = os.path.abspath(os.path.dirname(__file__))
        self.filemap = {"static/d3.js":{"fd":open(d3py_path+"/d3.js","r"), 
                                        "timestamp":time.time()},}
                                                                               
        # Networking stuff
        self.host = host
        self.port = port
        self._server_thread = None
        self.httpd = None

        '''Interactive is true by default, as this is designed to be a command
        line tool. We do not want to block interaction after plotting.'''
        self.interactive = interactive
        self.logging = logging

        # initialise strings
        self.js = JS.JavaScript()
        self.margins = {"top": 10, "right": 20, "bottom": 25, "left": 60, 
                        "height":height, "width":width}
        
        # we use bostock's scheme http://bl.ocks.org/1624660
        self.css = CSS()
        self.html = ""
        self.template = template or resource_string('d3py', 'd3py_template.html')
        self.js_geoms = JS.JavaScript()
        self.css_geoms = CSS()
        self.geoms = []
        # misc arguments - these go into the css!
        self.font = font
        self.args = {"width": width - self.margins["left"] - self.margins["right"],
                     "height": height - self.margins["top"] - self.margins["bottom"],
                     "font-family": "'%s'; sans-serif"%self.font}
        
        kwargs = dict([(k[0].replace('_','-'), k[1]) for k in kwargs.items()])
        self.args.update(kwargs)
        
    def update(self):
        '''Build or update JS, CSS, & HTML, and save all data'''
        logging.debug('updating chart')
        self._build()
        self.save()
        
    def _build(self):
        '''Build all JS, CSS, HTML, and Geometries'''
        logging.debug('building chart')
        if hasattr(self, 'vega'):
            self.vega.build_vega()
        self._build_js()
        self._build_css()
        self._build_html()
        self._build_geoms()

    def _build_css(self):
        '''Build basic CSS'''
        chart = {}
        chart.update(self.args)
        self.css["#chart"] = chart

    def _build_html(self):
        '''Build HTML, either via 'template' argument or default template 
        at /d3py_template.html.'''
        self.html = self.template
        self.html = self.html.replace("{{ name }}", self.name)
        self.html = self.html.replace("{{ font }}", self.font)
        self._save_html()

    def _build_geoms(self):
        '''Build D3py CSS/JS geometries. See /geoms for more details'''
        self.js_geoms = JS.JavaScript()
        self.css_geoms = CSS()
        for geom in self.geoms:
            self.js_geoms.merge(geom._build_js())
            self.css_geoms += geom._build_css()
        
    def _build_js(self):
        '''Build Javascript for Figure'''
        draw = JS.Function("draw", ("data",))
        draw += "var margin = %s;"%json.dumps(self.margins).replace('""','')
        draw += "    width = %s - margin.left - margin.right"%self.margins["width"]
        draw += "    height = %s - margin.top - margin.bottom;"%self.margins["height"]
        # this approach to laying out the graph is from Bostock: http://bl.ocks.org/1624660
        draw += "var g = " + JS.Selection("d3").select("'#chart'") \
            .append("'svg'") \
            .attr("'width'", 'width + margin.left + margin.right + 25') \
            .attr("'height'", 'height + margin.top + margin.bottom + 25') \
            .append("'g'") \
            .attr("'transform'", "'translate(' + margin.left + ',' + margin.top + ')'")

        self.js = JS.JavaScript() + draw + JS.Function("init")
        
    def _cleanup(self):
        raise NotImplementedError


    def __enter__(self):
        self.interactive = False
        return self

    def __exit__(self, ex_type, ex_value, ex_tb):
        if ex_tb is not None:
            print "Cleanup after exception: %s: %s"%(ex_type, ex_value)
        self._cleanup()

    def __del__(self):
        self._cleanup()

    def ion(self):
        """
        Turns interactive mode on ala pylab
        """
        self.interactive = True
    
    def ioff(self):
        """
        Turns interactive mode off
        """
        self.interactive = False

    def _set_data(self):
        '''Update JS, CSS, HTML, save all'''
        self.update()
        
    def __add__(self, geom):
        '''Add d3py.geom object to the Figure'''
        if isinstance(figure, vega.Vega):
            self._add_vega(figure)
        else: 
            self._add_geom(figure)

    def __iadd__(self, figure):
        '''Add d3py.geom or d3py.vega object to the Figure'''
        if isinstance(figure, vega.Vega):
            self._add_vega(figure)
        else: 
            self._add_geom(figure)
        return self
        
    def _add_vega(self, figure):
        '''Add D3py.Vega Figure'''
        self.vega = figure
        self.vega.tabular_data(self.data, columns=self.columns,
                               use_index=self.use_index)
        self.template = resource_string('d3py', 'vega_template.html')
        self._save_vega()                                 
        
    def _add_geom(self, geom):
        '''Append D3py.geom to existing D3py geoms'''
        self.geoms.append(geom)
        self.save()
        
    def save(self):
        '''Save data and all Figure components: JS, CSS, and HTML'''
        logging.debug('saving chart')
        if hasattr(self, 'vega'):
            self._save_vega()
        self._save_data()
        self._save_css()
        self._save_js()
        self._save_html()
        
    def _save_data(self,directory=None):
        """
        Build file map (dir path and StringIO for output) of data
        
        Parameters:
        -----------
        directory : str
            Specify a directory to store the data in (optional)
        """
        # write data
        filename = "%s.json"%self.name
        self.filemap[filename] = {"fd":StringIO(self._data_to_json()),
                                  "timestamp":time.time()}
                                  
    def _save_vega(self):
        '''Build file map (dir path and StringIO for output) of Vega'''
        vega = json.dumps(self.vega.vega, sort_keys=True, indent=4)
        self.filemap['vega.json'] = {"fd":StringIO(vega),
                                     "timestamp":time.time()}

    def _save_css(self):
        '''Build file map (dir path and StringIO for output) of CSS'''
        filename = "%s.css"%self.name
        css = "%s\n%s"%(self.css, self.css_geoms)
        self.filemap[filename] = {"fd":StringIO(css),
                                  "timestamp":time.time()}

    def _save_js(self):
        '''Build file map (dir path and StringIO for output) of data'''
        final_js = JS.JavaScript()
        final_js.merge(self.js)
        final_js.merge(self.js_geoms)

        filename = "%s.js"%self.name
        js = "%s"%final_js
        self.filemap[filename] = {"fd":StringIO(js),
                "timestamp":time.time()}

    def _save_html(self):
        '''Save HTML data. Will save Figure name to 'name.html'. Will also
        replace {{ port }} and {{ host }} fields in template with
        Figure.port and Figure.host '''
        self.html = self.html.replace("{{ port }}", str(self.port))
        self.html = self.html.replace("{{ host }}", str(self.host))
        # write html
        filename = "%s.html"%self.name
        self.filemap[filename] = {"fd":StringIO(self.html),
                "timestamp":time.time()}
                
    def _data_to_json(self):
        raise NotImplementedError

    def show(self, interactive=None):
        self.update()
        self.save()
        if interactive is not None:
            blocking = not interactive
        else:
            blocking = not self.interactive

        if blocking:
            self._serve(blocking=True)
        else:
            # if not blocking, we serve the 
            self._serve(blocking=False)
            # fire up a browser
            webbrowser.open_new_tab("http://%s:%s/%s.html"%(self.host,self.port, self.name))

    def display(self, width=700, height=400):
        html = "<iframe src=http://%s:%s/%s.html width=%s height=%s>" %(self.host, self.port, self.name, width, height)
        IPython.core.display.HTML(html)

    def _serve(self, blocking=True):
        """
        start up a server to serve the files for this vis.
        """
        msgparams = (self.host, self.port, self.name)
        url = "http://%s:%s/%s.html"%msgparams
        if self._server_thread is None or self._server_thread.active_count() == 0:
            Handler = CustomHTTPRequestHandler
            Handler.filemap = self.filemap
            Handler.logging = self.logging
            try:
                self.httpd = ThreadedHTTPServer(("", self.port), Handler)
            except Exception, e:
                print "Exception %s"%e
                return False
            if blocking:
                logging.info('serving forever on port: %s'%msgparams[1])
                msg = "You can find your chart at " + url
                print msg
                print "Ctrl-C to stop serving the chart and quit!"
                self._server_thread = None
                self.httpd.serve_forever()
            else:
                logging.info('serving asynchronously on port %s'%msgparams[1])
                self._server_thread = threading.Thread(
                    target=self.httpd.serve_forever
                )
                self._server_thread.daemon = True
                self._server_thread.start()
                msg = "You can find your chart at " + url
                print msg