Exemple #1
0
 def test_buffer_stream(self):
     stream = Buffer(data=self.df, index=False)
     plot = self.df.hvplot('x', 'y', stream=stream)
     pd.testing.assert_frame_equal(plot[()].data, self.df)
     new_df = pd.DataFrame([[7, 8], [9, 10]], columns=['x', 'y'])
     stream.send(new_df)
     pd.testing.assert_frame_equal(plot[()].data, pd.concat([self.df, new_df]))
    def test_buffer_stream_following(self):
        stream = Buffer(data={
            'x': np.array([1]),
            'y': np.array([1])
        },
                        following=True)
        dmap = DynamicMap(Curve, streams=[stream])

        plot = bokeh_renderer.get_plot(dmap)

        x_range = plot.handles['x_range']
        y_range = plot.handles['y_range']

        self.assertEqual(x_range.start, 0)
        self.assertEqual(x_range.end, 2)
        self.assertEqual(y_range.start, 0)
        self.assertEqual(y_range.end, 2)

        stream.send({'x': np.array([2]), 'y': np.array([-1])})

        self.assertEqual(x_range.start, 1)
        self.assertEqual(x_range.end, 2)
        self.assertEqual(y_range.start, -1)
        self.assertEqual(y_range.end, 1)

        stream.following = False

        stream.send({'x': np.array([3]), 'y': np.array([3])})

        self.assertEqual(x_range.start, 1)
        self.assertEqual(x_range.end, 2)
        self.assertEqual(y_range.start, -1)
        self.assertEqual(y_range.end, 1)
Exemple #3
0
 def test_spread_stream_data(self):
     buffer = Buffer({'y': np.array([]), 'yerror': np.array([]), 'x': np.array([])})
     dmap = DynamicMap(Spread, streams=[buffer])
     plot = bokeh_renderer.get_plot(dmap)
     buffer.send({'y': [1, 2, 1, 4], 'yerror': [.5, .2, .1, .5], 'x': [0,1,2,3]})
     cds = plot.handles['cds']
     self.assertEqual(cds.data['x'], np.array([0., 1., 2., 3., 3., 2., 1., 0.]))
     self.assertEqual(cds.data['y'], np.array([0.5, 1.8, 0.9, 3.5, 4.5, 1.1, 2.2, 1.5]))
Exemple #4
0
    def test_dynamic_groupby_kdims_and_streams(self):
        def plot_function(mydim, data):
            return Scatter(data[data[:, 2]==mydim])

        buff = Buffer(data=np.empty((0, 3)))
        dmap = DynamicMap(plot_function, streams=[buff], kdims='mydim').redim.values(mydim=[0, 1, 2])
        ndlayout = dmap.groupby('mydim', container_type=NdLayout)
        self.assertIsInstance(ndlayout[0], DynamicMap)
        data = np.array([(0, 0, 0), (1, 1, 1), (2, 2, 2)])
        buff.send(data)
        self.assertEqual(ndlayout[0][()], Scatter([(0, 0)]))
        self.assertEqual(ndlayout[1][()], Scatter([(1, 1)]))
        self.assertEqual(ndlayout[2][()], Scatter([(2, 2)]))
Exemple #5
0
    def test_dynamic_groupby_kdims_and_streams(self):
        def plot_function(mydim, data):
            return Scatter(data[data[:, 2]==mydim])

        buff = Buffer(data=np.empty((0, 3)))
        dmap = DynamicMap(plot_function, streams=[buff], kdims='mydim').redim.values(mydim=[0, 1, 2])
        ndlayout = dmap.groupby('mydim', container_type=NdLayout)
        self.assertIsInstance(ndlayout[0], DynamicMap)
        data = np.array([(0, 0, 0), (1, 1, 1), (2, 2, 2)])
        buff.send(data)
        self.assertEqual(ndlayout[0][()], Scatter([(0, 0)]))
        self.assertEqual(ndlayout[1][()], Scatter([(1, 1)]))
        self.assertEqual(ndlayout[2][()], Scatter([(2, 2)]))
class BokehApp:
    def __init__(self):
        self.switch_key = 'peak_8'
        self.maxlen = 1000000

        # Initialize buffers
        self.b_th_peak = Buffer(pd.DataFrame({
            'peak': [],
            'lowerbound': [],
            'higherbound': []
        }),
                                length=1000000)

        # Initialize callbacks
        self.callback_id_th_b = None

    def clear_buffer(self):
        """
        Modified version of hv.buffer.clear() since original appears to be
        buggy

        Clear buffer/graph whenever switch is toggled

        """
        with util.disable_constant(self.b_th_peak):

            self.b_th_peak.data = self.b_th_peak.data.iloc[:0]

        self.b_th_peak.send(
            pd.DataFrame({
                'peak': [],
                'lowerbound': [],
                'higherbound': []
            }))

    def produce_timehistory(self, context, doc):
        """
        Create timetool data timehistory
        
        Parameters
        ----------
        
        context = zmq.Context()
            Creates zmq socket to receive data
            
        doc: bokeh.document (I think)
            Bokeh document to be displayed on webpage
        
        """

        # Port to connect to master
        port = 5000
        socket = context.socket(zmq.REQ)

        # MUST BE FROM SAME MACHINE, CHANGE IF NECESSARY!!!
        socket.connect("tcp://localhost:%d" % port)

        # Dynamic Maps
        plot_peak_b = hv.DynamicMap(partial(hv.Curve, kdims=['index', 'peak']),
                                    streams=[self.b_th_peak]).options(
                                        width=1000,
                                        finalize_hooks=[
                                            apply_formatter
                                        ]).redim.label(index='Time in UTC')

        plot_peak_std_low = hv.DynamicMap(
            partial(hv.Curve, kdims=['index', 'lowerbound']),
            streams=[self.b_th_peak]).options(
                line_alpha=0.5,
                width=1000,
                line_color='gray',
                finalize_hooks=[apply_formatter
                                ]).redim.label(index='Time in UTC')

        plot_peak_std_high = hv.DynamicMap(
            partial(hv.Curve, kdims=['index', 'higherbound']),
            streams=[self.b_th_peak]).options(
                line_alpha=0.5, width=1000,
                line_color='gray').redim.label(index='Time in UTC')

        # Decimate and datashade
        pointTest = decimate(plot_peak_b, streams=[hv.streams.PlotSize])

        test1 = datashade(plot_peak_std_low,
                          streams=[hv.streams.PlotSize],
                          normalization='linear').options(
                              width=1000, finalize_hooks=[apply_formatter])

        test2 = datashade(plot_peak_std_high,
                          streams=[hv.streams.PlotSize],
                          normalization='linear')

        plot = (test1 * test2 * pointTest)

        # Use bokeh to render plot
        hvplot = renderer.get_plot(plot, doc)

        def push_data(stream):
            """
            Push data to timetool time history graph
            
            """

            median = pd.DataFrame({'peak': []})
            lowerbound = pd.DataFrame({'peak': []})
            higherbound = pd.DataFrame({'peak': []})

            # Request
            socket.send_string("Hello")
            print("Oof")

            data_dict = socket.recv_pyobj()
            peakDict = data_dict['peakDict']
            peakTSDict = data_dict['peakTSDict']

            TS_key = self.switch_key + '_TS'
            data = list(peakDict[self.switch_key])
            timestamp = list(peakTSDict[TS_key])

            times = [1000 * time for time in timestamp]
            dataSeries = pd.Series(data, index=times)

            zipped = basic_event_builder(peak=dataSeries)
            median = zipped.rolling(120, min_periods=1).median()
            std = zipped.rolling(120, min_periods=1).std()
            lowerbound = median - std
            higherbound = median + std

            df = pd.DataFrame({
                'peak': median['peak'],
                'lowerbound': lowerbound['peak'],
                'higherbound': higherbound['peak']
            })

            stream.send(df)

        def switch(attr, old, new):
            """
            Update drop down menu value

            """
            self.switch_key = select.value
            self.clear_buffer()
            print("Yes!")

        def play_graph():
            """
            Provide play and pause functionality to the graph

            """

            if startButton.label == '► Play':
                startButton.label = '❚❚ Pause'

                self.callback_id_th_b = doc.add_periodic_callback(
                    partial(push_data, stream=self.b_th_peak), 1000)

            else:
                startButton.label = '► Play'
                doc.remove_periodic_callback(self.callback_id_th_b)

        peak_list = [
            'peak_8', 'peak_9', 'peak_10', 'peak_11', 'peak_12', 'peak_13',
            'peak_14', 'peak_15'
        ]
        select = Select(title='Peak:', value='peak_8', options=peak_list)
        select.on_change('value', switch)

        startButton = Button(label='❚❚ Pause')
        startButton.on_click(play_graph)

        self.callback_id_th_b = doc.add_periodic_callback(
            partial(push_data, stream=self.b_th_peak), 1000)

        plot = column(select, startButton, hvplot.state)

        doc.title = "Time History Graphs"
        doc.add_root(plot)
Exemple #7
0
class HoloViewsConverter(object):
    def __init__(self,
                 data,
                 kind=None,
                 by=None,
                 width=700,
                 height=300,
                 shared_axes=False,
                 columns=None,
                 grid=False,
                 legend=True,
                 rot=None,
                 title=None,
                 xlim=None,
                 ylim=None,
                 xticks=None,
                 yticks=None,
                 fontsize=None,
                 colormap=None,
                 stacked=False,
                 logx=False,
                 logy=False,
                 loglog=False,
                 hover=True,
                 style_opts={},
                 plot_opts={},
                 use_index=False,
                 value_label='value',
                 group_label='Group',
                 colorbar=False,
                 streaming=False,
                 backlog=1000,
                 timeout=1000,
                 persist=False,
                 use_dask=False,
                 **kwds):

        # Validate DataSource
        if not isinstance(data, DataSource):
            raise TypeError('Can only plot intake DataSource types')
        elif data.container != 'dataframe':
            raise NotImplementedError('Plotting interface currently only '
                                      'supports DataSource objects with '
                                      'dataframe container.')
        self.data_source = data
        self.streaming = streaming
        self.use_dask = use_dask
        if streaming:
            self.data = data.read()
            self.stream = Buffer(self.data, length=backlog)
            if gen is None:
                raise ImportError('Streaming support requires tornado.')

            @gen.coroutine
            def f():
                self.stream.send(data.read())

            self.cb = PeriodicCallback(f, timeout)
        elif use_dask and dd is not None:
            ddf = data.to_dask()
            self.data = ddf.persist() if persist else ddf
        else:
            self.data = data.read()

        # High-level options
        self.by = by or []
        self.columns = columns
        self.stacked = stacked
        self.use_index = use_index
        self.kwds = kwds
        self.value_label = value_label
        self.group_label = group_label

        # Process style options
        if 'cmap' in kwds and colormap:
            raise TypeError("Only specify one of `cmap` and `colormap`.")
        elif 'cmap' in kwds:
            cmap = kwds.pop('cmap')
        else:
            cmap = colormap

        self._style_opts = dict(**style_opts)
        if cmap:
            self._style_opts['cmap'] = cmap
        if 'size' in kwds:
            self._style_opts['size'] = kwds.pop('size')
        if 'alpha' in kwds:
            self._style_opts['alpha'] = kwds.pop('alpha')

        # Process plot options
        plot_options = dict(plot_opts)
        plot_options['logx'] = logx or loglog
        plot_options['logy'] = logy or loglog
        plot_options['show_grid'] = grid
        plot_options['shared_axes'] = shared_axes
        plot_options['show_legend'] = legend
        if xticks:
            plot_options['xticks'] = xticks
        if yticks:
            plot_options['yticks'] = yticks
        if width:
            plot_options['width'] = width
        if height:
            plot_options['height'] = height
        if fontsize:
            plot_options['fontsize'] = fontsize
        if colorbar:
            plot_options['colorbar'] = colorbar
        if self.kwds.get('vert', False):
            plot_options['invert_axes'] = True
        if rot:
            if (kind == 'barh' or kwds.get('orientation') == 'horizontal'
                    or kwds.get('vert')):
                axis = 'yrotation'
            else:
                axis = 'xrotation'
            plot_options[axis] = rot
        if hover:
            plot_options['tools'] = ['hover']
        self._hover = hover
        self._plot_opts = plot_options

        self._relabel = {'label': title}
        self._dim_ranges = {
            'x': xlim or (None, None),
            'y': ylim or (None, None)
        }
        self._norm_opts = {'framewise': True}

    @streaming
    def table(self, x=None, y=None, data=None):
        allowed = ['width', 'height']
        opts = {k: v for k, v in self._plot_opts.items() if k in allowed}

        data = self.data if data is None else data
        return Table(data, self.columns, []).opts(plot=opts)

    def __call__(self, kind, x, y):
        return getattr(self, kind)(x, y)

    def single_chart(self, element, x, y, data=None):
        opts = {
            element.__name__:
            dict(plot=self._plot_opts,
                 norm=self._norm_opts,
                 style=self._style_opts)
        }
        ranges = {y: self._dim_ranges['y']}
        if x:
            ranges[x] = self._dim_ranges['x']

        data = self.data if data is None else data
        ys = [y]
        if 'c' in self.kwds and self.kwds['c'] in data.columns:
            ys += [self.kwds['c']]

        if self.by:
            chart = Dataset(data, [self.by, x], ys).to(element, x, ys,
                                                       self.by).overlay()
        else:
            chart = element(data, x, ys)
        return chart.redim.range(**ranges).relabel(**self._relabel).opts(opts)

    def chart(self, element, x, y, data=None):
        "Helper method for simple x vs. y charts"
        if x and y:
            return self.single_chart(element, x, y, data)

        # Note: Loading dask dataframe into memory due to rename bug
        data = (self.data if data is None else data)
        if self.use_dask: data = data.compute()
        opts = dict(plot=dict(self._plot_opts, labelled=['x']),
                    norm=self._norm_opts,
                    style=self._style_opts)

        if self.use_index or x:
            if self.use_index is not None and isinstance(self.use_index, bool):
                x = x or data.index.name or 'index'
            else:
                x = self.use_index
            columns = self.columns or data.columns
            charts = {}
            for c in columns:
                chart = element(data, x, c).redim(**{c: self.value_label})
                ranges = {x: self._dim_ranges['x'], c: self._dim_ranges['y']}
                charts[c] = (chart.relabel(**self._relabel).redim.range(
                    **ranges).opts(**opts))
            return NdOverlay(charts)
        else:
            raise ValueError('Could not determine what to plot. Expected '
                             'either x and y parameters to be declared '
                             'or use_index to be enabled.')

    @streaming
    def line(self, x, y, data=None):
        return self.chart(Curve, x, y, data)

    @streaming
    def scatter(self, x, y, data=None):
        scatter = self.chart(Scatter, x, y, data)
        if 'c' in self.kwds:
            color_opts = {
                'Scatter': {
                    'colorbar': self.kwds.get('colorbar', False),
                    'color_index': self.kwds['c']
                }
            }
            return scatter.opts(plot=color_opts)
        return scatter

    @streaming
    def area(self, x, y, data=None):
        areas = self.chart(Area, x, y, data)
        if self.stacked:
            areas = areas.map(Area.stack, NdOverlay)
        return areas

    def _category_plot(self, element, data=None):
        """
        Helper method to generate element from indexed dataframe.
        """
        data = self.data if data is None else data
        if isinstance(self.use_index, bool):
            index = data.index.name or 'index'
        else:
            index = self.use_index

        kdims = [index, self.group_label]
        id_vars = [index]
        invert = not self.kwds.get('vert', True)
        opts = {
            'plot': dict(self._plot_opts, labelled=[]),
            'norm': self._norm_opts
        }
        ranges = {self.value_label: self._dim_ranges['y']}

        if self.columns:
            data = data[self.columns + id_vars]
        if dd and isinstance(data, dd.DataFrame):
            data = data.compute()
        df = pd.melt(data,
                     id_vars=id_vars,
                     var_name=self.group_label,
                     value_name=self.value_label)
        return (element(df, kdims, self.value_label).redim.range(
            **ranges).relabel(**self._relabel).opts(**opts))

    def _stats_plot(self, element, y, data=None):
        """
        Helper method to generate element from indexed dataframe.
        """
        data = self.data if data is None else data

        opts = {
            'plot': dict(self._plot_opts, labelled=[]),
            'norm': self._norm_opts,
            'style': self._style_opts
        }
        if y:
            ranges = {y: self._dim_ranges['y']}
            kdims = [self.by] if self.by else []
            return (element(
                data, kdims,
                y).redim.range(**ranges).relabel(**self._relabel).opts(**opts))

        kdims = [self.group_label]
        ranges = {self.value_label: self._dim_ranges['y']}
        if self.columns:
            data = data[self.columns]
        if dd and isinstance(data, dd.DataFrame):
            data = data.compute()
        df = pd.melt(data,
                     var_name=self.group_label,
                     value_name=self.value_label)
        return (element(df, kdims, self.value_label).redim.range(
            **ranges).relabel(**self._relabel).opts(**opts))

    @streaming
    def bar(self, x, y, data=None):
        if x and y:
            return self.single_chart(Bars, x, y, data)
        elif self.use_index:
            stack_index = 1 if self.stacked else None
            opts = {'Bars': {'stack_index': stack_index}}
            return self._category_plot(Bars, data).opts(plot=opts)
        else:
            raise ValueError('Could not determine what to plot. Expected '
                             'either x and y parameters to be declared '
                             'or use_index to be enabled.')

    @streaming
    def barh(self, x, y, data=None):
        return self.bar(x, y, data).opts(plot={'Bars': dict(invert_axes=True)})

    @streaming
    def box(self, x, y, data=None):
        return self._stats_plot(BoxWhisker, y, data)

    @streaming
    def violin(self, x, y, data=None):
        try:
            from holoviews.element import Violin
        except ImportError:
            raise ImportError('Violin plot requires HoloViews version >=1.10')
        return self._stats_plot(Violin, y, data)

    @streaming
    def hist(self, x, y, data=None):
        plot_opts = dict(self._plot_opts)
        invert = self.kwds.get('orientation', False) == 'horizontal'
        opts = dict(plot=dict(plot_opts, labelled=['x'], invert_axes=invert),
                    style=self._style_opts,
                    norm=self._norm_opts)
        hist_opts = {
            'num_bins': self.kwds.get('bins', 10),
            'bin_range': self.kwds.get('bin_range', None),
            'normed': self.kwds.get('normed', False)
        }

        data = self.data if data is None else data
        ds = Dataset(data)
        if y and self.by:
            return histogram(ds.to(Dataset, [], y, self.by), **hist_opts).\
                overlay().opts({'Histogram': opts})
        elif y:
            return histogram(ds, dimension=y, **hist_opts).\
                opts({'Histogram': opts})

        hists = {}
        columns = self.columns or data.columns
        for col in columns:
            hist = histogram(ds, dimension=col, **hist_opts)
            ranges = {hist.vdims[0].name: self._dim_ranges['y']}
            hists[col] = (hist.redim.range(**ranges).relabel(
                **self._relabel).opts(**opts))
        return NdOverlay(hists)

    @streaming
    def kde(self, x, y, data=None):
        data = self.data if data is None else data
        plot_opts = dict(self._plot_opts)
        invert = self.kwds.get('orientation', False) == 'horizontal'
        opts = dict(plot=dict(plot_opts, invert_axes=invert),
                    style=self._style_opts,
                    norm=self._norm_opts)
        opts = {
            'Distribution': opts,
            'Area': opts,
            'NdOverlay': {
                'plot': dict(plot_opts, legend_limit=0)
            }
        }

        if y and self.by:
            ds = Dataset(data)
            return ds.to(Distribution, y, [], self.by).overlay().opts(opts)
        elif y:
            return Distribution(data, y, []).opts(opts)

        if self.columns:
            data = data[self.columns]
        df = pd.melt(data,
                     var_name=self.group_label,
                     value_name=self.value_label)
        ds = Dataset(df)
        if len(df):
            overlay = ds.to(Distribution, self.value_label).overlay()
        else:
            vdim = self.value_label + ' Density'
            overlay = NdOverlay({0: Area([], self.value_label, vdim)},
                                [self.group_label])
        return overlay.relabel(**self._relabel).opts(opts)

    @streaming
    def heatmap(self, x, y, data=None):
        data = data or self.data
        if not x: x = data.columns[0]
        if not y: y = data.columns[1]
        z = self.kwds.get('C', data.columns[2])

        opts = dict(plot=self._plot_opts,
                    norm=self._norm_opts,
                    style=self._style_opts)
        hmap = HeatMap(data, [x, y], z).opts(**opts)
        if 'reduce_function' in self.kwds:
            return hmap.aggregate(function=self.kwds['reduce_function'])
        return hmap
Exemple #8
0
class Resident:
    def __init__(self, name:str, rh:ResidentHealth, 
                 move_freq:datetime.timedelta, move_dist_range:Tuple[float, float],
                 ec:EpidemicConfig, cc:CityConfig
                ):
        """
        :param move_freq: how frequently does the resident go out of their house
        :param move_dist_range: when they go out, what distance range do they travel
        """
        self.name = name
        self.infected, self.infected_since = HealthStateEnum.NEVER_INFECTED, SENTINAL_DATE
        self.recovered_on, self.died_on = SENTINAL_DATE, SENTINAL_DATE
        self.health, self.ec, self.cc = rh, ec, cc
        self.move_freq, self.move_dist_range= move_freq, move_dist_range
        self.loc_hist:List[Tuple[datetime.datetime, float, float]] = []
        self._loc_hist_replay_stream = None
    
    def _valid_coords(self, x:float, y:float):
        """Check if resident is at a valid location"""
        min_x, min_y, max_x, max_y = 0, 0, self.cc.length, self.cc.breadth
        return min_x <= x <= max_x and min_y <= y <= max_y

    def location(self):
        return self._x_pos, self._y_pos
    
    def as_df(self):
        return pd.DataFrame([self.__dict__]).drop(columns=["ec", "cc", "loc_hist"]).astype("str")
    
    def move_to(self, new_x:float, new_y:float, date:datetime.datetime):
        if self._valid_coords(new_x, new_y):
            self._x_pos, self._y_pos = new_x, new_y
            self.loc_hist.append((date, new_x, new_y))
            return True
        return False
    
    def __repr__(self):
        return f"{self.name} is at ({self._x_pos}, {self._y_pos})"
    
    def __call__(self):
        infected_since = self.infected_since.strftime("%c") if self.infected_since < SENTINAL_DATE else None
        return self._x_pos, self._y_pos, self.name, self.infected, infected_since
    
    def is_infected(self):
        return self.infected == HealthStateEnum.INFECTED
    
    def is_alive(self):
        return self.infected != HealthStateEnum.DEAD
    
    def infect(self, date:datetime.datetime):
        if not self.is_alive():
            return
        self.infected = HealthStateEnum.INFECTED
        self.infected_since = date
        
    def _did_recover(self):
        return self.health.recovery_prob > random.random() # TODO: Any better way to build this?
        
    def cure(self, date:datetime.datetime, epidemic_config:EpidemicConfig):
        """Returns true if the resident was successfully cured"""
        if not self.is_alive():
            return
        duration_since_infected = date - self.infected_since
        if self.is_infected() and duration_since_infected >= epidemic_config.recover_after:
            if self._did_recover():
                self.infected = HealthStateEnum.RECOVERED
                self.recovered_on = date
                return True
            if duration_since_infected > epidemic_config.decease_after:
                self.kill(date)
        return False
        
    def kill(self, date:datetime.datetime):
        self.infected = HealthStateEnum.DEAD
        self.died_on = date
        
    def progress_time(self, current_date:datetime.datetime, move_prob:float=1) -> bool:
        """
        Make necessary changes to the state of the resident
        :param move_prob: for finer control over move_freq. 
            Say resident goes / moves out once a week and we progress the simulator day by day, 
            move_prob can be used to select the day of move during the week
        :return True if state of the resident changed or False otherwise
        """
        if not self.is_alive():
            return False
        last_moved_on, _, _ = self.loc_hist[-1]
        if last_moved_on + self.move_freq <= current_date and random.random() <= move_prob and self._move_to_rand(current_date):
            return True # as the resident moved this time
        return False
        
    def _move_to_rand(self, date:datetime.datetime, max_attempts:int=3) -> bool:
        """
        :param max_attempts: max number of times to try to find a valid new position before giving up
        :returns True if the resident was able to move to a valid position
        """
        for attempt_num in range(max_attempts):
            distance, direction = random.uniform(*self.move_dist_range), random.uniform(0, 2 * math.pi)
            new_x, new_y = self._x_pos + distance * math.cos(direction), self._y_pos + distance * math.sin(direction)
            move_ok = self.move_to(new_x, new_y, date)
            if move_ok:
                return True
        return False
    
    def initialize_loc_history_replay(self):
        self._loc_hist_replay_stream = Buffer(pd.DataFrame({
            "x": pd.Series([], dtype=float), 
            "y": pd.Series([], dtype=float), 
            "Name": pd.Series([], dtype=str), 
            "Date": pd.Series([], dtype=str)
        }), length=100, index=False)
        
        loc_dmap = hv.DynamicMap(partial(hv.Points, vdims=["Name", "Date"]), streams=[self._loc_hist_replay_stream])
        trace_dmap = hv.DynamicMap(partial(hv.Curve), streams=[self._loc_hist_replay_stream])
        # title_dmap = hv.DynamicMap(partial(hv.Text, x=13, y=13, text="Hello", vdims=["Date", "Name", "timestamp"], streams=[self._loc_hist_replay_stream]))
        
        print("Now call start_loc_history_replay() whenever you are ready")
        return (loc_dmap * trace_dmap).                    opts(ylim=(0, self.cc.breadth + 2), xlim=(0, self.cc.length + 2), 
                         show_legend=False).\
                    opts(opts.Points(size=6, tools=["hover"], color="Date", cmap="Blues"))
    
    def start_loc_history_replay(self, complete_within_sec:float=10):
        """
        :param complete_within_sec: adjusts the playback speed accordingly
        """
        self._loc_hist_replay_stream.clear()
        playback_speed = complete_within_sec / len(self.loc_hist)
        for date, x, y in self.loc_hist:
            sleep(playback_speed)            
            self._loc_hist_replay_stream.send(pd.DataFrame([
                (x, y, self.name, date.strftime("%c"))
            ], columns=["x", "y", "Name", "Date"]))
    
    def visualize_loc_history(self):
        df = pd.DataFrame(self.loc_hist, columns=["date", "x", "y"])
        df["date"] = df["date"].apply(lambda d: d.strftime("%c"))

        scatter = hv.Scatter(df,
            kdims=["x", "y"], vdims=["date", "date"]).\
            opts(tools=["hover"], color="date", cmap="Blues", size=6, padding=0.05, legend_position="bottom")
        line = hv.Curve(scatter, label="path")
        table = hv.Table(self.as_df().T.reset_index().rename(columns={"index": "Field", 0: "Details"}))
        return table + (line * scatter).opts(width=500, height=500, xlim=(0, self.cc.length), ylim=(0, self.cc.breadth))
class BokehApp:
    def __init__(self):
        self.switchButton = 'ipm2__sum'
        self.maxlen = 1000000

        # Initialize buffers
        self.b_timetool = Buffer(pd.DataFrame({
            'timestamp': [],
            'timetool': []
        }),
                                 length=40000)
        self.b_IpmAmp = Buffer(pd.DataFrame({
            'timetool': [],
            'ipm': []
        }),
                               length=1000)
        self.b_corr_timehistory = Buffer(pd.DataFrame({
            'timestamp': [],
            'correlation': []
        }),
                                         length=40000)

        # Initialize callbacks
        self.cb_id_timetool = None
        self.cb_id_amp_ipm = None
        self.cb_id_corr_timehistory = None

    def clear_buffer(self):
        """
        Modified version of hv.buffer.clear() since original appears to be
        buggy
        
        Clear buffer/graph whenever switch is toggled
        
        """

        data = pd.DataFrame({'timestamp': [], 'correlation': []})

        with util.disable_constant(
                self.b_corr_timehistory) and util.disable_constant(
                    self.b_IpmAmp):

            self.b_IpmAmp.data = self.b_IpmAmp.data.iloc[:0]
            self.b_corr_timehistory.data = self.b_corr_timehistory.data.iloc[:
                                                                             0]

        self.b_IpmAmp.send(pd.DataFrame({'timetool': [], 'ipm': []}))
        self.b_corr_timehistory.send(data)

    def produce_graphs(self, context, doc):
        """
        Create timetool data timehistory, timetool vs ipm, 
        and correlation timehistory graphs.
        
        Parameters
        ----------
        
        context = zmq.Context()
            Creates zmq socket to receive data
            
        doc: bokeh.document (I think)
            Bokeh document to be displayed on webpage
        
        """

        port = 5006
        socket = context.socket(zmq.SUB)

        # MUST BE FROM SAME MACHINE, CHANGE IF NECESSARY!!!
        socket.connect("tcp://psanagpu114:%d" % port)
        socket.setsockopt(zmq.SUBSCRIBE, b"")

        # Note: Cannot name 'timetool' variables in hvTimeTool and hvIpmAmp the same thing
        # Otherwise, holoviews will try to sync the axis and throw off the ranges for the plots
        # since hvIpmAmp only deals with the last 1000 points whereas hvTimeTool deals with all
        # the points
        hvTimeTool = hv.DynamicMap(my_partial(hv.Points,
                                              kdims=['timestamp', 'timetool']),
                                   streams=[self.b_timetool]).options(
                                       width=1000,
                                       finalize_hooks=[apply_formatter],
                                       xrotation=45).redim.label(
                                           timestamp='Time in UTC',
                                           timetool='Timetool Data')

        hvIpmAmp = hv.DynamicMap(
            my_partial(hv.Scatter, kdims=['timetool', 'ipm']),
            streams=[self.b_IpmAmp]).options(width=500).redim.label(
                timetool='Last 1000 Timetool Data Points',
                ipm='Last 1000 Ipm Data Points')

        hvCorrTimeHistory = hv.DynamicMap(
            my_partial(hv.Scatter, kdims=['timestamp', 'correlation']),
            streams=[self.b_corr_timehistory
                     ]).options(width=500,
                                finalize_hooks=[apply_formatter],
                                xrotation=45).redim.label(time='Time in UTC')

        layout = (hvIpmAmp + hvCorrTimeHistory + hvTimeTool).cols(2)
        hvplot = renderer.get_plot(layout)

        def push_data_timetool(buffer):
            """
            Push data to timetool time history graph
            
            """

            timetool_d = deque(maxlen=self.maxlen)
            timetool_t = deque(maxlen=self.maxlen)

            if socket.poll(timeout=0):
                data_dict = socket.recv_pyobj()
                timetool_d = data_dict['tt__FLTPOS_PS']

                # Get time from data_dict
                timeData = deque(maxlen=self.maxlen)
                for time in data_dict['event_time']:
                    num1 = str(time[0])
                    num2 = str(time[1])
                    fullnum = num1 + "." + num2
                    timeData.append(float(fullnum))
                timetool_t = timeData

            # Convert time to seconds so bokeh formatter can get correct datetime
            times = [1000 * time for time in list(timetool_t)]

            data = pd.DataFrame({'timestamp': times, 'timetool': timetool_d})

            buffer.send(data)

        def push_data_amp_ipm(buffer):
            """
            Push data into timetool amp vs ipm graph
            
            """

            timetool_d = deque(maxlen=self.maxlen)
            ipm_d = deque(maxlen=self.maxlen)

            if socket.poll(timeout=0):
                data_dict = socket.recv_pyobj()
                timetool_d = data_dict['tt__AMPL']
                ipm_d = data_dict[self.switchButton]

            data = pd.DataFrame({'timetool': timetool_d, 'ipm': ipm_d})

            buffer.send(data)

        def push_data_corr_time_history(buffer):
            """
            Calculate correlation between timetool amp and ipm and
            push to correlation time history graph
            
            """

            timetool_d = deque(maxlen=self.maxlen)
            timetool_t = deque(maxlen=self.maxlen)
            ipm_d = deque(maxlen=self.maxlen)

            if socket.poll(timeout=0):
                data_dict = socket.recv_pyobj()
                timetool_d = data_dict['tt__FLTPOS_PS']
                ipm_d = data_dict[self.switchButton]

                # Get time from data_dict
                timeData = deque(maxlen=self.maxlen)
                for time in data_dict['event_time']:
                    num1 = str(time[0])
                    num2 = str(time[1])
                    fullnum = num1 + "." + num2
                    timeData.append(float(fullnum))
                timetool_t = timeData

            # Convert time to seconds so bokeh formatter can get correct datetime
            times = [1000 * time for time in list(timetool_t)]

            data = pd.DataFrame({'timetool': timetool_d, 'ipm': ipm_d})
            data_corr = data['timetool'].rolling(window=120).corr(
                other=data['ipm'])

            # Start at index 119 so we don't get null data
            final_df = pd.DataFrame({
                'timestamp': times[119:],
                'correlation': data_corr[119:]
            })

            buffer.send(final_df)

        def switch(attr, old, new):
            """
            Update drop down menu value

            """

            self.switchButton = select.value
            self.clear_buffer()

        def stop():
            """
            Add pause and play functionality to graph
            
            """

            if stopButton.label == 'Play':
                stopButton.label = 'Pause'
                self.cb_id_timetool = doc.add_periodic_callback(
                    partial(push_data_timetool, buffer=self.b_timetool), 1000)

                self.cb_id_amp_ipm = doc.add_periodic_callback(
                    partial(push_data_amp_ipm, buffer=self.b_IpmAmp), 1000)

                self.cb_id_corr_timehistory = doc.add_periodic_callback(
                    partial(push_data_corr_time_history,
                            buffer=self.b_corr_timehistory), 1000)
            else:
                stopButton.label = 'Play'
                doc.remove_periodic_callback(self.cb_id_timetool)
                doc.remove_periodic_callback(self.cb_id_amp_ipm)
                doc.remove_periodic_callback(self.cb_id_corr_timehistory)

        # Start the callback
        self.cb_id_timetool = doc.add_periodic_callback(
            partial(push_data_timetool, buffer=self.b_timetool), 1000)

        self.cb_id_amp_ipm = doc.add_periodic_callback(
            partial(push_data_amp_ipm, buffer=self.b_IpmAmp), 1000)

        self.cb_id_corr_timehistory = doc.add_periodic_callback(
            partial(push_data_corr_time_history,
                    buffer=self.b_corr_timehistory), 1000)

        # Use this to test since ipm2 and ipm3 are too similar to see any differences
        # select = Select(title='ipm value:', value='ipm2__sum', options=['ipm2__sum', 'tt__FLTPOS_PS'])
        select = Select(title='ipm value:',
                        value='ipm2__sum',
                        options=['ipm2__sum', 'ipm3__sum'])
        select.on_change('value', switch)

        stopButton = Button(label='Pause')
        stopButton.on_click(stop)

        plot = column(select, stopButton, hvplot.state)
        doc.add_root(plot)