Exemple #1
0
class histogram(Operation):
    """
    Returns a Histogram of the input element data, binned into
    num_bins over the bin_range (if specified) along the specified
    dimension.
    """

    bin_range = param.NumericTuple(default=None,
                                   length=2,
                                   doc="""
      Specifies the range within which to compute the bins.""")

    bins = param.ClassSelector(default=None,
                               class_=(np.ndarray, list, tuple, str),
                               doc="""
      An explicit set of bin edges or a method to find the optimal
      set of bin edges, e.g. 'auto', 'fd', 'scott' etc. For more
      documentation on these approaches see the np.histogram_bin_edges
      documentation.""")

    cumulative = param.Boolean(default=False,
                               doc="""
      Whether to compute the cumulative histogram""")

    dimension = param.String(default=None,
                             doc="""
      Along which dimension of the Element to compute the histogram.""")

    frequency_label = param.String(default=None,
                                   doc="""
      Format string defining the label of the frequency dimension of the Histogram."""
                                   )

    groupby = param.ClassSelector(default=None,
                                  class_=(basestring, Dimension),
                                  doc="""
      Defines a dimension to group the Histogram returning an NdOverlay of Histograms."""
                                  )

    log = param.Boolean(default=False,
                        doc="""
      Whether to use base 10 logarithmic samples for the bin edges.""")

    mean_weighted = param.Boolean(default=False,
                                  doc="""
      Whether the weighted frequencies are averaged.""")

    normed = param.ObjectSelector(default=False,
                                  objects=[True, False, 'integral', 'height'],
                                  doc="""
      Controls normalization behavior.  If `True` or `'integral'`, then
      `density=True` is passed to np.histogram, and the distribution
      is normalized such that the integral is unity.  If `False`,
      then the frequencies will be raw counts. If `'height'`, then the
      frequencies are normalized such that the max bin height is unity.""")

    nonzero = param.Boolean(default=False,
                            doc="""
      Whether to use only nonzero values when computing the histogram""")

    num_bins = param.Integer(default=20,
                             doc="""
      Number of bins in the histogram .""")

    weight_dimension = param.String(default=None,
                                    doc="""
       Name of the dimension the weighting should be drawn from""")

    style_prefix = param.String(default=None,
                                allow_None=None,
                                doc="""
      Used for setting a common style for histograms in a HoloMap or AdjointLayout."""
                                )

    def _process(self, element, key=None):
        if self.p.groupby:
            if not isinstance(element, Dataset):
                raise ValueError(
                    'Cannot use histogram groupby on non-Dataset Element')
            grouped = element.groupby(self.p.groupby,
                                      group_type=Dataset,
                                      container_type=NdOverlay)
            self.p.groupby = None
            return grouped.map(self._process, Dataset)

        normed = False if self.p.mean_weighted and self.p.weight_dimension else self.p.normed
        if self.p.dimension:
            selected_dim = self.p.dimension
        else:
            selected_dim = [d.name for d in element.vdims + element.kdims][0]
        dim = element.get_dimension(selected_dim)

        if hasattr(element, 'interface'):
            data = element.interface.values(element,
                                            selected_dim,
                                            compute=False)
        else:
            data = element.dimension_values(selected_dim)

        is_datetime = isdatetime(data)
        if is_datetime:
            data = data.astype('datetime64[ns]').astype('int64')

        # Handle different datatypes
        is_finite = isfinite
        is_cupy = is_cupy_array(data)
        if is_cupy:
            import cupy
            full_cupy_support = LooseVersion(cupy.__version__) > '8.0'
            if not full_cupy_support and (normed or self.p.weight_dimension):
                data = cupy.asnumpy(data)
                is_cupy = False
            else:
                is_finite = cupy.isfinite

        # Mask data
        if is_ibis_expr(data):
            mask = data.notnull()
            if self.p.nonzero:
                mask = mask & (data != 0)
            data = data.to_projection()
            data = data[mask]
            no_data = not len(data.head(1).execute())
            data = data[dim.name]
        else:
            mask = is_finite(data)
            if self.p.nonzero:
                mask = mask & (data != 0)
            data = data[mask]
            da = dask_array_module()
            no_data = False if da and isinstance(data,
                                                 da.Array) else not len(data)

        # Compute weights
        if self.p.weight_dimension:
            if hasattr(element, 'interface'):
                weights = element.interface.values(element,
                                                   self.p.weight_dimension,
                                                   compute=False)
            else:
                weights = element.dimension_values(self.p.weight_dimension)
            weights = weights[mask]
        else:
            weights = None

        # Compute bins
        if isinstance(self.p.bins, str):
            bin_data = cupy.asnumpy(data) if is_cupy else data
            edges = np.histogram_bin_edges(bin_data, bins=self.p.bins)
        elif isinstance(self.p.bins, (list, np.ndarray)):
            edges = self.p.bins
            if isdatetime(edges):
                edges = edges.astype('datetime64[ns]').astype('int64')
        else:
            hist_range = self.p.bin_range or element.range(selected_dim)
            # Avoids range issues including zero bin range and empty bins
            if hist_range == (0, 0) or any(not isfinite(r)
                                           for r in hist_range):
                hist_range = (0, 1)
            steps = self.p.num_bins + 1
            start, end = hist_range
            if is_datetime:
                start, end = dt_to_int(start, 'ns'), dt_to_int(end, 'ns')
            if self.p.log:
                bin_min = max([abs(start), data[data > 0].min()])
                edges = np.logspace(np.log10(bin_min), np.log10(end), steps)
            else:
                edges = np.linspace(start, end, steps)
        if is_cupy:
            edges = cupy.asarray(edges)

        if not is_dask_array(data) and no_data:
            nbins = self.p.num_bins if self.p.bins is None else len(
                self.p.bins) - 1
            hist = np.zeros(nbins)
        elif hasattr(element, 'interface'):
            density = True if normed else False
            hist, edges = element.interface.histogram(data,
                                                      edges,
                                                      density=density,
                                                      weights=weights)
            if normed == 'height':
                hist /= hist.max()
            if self.p.weight_dimension and self.p.mean_weighted:
                hist_mean, _ = element.interface.histogram(data,
                                                           density=False,
                                                           bins=edges)
                hist /= hist_mean
        elif normed:
            # This covers True, 'height', 'integral'
            hist, edges = np.histogram(data,
                                       density=True,
                                       weights=weights,
                                       bins=edges)
            if normed == 'height':
                hist /= hist.max()
        else:
            hist, edges = np.histogram(data,
                                       normed=normed,
                                       weights=weights,
                                       bins=edges)
            if self.p.weight_dimension and self.p.mean_weighted:
                hist_mean, _ = np.histogram(data,
                                            density=False,
                                            bins=self.p.num_bins)
                hist /= hist_mean

        hist[np.isnan(hist)] = 0
        if is_datetime:
            edges = (edges / 1e3).astype('datetime64[us]')

        params = {}
        if self.p.weight_dimension:
            params['vdims'] = [element.get_dimension(self.p.weight_dimension)]
        elif self.p.frequency_label:
            label = self.p.frequency_label.format(dim=dim.pprint_label)
            params['vdims'] = [Dimension('Frequency', label=label)]
        else:
            label = 'Frequency' if normed else 'Count'
            params['vdims'] = [
                Dimension('{0}_{1}'.format(dim.name, label.lower()),
                          label=label)
            ]

        if element.group != element.__class__.__name__:
            params['group'] = element.group

        if self.p.cumulative:
            hist = np.cumsum(hist)
            if self.p.normed in (True, 'integral'):
                hist *= edges[1] - edges[0]

        # Save off the computed bin edges so that if this operation instance
        # is used to compute another histogram, it will default to the same
        # bin edges.
        self.bins = list(edges)
        return Histogram((edges, hist),
                         kdims=[element.get_dimension(selected_dim)],
                         label=element.label,
                         **params)
Exemple #2
0
class RunBatchCommand(CommandTemplate):
    """ RunBatchCommand is designed to to format task specifications into
       Topographica run_batch tasks in a way that should be flexible enough to
       be used generally. Note that Topographica is invoked with the -a flag so
       all of topo.command is imported.

       Though some of the parameters required appear to duplicate those in
       run_batch, they are necessary to ensure some basic consistency in the use
       of run_batch between tasks. This CommandTemplate class constrains -all- the
       options for run_batch with the exception of 'times' which is free to vary
       as part of the task specification. This is allowed as you may wish to
       change the times at which the analysis function is invoked across
       batches.

       This command is queuable with both the queue and specify methods
       implemented. As necessary, specifications are generated in the
       'specifications' subdirectory of the root directory.
       """

    topo_switches = param.List(default=['-a'],
                               doc="""
          Specifies the Topographica qsub switches (flags without arguments) as a
          list of strings. By default the -a switch is always used to auto import
          commands.""")

    topo_flag_options = param.Dict(default={},
                                   doc="""
          Specifies Topographica flags and their corresponding options as a
          dictionary.  This parameter is suitable for setting -c and -p flags for
          Topographica (eg. to customize OpenMP settings). This parameter is also
          important to introduce an analysis callable into the namespace if the
          analysis function is set to 'custom'.

          Tuples can be used to indicate groups of options using the same flag:
          {'-p':'retina_density=5'} => -p retina_density=5
          {'-p':('retina_density=5', 'scale=2') => -p retina_density=5 -p scale=2
          {'-p':'

          If a plain Python dictionary is used, the keys are alphanumerically sorted,
          otherwise the dictionary is assumed to be an OrderedDict (Python 2.7+,
          Python3 or param.external.OrderedDict) and the key ordering will be
          preserved. Finally note that the '-' is added to the key if missing (to
          make into a valid flag) to allow you to specify keywords in a dict
          constructor ie. dict(key1=value1, key2=value2,....)""")

    analysis = param.ObjectSelector(
        default='default',
        objects=['default', 'RunBatchAnalysis', 'custom'],
        constant=True,
        doc="""
              The type of analysis to use with run_batch. The options are 'default'
              which runs the default run_batch analysis function,
              'RunBatchAnalysis' which use the more sophisticated picklable
              analysis function and custom which requires the analysis callable to be
              created in the run_batch namespace via a -c option with
              topo_flag_options""")

    analysis_arguments = param.List(default=[],
                                    doc='''
         The specifier keys to be consumed as analysis function arguments''')

    name_time_format = param.String(default='%Y%m%d%H%M',
                                    doc="""
         The timestamp format for directories created by run_batch in python
         datetime format.""")

    max_name_length = param.Number(
        default=200, doc="Equivalent to the run_batch parameter of same name.")

    snapshot = param.Boolean(
        default=True,
        doc="Equivalent to the run_batch parameter of same name.")

    vc_info = param.Boolean(
        default=True,
        doc="Equivalent to the run_batch parameter of same name.")

    save_global_params = param.Boolean(
        default=True,
        doc="Equivalent to the run_batch parameter of same name.")

    param_formatter = param.Callable(param_formatter.instance(),
                                     doc="""
        If None, defaults to normal run_batch formatting.""")

    def __init__(self, tyfile, analysis='default', **kwargs):

        if (analysis == 'custom') and ('-c' not in self.topo_flag_options):
            raise Exception, 'Please use -c option to introduce custom analysis to namespace!'

        executable = os.path.abspath(sys.argv[0])
        super(RunBatchCommand, self).__init__(analysis=analysis,
                                              executable=executable,
                                              **kwargs)

        self.tyfile = os.path.abspath(tyfile)
        assert os.path.exists(
            self.tyfile), "Tyfile doesn't exist! Cannot proceed."

        self._prelude = []
        if self.analysis == 'RunBatchAnalysis':
            self._prelude = ['from topo.misc.lancext import RunBatchAnalysis']
        if self.analysis == 'default':
            self._prelude = ["analysis = default_analysis_function"]

    def topo_args(self, switch_override=[]):
        """ Method to generate Popen style argument list for Topographica using the
      topo_switches and topo_flag_options parameters. Switches are returned first,
      sorted alphanumerically.  The qsub_flag_options follow in keys() ordered if
      not a vanilla Python dictionary (ie. a Python 2.7+ or param.external
      OrderedDict). Otherwise the keys are sorted alphanumerically."""

        opt_dict = type(self.topo_flag_options)()
        opt_dict.update(self.topo_flag_options)

        if type(self.topo_flag_options
                ) == dict:  # Alphanumeric sort if vanilla Python dictionary
            ordered_options = [(k, opt_dict[k]) for k in sorted(opt_dict)]
        else:
            ordered_options = list(opt_dict.items())

        # Unpack tuple values so flag:(v1, v2,...)) => ..., flag:v1, flag:v2, ...
        unpacked_groups = [[(k, v)
                            for v in val] if type(val) == tuple else [(k, val)]
                           for (k, val) in ordered_options]
        unpacked_kvs = [el for group in unpacked_groups for el in group]

        # Adds '-' if missing (eg, keywords in dict constructor) and flattens lists.
        ordered_pairs = [(k, v) if (k[0] == '-') else ('-%s' % (k), v)
                         for (k, v) in unpacked_kvs]
        ordered_options = [[k] + ([v] if type(v) == str else v)
                           for (k, v) in ordered_pairs]
        flattened_options = [el for kvs in ordered_options for el in kvs]
        switches = [
            s for s in switch_override if (s not in self.topo_switches)
        ] + self.topo_switches
        return sorted(switches) + flattened_options

    def queue(self, tid, info):
        ''' Uses load_kwargs helper function in topo.command.misc to get the
      run_batch settings from the specifications directory. '''
        spec_path = os.path.join(info['root_directory'], 'specifications')
        spec_file_path = os.path.join(spec_path, 't%s.spec' % tid)
        prelude = self._prelude[:]
        prelude += [
            "kwargs = load_kwargs('%s',globals(),locals())" % spec_file_path
        ]
        if self.analysis == 'RunBatchAnalysis':
            prelude += [
                "analysis=RunBatchAnalysis.load(%s)" %
                repr(info['root_directory'])
            ]
            prelude += ["analysis.current_tid = %s" % tid]
            prelude += ["analysis.analysis_kwargs = dict(kwargs)"]

        run_batch_cmd = "run_batch('%s',**kwargs)" % self.tyfile
        topo_args = self.topo_args(['-a'])
        return [self.executable
                ] + topo_args + ['-c', ';'.join(prelude + [run_batch_cmd])]

    def specify(self, spec, tid, info):
        ''' Writes the specification as a Python dictionary to file (to the
          specifications subdirectory of root directory) as required by the
          load_kwargs helper method in topo.misc.command.'''

        spec_path = os.path.join(info['root_directory'], 'specifications')
        spec_file_path = os.path.join(spec_path, 't%d.spec' % tid)
        if not os.path.exists(spec_path): os.mkdir(spec_path)

        spec_file = open(spec_file_path, 'w')
        kwarg_opts = self._run_batch_kwargs(spec, tid, info)
        allopts = dict(
            spec,
            **kwarg_opts)  # Override run_batch keys if (mistakenly) provided.

        keywords = ',\n'.join([
            '%s=%s' % (k, allopts[k])
            for k in sorted(kwarg_opts.keys()) + sorted(spec.keys())
        ])
        spec_file.write('dict(\n%s\n)' % keywords)
        spec_file.close()

    def _run_batch_kwargs(self, spec, tid, info):
        ''' Defines the keywords accepted by run_batch and so specifies run_batch behaviour. '''
        options = {
            'name_time_format': repr(self.name_time_format),
            'max_name_length': self.max_name_length,
            'snapshot': self.snapshot,
            'vc_info': self.vc_info,
            'save_global_params': self.save_global_params
        }

        if info['batch_tag'] == '': dirname_prefix = info['batch_name']
        else:
            dirname_prefix = info['batch_name'] + ('[%s]' % info['batch_tag'])

        # Settings using information from launcher
        derived_options = {
            'output_directory': repr(info['root_directory']),
            'dirname_prefix': repr(dirname_prefix),
            'tag': repr('t%s_' % tid)
        }

        # Use fixed timestamp argument to run_batch if available.
        if info['timestamp'] is not None:
            derived_options['timestamp'] = info['timestamp']
        derived_options['analysis_fn'] = 'analysis'

        # Use the specified param_formatter (if desired) to create lambda in run_batch
        if self.param_formatter is not None:
            dir_format = self.param_formatter(info['constant_keys'],
                                              info['varying_keys'], spec)
            derived_options['dirname_params_filter'] = 'lambda p: %s' % repr(
                dir_format)

        return dict(options.items() + derived_options.items())

    def __call__(self, spec, tid=None, info={}):
        """ Returns a Popen argument list to invoke Topographica and execute
      run_batch with all options appropriately set (in alphabetical
      order). Keywords that are not run_batch options are also in alphabetical
      order at the end of the keyword list"""

        kwarg_opts = self._run_batch_kwargs(spec, tid, info)
        allopts = dict(
            spec, **kwarg_opts)  # Override spec values if mistakenly included.

        prelude = self._prelude[:]
        if self.analysis == 'RunBatchAnalysis':
            prelude += [
                "analysis=RunBatchAnalysis.load(%s)" %
                repr(info['root_directory'])
            ]
            prelude += ["analysis.current_tid = %s" % tid]
            analysis_kwargs = [
                "'%s':%s" % (k, v) for (k, v) in allopts.items()
                if k in self.analysis_arguments
            ]
            prelude += [
                "analysis.analysis_kwargs = {%s}" % ', '.join(analysis_kwargs)
            ]

        keywords = ', '.join([
            '%s=%s' % (k, allopts[k]) for k in  # Was %s
            sorted(kwarg_opts.keys()) + sorted(spec.keys())
        ])
        run_batch_list = prelude + [
            "run_batch(%s,%s)" % (repr(self.tyfile), keywords)
        ]
        topo_args = self.topo_args(['-a'])
        return [self.executable
                ] + topo_args + ['-c', '; '.join(run_batch_list)]
Exemple #3
0
 class Test(param.Parameterized):
     a = param.ObjectSelector(default='b', objects=[1, 'b', 'c'])
Exemple #4
0
class PlotlyRenderer(Renderer):

    backend = param.String(default='plotly', doc="The backend name.")

    fig = param.ObjectSelector(default='auto',
                               objects=['html', 'json', 'auto'],
                               doc="""
        Output render format for static figures. If None, no figure
        rendering will occur. """)

    mode_formats = {
        'fig': {
            'default': ['html', 'json']
        },
        'holomap': {
            'default': ['widgets', 'scrubber', 'auto']
        }
    }

    widgets = {
        'scrubber': PlotlyScrubberWidget,
        'widgets': PlotlySelectionWidget
    }

    backend_dependencies = {'js': (get_plotlyjs(), )}

    comm_msg_handler = plotly_msg_handler

    _loaded = False

    def __call__(self, obj, fmt='html', divuuid=None):
        plot, fmt = self._validate(obj, fmt)
        mime_types = {'file-ext': fmt, 'mime_type': MIME_TYPES[fmt]}

        if isinstance(plot, tuple(self.widgets.values())):
            return plot(), mime_types
        elif fmt == 'html':
            return self._figure_data(plot, divuuid=divuuid), mime_types
        elif fmt == 'json':
            return self.diff(plot), mime_types

    def diff(self, plot, serialize=True):
        """
        Returns a json diff required to update an existing plot with
        the latest plot data.
        """
        diff = plot.state.to_plotly_json()
        if serialize:
            return json.dumps(diff, cls=utils.PlotlyJSONEncoder)
        else:
            return diff

    def _figure_data(self,
                     plot,
                     fmt=None,
                     divuuid=None,
                     comm=True,
                     as_script=False,
                     width=800,
                     height=600):
        figure = plot.state
        if divuuid is None:
            divuuid = plot.id

        jdata = json.dumps(figure.data, cls=utils.PlotlyJSONEncoder)
        jlayout = json.dumps(figure.layout, cls=utils.PlotlyJSONEncoder)

        config = {}
        config['showLink'] = False
        jconfig = json.dumps(config)

        if as_script:
            header = 'window.PLOTLYENV=window.PLOTLYENV || {};'
        else:
            header = ('<script type="text/javascript">'
                      'window.PLOTLYENV=window.PLOTLYENV || {};'
                      '</script>')

        script = '\n'.join([
            'var plotly = window._Plotly || window.Plotly;'
            'plotly.plot("{id}", {data}, {layout}, {config}).then(function() {{',
            '    var elem = document.getElementById("{id}.loading"); elem.parentNode.removeChild(elem);',
            '}})'
        ]).format(id=divuuid, data=jdata, layout=jlayout, config=jconfig)

        html = ('<div id="{id}.loading" style="color: rgb(50,50,50);">'
                'Drawing...</div>'
                '<div id="{id}" style="height: {height}; width: {width};" '
                'class="plotly-graph-div">'
                '</div>'.format(id=divuuid, height=height, width=width))
        if as_script:
            return html, header + script

        content = ('{html}'
                   '<script type="text/javascript">'
                   '  {script}'
                   '</script>').format(html=html, script=script)
        return '\n'.join([header, content])

    @classmethod
    def plot_options(cls, obj, percent_size):
        factor = percent_size / 100.0
        obj = obj.last if isinstance(obj, HoloMap) else obj
        plot = Store.registry[cls.backend].get(type(obj), None)
        options = plot.lookup_options(obj, 'plot').options
        width = options.get('width', plot.width) * factor
        height = options.get('height', plot.height) * factor
        return dict(options, **{'width': int(width), 'height': int(height)})

    @classmethod
    def load_nb(cls, inline=True):
        """
        Loads the plotly notebook resources.
        """
        from IPython.display import display, HTML, publish_display_data
        if not cls._loaded:
            display(HTML(PLOTLY_WARNING))
            cls._loaded = True
        init_notebook_mode(connected=not inline)
        publish_display_data(data={MIME_TYPES['jlab-hv-load']: get_plotlyjs()})
Exemple #5
0
 'tabs': [
     {
         'tabTitle':'Tab 1',
         'tabDesc': 'A description of the tabbed section.',
         'tables': [
             {
                 'tableTitle':'Table 1',
                 'tableDesc':'A description of table 1.',
                 'tableLayout':[['A','B','C'],['D','E','F']],
                 'tableParams':{'A':param.String('A')}
             },
             {
                 'tableTitle':'Table 2',
                 'tableDesc':'A description of table 1.',
                 'tableLayout':[['A'],['J','K','L']],
                 'tableParams':{'A':param.ObjectSelector(default='Asia', objects=['Africa', 'Asia', 'Europe'])}
             }
         ]
     },
     {
         'tabTitle':'Tab 2',
         'tabDesc': 'A description of the second tabbed section.',
         'tables': [
             {
                 'tableTitle':'Table 3',
                 'tableDesc':'A description of table 1.',
                 'tableLayout':[['A','B','C'],['D','E','F']],
                 'tableParams':{'A':param.String('A')}
             },
             {
                 'tableTitle':'Table 4',
Exemple #6
0
class PointPlot(LegendPlot, ColorbarPlot):

    color_index = param.ClassSelector(default=None,
                                      class_=(basestring, int),
                                      allow_None=True,
                                      doc="""
      Index of the dimension from which the color will the drawn""")

    size_index = param.ClassSelector(default=None,
                                     class_=(basestring, int),
                                     allow_None=True,
                                     doc="""
      Index of the dimension from which the sizes will the drawn.""")

    scaling_method = param.ObjectSelector(default="area",
                                          objects=["width", "area"],
                                          doc="""
      Determines whether the `scaling_factor` should be applied to
      the width or area of each point (default: "area").""")

    scaling_factor = param.Number(default=1,
                                  bounds=(0, None),
                                  doc="""
      Scaling factor which is applied to either the width or area
      of each point, depending on the value of `scaling_method`.""")

    size_fn = param.Callable(default=np.abs,
                             doc="""
      Function applied to size values before applying scaling,
      to remove values lower than zero.""")

    style_opts = (['cmap', 'palette', 'marker', 'size'] + line_properties +
                  fill_properties)

    _plot_methods = dict(single='scatter', batched='scatter')
    _batched_style_opts = line_properties + fill_properties + ['size']

    def _get_size_data(self, element, ranges, style):
        data, mapping = {}, {}
        sdim = element.get_dimension(self.size_index)
        if sdim:
            map_key = 'size_' + sdim.name
            ms = style.get('size', np.sqrt(6))**2
            sizes = element.dimension_values(self.size_index)
            sizes = compute_sizes(sizes, self.size_fn, self.scaling_factor,
                                  self.scaling_method, ms)
            if sizes is None:
                eltype = type(element).__name__
                self.warning('%s dimension is not numeric, cannot '
                             'use to scale %s size.' %
                             (sdim.pprint_label, eltype))
            else:
                data[map_key] = np.sqrt(sizes)
                mapping['size'] = map_key
        return data, mapping

    def get_data(self, element, ranges=None, empty=False):
        style = self.style[self.cyclic_index]
        dims = element.dimensions(label=True)

        xidx, yidx = (1, 0) if self.invert_axes else (0, 1)
        mapping = dict(x=dims[xidx], y=dims[yidx])
        data = {}

        xdim, ydim = dims[xidx], dims[yidx]
        data[xdim] = [] if empty else element.dimension_values(xidx)
        data[ydim] = [] if empty else element.dimension_values(yidx)
        self._categorize_data(data, (xdim, ydim), element.dimensions())

        cdata, cmapping = self._get_color_data(element, ranges, style)
        data.update(cdata)
        mapping.update(cmapping)

        sdata, smapping = self._get_size_data(element, ranges, style)
        data.update(sdata)
        mapping.update(smapping)

        self._get_hover_data(data, element, empty)
        return data, mapping

    def get_batched_data(self, element, ranges=None, empty=False):
        data = defaultdict(list)
        zorders = self._updated_zorders(element)
        styles = self.lookup_options(element.last, 'style')
        styles = styles.max_cycles(len(self.ordering))
        for (key, el), zorder in zip(element.data.items(), zorders):
            self.set_param(**self.lookup_options(el, 'plot').options)
            eldata, elmapping = self.get_data(el, ranges, empty)
            for k, eld in eldata.items():
                data[k].append(eld)

            # Apply static styles
            nvals = len(list(eldata.values())[0])
            style = styles[zorder]
            sdata, smapping = expand_batched_style(style,
                                                   self._batched_style_opts,
                                                   elmapping, nvals)
            elmapping.update(smapping)
            for k, v in sdata.items():
                data[k].append(v)

            if any(isinstance(t, HoverTool) for t in self.state.tools):
                for dim, k in zip(element.dimensions(), key):
                    sanitized = dimension_sanitizer(dim.name)
                    data[sanitized].append([k] * nvals)

        data = {k: np.concatenate(v) for k, v in data.items()}
        return data, elmapping
Exemple #7
0
class ElementPlot(PlotlyPlot, GenericElementPlot):

    aspect = param.Parameter(default='cube',
                             doc="""
        The aspect ratio mode of the plot. By default, a plot may
        select its own appropriate aspect ratio but sometimes it may
        be necessary to force a square aspect ratio (e.g. to display
        the plot as an element of a grid). The modes 'auto' and
        'equal' correspond to the axis modes of the same name in
        matplotlib, a numeric value may also be passed.""")

    bgcolor = param.ClassSelector(class_=(str, tuple),
                                  default=None,
                                  doc="""
        If set bgcolor overrides the background color of the axis.""")

    invert_axes = param.ObjectSelector(default=False,
                                       doc="""
        Inverts the axes of the plot. Note that this parameter may not
        always be respected by all plots but should be respected by
        adjoined plots when appropriate.""")

    invert_xaxis = param.Boolean(default=False,
                                 doc="""
        Whether to invert the plot x-axis.""")

    invert_yaxis = param.Boolean(default=False,
                                 doc="""
        Whether to invert the plot y-axis.""")

    invert_zaxis = param.Boolean(default=False,
                                 doc="""
        Whether to invert the plot z-axis.""")

    labelled = param.List(default=['x', 'y', 'z'],
                          doc="""
        Whether to label the 'x' and 'y' axes.""")

    logx = param.Boolean(default=False,
                         doc="""
         Whether to apply log scaling to the x-axis of the Chart.""")

    logy = param.Boolean(default=False,
                         doc="""
         Whether to apply log scaling to the y-axis of the Chart.""")

    logz = param.Boolean(default=False,
                         doc="""
         Whether to apply log scaling to the y-axis of the Chart.""")

    margins = param.NumericTuple(default=(50, 50, 50, 50),
                                 doc="""
         Margins in pixel values specified as a tuple of the form
         (left, bottom, right, top).""")

    show_legend = param.Boolean(default=False,
                                doc="""
        Whether to show legend for the plot.""")

    xaxis = param.ObjectSelector(
        default='bottom',
        objects=['top', 'bottom', 'bare', 'top-bare', 'bottom-bare', None],
        doc="""
        Whether and where to display the xaxis, bare options allow suppressing
        all axis labels including ticks and xlabel. Valid options are 'top',
        'bottom', 'bare', 'top-bare' and 'bottom-bare'.""")

    xticks = param.Parameter(default=None,
                             doc="""
        Ticks along x-axis specified as an integer, explicit list of
        tick locations, list of tuples containing the locations.""")

    yaxis = param.ObjectSelector(
        default='left',
        objects=['left', 'right', 'bare', 'left-bare', 'right-bare', None],
        doc="""
        Whether and where to display the yaxis, bare options allow suppressing
        all axis labels including ticks and ylabel. Valid options are 'left',
        'right', 'bare' 'left-bare' and 'right-bare'.""")

    yticks = param.Parameter(default=None,
                             doc="""
        Ticks along y-axis specified as an integer, explicit list of
        tick locations, list of tuples containing the locations.""")

    zlabel = param.String(default=None,
                          doc="""
        An explicit override of the z-axis label, if set takes precedence
        over the dimension label.""")

    zticks = param.Parameter(default=None,
                             doc="""
        Ticks along z-axis specified as an integer, explicit list of
        tick locations, list of tuples containing the locations.""")

    trace_kwargs = {}

    _style_key = None

    # Whether vectorized styles are applied per trace
    _per_trace = False

    # Declare which styles cannot be mapped to a non-scalar dimension
    _nonvectorized_styles = []

    def initialize_plot(self, ranges=None):
        """
        Initializes a new plot object with the last available frame.
        """
        # Get element key and ranges for frame
        fig = self.generate_plot(self.keys[-1], ranges)
        self.drawn = True
        return fig

    def generate_plot(self, key, ranges, element=None):
        if element is None:
            element = self._get_frame(key)

        if element is None:
            return self.handles['fig']

        # Set plot options
        plot_opts = self.lookup_options(element, 'plot').options
        self.param.set_param(
            **{k: v
               for k, v in plot_opts.items() if k in self.params()})

        # Get ranges
        ranges = self.compute_ranges(self.hmap, key, ranges)
        ranges = util.match_spec(element, ranges)

        # Get style
        self.style = self.lookup_options(element, 'style')
        style = self.style[self.cyclic_index]

        # Get data and options and merge them
        data = self.get_data(element, ranges, style)
        opts = self.graph_options(element, ranges, style)
        graphs = []
        for i, d in enumerate(data):
            # Initialize traces
            traces = self.init_graph(d, opts, index=i)
            graphs.extend(traces)
        self.handles['graphs'] = graphs

        # Initialize layout
        layout = self.init_layout(key, element, ranges)
        self.handles['layout'] = layout

        # Create figure and return it
        self.drawn = True
        fig = dict(data=graphs, layout=layout)
        self.handles['fig'] = fig
        return fig

    def graph_options(self, element, ranges, style):
        if self.overlay_dims:
            legend = ', '.join([
                d.pprint_value_string(v) for d, v in self.overlay_dims.items()
            ])
        else:
            legend = element.label

        opts = dict(name=legend, **self.trace_kwargs)

        if self.trace_kwargs.get('type', None) in legend_trace_types:
            opts.update(showlegend=self.show_legend, legendgroup=element.group)

        if self._style_key is not None:
            styles = self._apply_transforms(element, ranges, style)
            opts[self._style_key] = {
                STYLE_ALIASES.get(k, k): v
                for k, v in styles.items()
            }
        else:
            opts.update({
                STYLE_ALIASES.get(k, k): v
                for k, v in style.items() if k != 'cmap'
            })

        return opts

    def init_graph(self, data, options, index=0):
        trace = dict(options)
        for k, v in data.items():
            if k in trace and isinstance(trace[k], dict):
                trace[k].update(v)
            else:
                trace[k] = v

        if self._style_key and self._per_trace:
            vectorized = {
                k: v
                for k, v in options[self._style_key].items()
                if isinstance(v, np.ndarray)
            }
            trace[self._style_key] = dict(trace[self._style_key])
            for s, val in vectorized.items():
                trace[self._style_key][s] = val[index]
        return [trace]

    def get_data(self, element, ranges, style):
        return []

    def get_aspect(self, xspan, yspan):
        """
        Computes the aspect ratio of the plot
        """
        return self.width / self.height

    def _get_axis_dims(self, element):
        """Returns the dimensions corresponding to each axis.

        Should return a list of dimensions or list of lists of
        dimensions, which will be formatted to label the axis
        and to link axes.
        """
        dims = element.dimensions()[:3]
        pad = [None] * max(3 - len(dims), 0)
        return dims + pad

    def _apply_transforms(self, element, ranges, style):
        new_style = dict(style)
        for k, v in dict(style).items():
            if isinstance(v, util.basestring):
                if k == 'marker' and v in 'xsdo':
                    continue
                elif v in element:
                    v = dim(v)
                elif any(d == v for d in self.overlay_dims):
                    v = dim([d for d in self.overlay_dims if d == v][0])

            if not isinstance(v, dim):
                continue
            elif (not v.applies(element)
                  and v.dimension not in self.overlay_dims):
                new_style.pop(k)
                self.warning(
                    'Specified %s dim transform %r could not be applied, as not all '
                    'dimensions could be resolved.' % (k, v))
                continue

            if len(v.ops) == 0 and v.dimension in self.overlay_dims:
                val = self.overlay_dims[v.dimension]
            else:
                val = v.apply(element, ranges=ranges, flat=True)

            if (not util.isscalar(val) and len(util.unique_array(val)) == 1
                    and not 'color' in k):
                val = val[0]

            if not util.isscalar(val):
                if k in self._nonvectorized_styles:
                    element = type(element).__name__
                    raise ValueError(
                        'Mapping a dimension to the "{style}" '
                        'style option is not supported by the '
                        '{element} element using the {backend} '
                        'backend. To map the "{dim}" dimension '
                        'to the {style} use a groupby operation '
                        'to overlay your data along the dimension.'.format(
                            style=k,
                            dim=v.dimension,
                            element=element,
                            backend=self.renderer.backend))

            # If color is not valid colorspec add colormapper
            numeric = isinstance(val, np.ndarray) and val.dtype.kind in 'uifMm'
            if ('color' in k and isinstance(val, np.ndarray) and numeric):
                copts = self.get_color_opts(v, element, ranges, style)
                new_style.pop('cmap', None)
                new_style.update(copts)
            new_style[k] = val
        return new_style

    def init_layout(self, key, element, ranges):
        el = element.traverse(lambda x: x, [Element])
        el = el[0] if el else element

        extent = self.get_extents(element, ranges)

        if len(extent) == 4:
            l, b, r, t = extent
        else:
            l, b, z0, r, t, z1 = extent

        options = {}

        dims = self._get_axis_dims(el)
        if len(dims) > 2:
            xdim, ydim, zdim = dims
        else:
            xdim, ydim = dims
            zdim = None
        xlabel, ylabel, zlabel = self._get_axis_labels(dims)

        if self.invert_axes:
            xlabel, ylabel = ylabel, xlabel
            ydim, xdim = xdim, ydim
            l, b, r, t = b, l, t, r

        if 'x' not in self.labelled:
            xlabel = ''
        if 'y' not in self.labelled:
            ylabel = ''
        if 'z' not in self.labelled:
            zlabel = ''

        if xdim:
            xrange = [r, l] if self.invert_xaxis else [l, r]
            xaxis = dict(range=xrange, title=xlabel)
            if self.logx:
                xaxis['type'] = 'log'
            self._get_ticks(xaxis, self.xticks)
        else:
            xaxis = {}

        if ydim:
            yrange = [t, b] if self.invert_yaxis else [b, t]
            yaxis = dict(range=yrange, title=ylabel)
            if self.logy:
                yaxis['type'] = 'log'
            self._get_ticks(yaxis, self.yticks)
        else:
            yaxis = {}

        if self.projection == '3d':
            scene = dict(xaxis=xaxis, yaxis=yaxis)
            if zdim:
                zrange = [z1, z0] if self.invert_zaxis else [z0, z1]
                zaxis = dict(range=zrange, title=zlabel)
                if self.logz:
                    zaxis['type'] = 'log'
                self._get_ticks(zaxis, self.zticks)
                scene['zaxis'] = zaxis
            if self.aspect == 'cube':
                scene['aspectmode'] = 'cube'
            else:
                scene['aspectmode'] = 'manual'
                scene['aspectratio'] = self.aspect
            options['scene'] = scene
        else:
            l, b, r, t = self.margins
            options['xaxis'] = xaxis
            options['yaxis'] = yaxis
            options['margin'] = dict(l=l, r=r, b=b, t=t, pad=4)

        return dict(width=self.width,
                    height=self.height,
                    title=self._format_title(key, separator=' '),
                    plot_bgcolor=self.bgcolor,
                    **options)

    def _get_ticks(self, axis, ticker):
        axis_props = {}
        if isinstance(ticker, (tuple, list)):
            if all(isinstance(t, tuple) for t in ticker):
                ticks, labels = zip(*ticker)
                labels = [
                    l if isinstance(l, util.basestring) else str(l)
                    for l in labels
                ]
                axis_props['tickvals'] = ticks
                axis_props['ticktext'] = labels
            else:
                axis_props['tickvals'] = ticker
            axis.update(axis_props)

    def update_frame(self, key, ranges=None, element=None):
        """
        Updates an existing plot with data corresponding
        to the key.
        """
        self.generate_plot(key, ranges, element)
Exemple #8
0
class stack(Operation):
    """
    The stack operation allows compositing multiple RGB Elements using
    the defined compositing operator.
    """

    compositor = param.ObjectSelector(
        objects=['add', 'over', 'saturate', 'source'],
        default='over',
        doc="""
        Defines how the compositing operation combines the images""")

    def uint8_to_uint32(self, element):
        img = np.dstack(
            [element.dimension_values(d, flat=False) for d in element.vdims])
        if img.shape[2] == 3:  # alpha channel not included
            alpha = np.ones(img.shape[:2])
            if img.dtype.name == 'uint8':
                alpha = (alpha * 255).astype('uint8')
            img = np.dstack([img, alpha])
        if img.dtype.name != 'uint8':
            img = (img * 255).astype(np.uint8)
        N, M, _ = img.shape
        return img.view(dtype=np.uint32).reshape((N, M))

    def _process(self, overlay, key=None):
        if not isinstance(overlay, CompositeOverlay):
            return overlay
        elif len(overlay) == 1:
            return overlay.last if isinstance(overlay,
                                              NdOverlay) else overlay.get(0)

        imgs = []
        for rgb in overlay:
            if not isinstance(rgb, RGB):
                raise TypeError('stack operation expect RGB type elements, '
                                'not %s name.' % type(rgb).__name__)
            rgb = rgb.rgb
            dims = [kd.name for kd in rgb.kdims][::-1]
            coords = {
                kd.name: rgb.dimension_values(kd, False)
                for kd in rgb.kdims
            }
            imgs.append(
                tf.Image(self.uint8_to_uint32(rgb), coords=coords, dims=dims))

        try:
            imgs = xr.align(*imgs, join='exact')
        except ValueError:
            raise ValueError(
                'RGB inputs to stack operation could not be aligned, '
                'ensure they share the same grid sampling.')

        stacked = tf.stack(*imgs, how=self.p.compositor)
        arr = shade.uint32_to_uint8(stacked.data)[::-1]
        data = (coords[dims[1]], coords[dims[0]], arr[:, :,
                                                      0], arr[:, :,
                                                              1], arr[:, :, 2])
        if arr.shape[-1] == 4:
            data = data + (arr[:, :, 3], )
        return rgb.clone(data,
                         datatype=[rgb.interface.datatype] + rgb.datatype)
Exemple #9
0
class dynspread(Operation):
    """
    Spreading expands each pixel in an Image based Element a certain
    number of pixels on all sides according to a given shape, merging
    pixels using a specified compositing operator. This can be useful
    to make sparse plots more visible. Dynamic spreading determines
    how many pixels to spread based on a density heuristic.

    See the datashader documentation for more detail:

    http://datashader.readthedocs.io/en/latest/api.html#datashader.transfer_functions.dynspread
    """

    how = param.ObjectSelector(default='source',
                               objects=['source', 'over', 'saturate', 'add'],
                               doc="""
        The name of the compositing operator to use when combining
        pixels.""")

    max_px = param.Integer(default=3,
                           doc="""
        Maximum number of pixels to spread on all sides.""")

    shape = param.ObjectSelector(default='circle',
                                 objects=['circle', 'square'],
                                 doc="""
        The shape to spread by. Options are 'circle' [default] or 'square'.""")

    threshold = param.Number(default=0.5,
                             bounds=(0, 1),
                             doc="""
        When spreading, determines how far to spread.
        Spreading starts at 1 pixel, and stops when the fraction
        of adjacent non-empty pixels reaches this threshold.
        Higher values give more spreading, up to the max_px
        allowed.""")

    link_inputs = param.Boolean(default=True,
                                doc="""
        By default, the link_inputs parameter is set to True so that
        when applying dynspread, backends that support linked streams
        update RangeXY streams on the inputs of the dynspread operation.
        Disable when you do not want the resulting plot to be interactive,
        e.g. when trying to display an interactive plot a second time.""")

    @classmethod
    def uint8_to_uint32(cls, img):
        shape = img.shape
        flat_shape = np.multiply.reduce(shape[:2])
        rgb = img.reshape((flat_shape, 4)).view('uint32').reshape(shape[:2])
        return rgb

    def _apply_dynspread(self, array):
        img = tf.Image(array)
        return tf.dynspread(img,
                            max_px=self.p.max_px,
                            threshold=self.p.threshold,
                            how=self.p.how,
                            shape=self.p.shape).data

    def _process(self, element, key=None):
        if not isinstance(element, RGB):
            raise ValueError('dynspread can only be applied to RGB Elements.')
        rgb = element.rgb
        new_data = {
            kd.name: rgb.dimension_values(kd, expanded=False)
            for kd in rgb.kdims
        }
        rgbarray = np.dstack(
            [element.dimension_values(vd, flat=False) for vd in element.vdims])
        data = self.uint8_to_uint32(rgbarray)
        array = self._apply_dynspread(data)
        img = datashade.uint32_to_uint8(array)
        for i, vd in enumerate(element.vdims):
            if i < img.shape[-1]:
                new_data[vd.name] = np.flipud(img[..., i])
        return element.clone(new_data)
Exemple #10
0
class trimesh_rasterize(aggregate):
    """
    Rasterize the TriMesh element using the supplied aggregator. If
    the TriMesh nodes or edges define a value dimension will plot
    filled and shaded polygons otherwise returns a wiremesh of the
    data.
    """

    aggregator = param.ClassSelector(class_=ds.reductions.Reduction,
                                     default=None)

    interpolation = param.ObjectSelector(default='bilinear',
                                         objects=['bilinear', None],
                                         doc="""
        The interpolation method to apply during rasterization.""")

    def _precompute(self, element):
        from datashader.utils import mesh
        if element.vdims:
            simplices = element.dframe([0, 1, 2, 3])
            verts = element.nodes.dframe([0, 1])
        elif element.nodes.vdims:
            simplices = element.dframe([0, 1, 2])
            verts = element.nodes.dframe([0, 1, 3])
        return {
            'mesh': mesh(verts, simplices),
            'simplices': simplices,
            'vertices': verts
        }

    def _process(self, element, key=None):
        if isinstance(element, TriMesh):
            x, y = element.nodes.kdims[:2]
        else:
            x, y = element.kdims
        info = self._get_sampling(element, x, y)
        (x_range, y_range), _, (width, height), (xtype, ytype) = info
        cvs = ds.Canvas(plot_width=width,
                        plot_height=height,
                        x_range=x_range,
                        y_range=y_range)

        if not (element.vdims or element.nodes.vdims):
            return aggregate._process(self, element, key)
        elif element._plot_id in self._precomputed:
            precomputed = self._precomputed[element._plot_id]
        else:
            precomputed = self._precompute(element)
        simplices = precomputed['simplices']
        pts = precomputed['vertices']
        mesh = precomputed['mesh']
        if self.p.precompute:
            self._precomputed = {element._plot_id: precomputed}

        vdim = element.vdims[0] if element.vdims else element.nodes.vdims[0]
        interpolate = bool(self.p.interpolation)
        agg = cvs.trimesh(pts,
                          simplices,
                          agg=self.p.aggregator,
                          interp=interpolate,
                          mesh=mesh)
        params = dict(get_param_values(element),
                      kdims=[x, y],
                      datatype=['xarray'],
                      vdims=[vdim])
        return Image(agg, **params)
Exemple #11
0
class rasterize(ResamplingOperation):
    """
    Rasterize is a high-level operation which will rasterize any
    Element or combination of Elements aggregating it with the supplied
    aggregator and interpolation method.

    The default aggregation method depends on the type of Element but
    usually defaults to the count of samples in each bin, other
    aggregators can be supplied implementing mean, max, min and other
    reduction operations.

    The bins of the aggregate are defined by the width and height and
    the x_range and y_range. If x_sampling or y_sampling are supplied
    the operation will ensure that a bin is no smaller than the minimum
    sampling distance by reducing the width and height when zoomed in
    beyond the minimum sampling distance.

    By default, the PlotSize and RangeXY streams are applied when this
    operation is used dynamically, which means that the width, height,
    x_range and y_range will automatically be set to match the inner
    dimensions of the linked plot and the ranges of the axes.
    """

    aggregator = param.ClassSelector(class_=ds.reductions.Reduction,
                                     default=None)

    interpolation = param.ObjectSelector(default='bilinear',
                                         objects=['bilinear', None],
                                         doc="""
        The interpolation method to apply during rasterization.""")

    def _process(self, element, key=None):
        # Get input Images to avoid multiple rasterization
        imgs = element.traverse(lambda x: x, [Image])

        # Rasterize TriMeshes
        tri_params = dict(
            {k: v
             for k, v in self.p.items() if k in aggregate.params()},
            dynamic=False)
        trirasterize = trimesh_rasterize.instance(**tri_params)
        trirasterize._precomputed = self._precomputed
        element = element.map(trirasterize, TriMesh)
        self._precomputed = trirasterize._precomputed

        # Rasterize QuadMesh
        quad_params = dict(
            {k: v
             for k, v in self.p.items() if k in aggregate.params()},
            dynamic=False)
        quadrasterize = quadmesh_rasterize.instance(**quad_params)
        quadrasterize._precomputed = self._precomputed
        element = element.map(quadrasterize, QuadMesh)
        self._precomputed = quadrasterize._precomputed

        # Rasterize NdOverlay of objects
        agg_params = dict(
            {k: v
             for k, v in self.p.items() if k in aggregate.params()},
            dynamic=False)
        dsrasterize = aggregate.instance(**agg_params)
        dsrasterize._precomputed = self._precomputed
        predicate = lambda x: (isinstance(x, NdOverlay) and issubclass(
            x.type, Dataset) and not issubclass(x.type, Image))
        element = element.map(dsrasterize, predicate)

        # Rasterize other Dataset types
        predicate = lambda x: (isinstance(x, Dataset) and
                               (not isinstance(x, Image) or x in imgs))
        element = element.map(dsrasterize, predicate)
        self._precomputed = dsrasterize._precomputed

        return element
Exemple #12
0
class regrid(ResamplingOperation):
    """
    regrid allows resampling a HoloViews Image type using specified
    up- and downsampling functions defined using the aggregator and
    interpolation parameters respectively. By default upsampling is
    disabled to avoid unnecessarily upscaling an image that has to be
    sent to the browser. Also disables expanding the image beyond its
    original bounds avoiding unneccessarily padding the output array
    with nan values.
    """

    aggregator = param.ObjectSelector(
        default='mean',
        objects=['first', 'last', 'mean', 'mode', 'std', 'var', 'min', 'max'],
        doc="""
        Aggregation method.
        """)

    expand = param.Boolean(default=False,
                           doc="""
       Whether the x_range and y_range should be allowed to expand
       beyond the extent of the data.  Setting this value to True is
       useful for the case where you want to ensure a certain size of
       output grid, e.g. if you are doing masking or other arithmetic
       on the grids.  A value of False ensures that the grid is only
       just as large as it needs to be to contain the data, which will
       be faster and use less memory if the resulting aggregate is
       being overlaid on a much larger background.""")

    interpolation = param.ObjectSelector(default='nearest',
                                         objects=['linear', 'nearest'],
                                         doc="""
        Interpolation method""")

    upsample = param.Boolean(default=False,
                             doc="""
        Whether to allow upsampling if the source array is smaller
        than the requested array. Setting this value to True will
        enable upsampling using the interpolation method, when the
        requested width and height are larger than what is available
        on the source grid. If upsampling is disabled (the default)
        the width and height are clipped to what is available on the
        source array.""")

    def _process(self, element, key=None):
        if ds_version <= '0.5.0':
            raise RuntimeError('regrid operation requires datashader>=0.6.0')

        x, y = element.kdims
        info = self._get_sampling(element, x, y)
        (x_range, y_range), _, (width, height), (xtype, ytype) = info

        coords = tuple(
            element.dimension_values(d, expanded=False) for d in [x, y])
        coord_dict = {x.name: coords[0], y.name: coords[1]}
        dims = [y.name, x.name]
        arrays = []
        for vd in element.vdims:
            if element.interface is XArrayInterface:
                xarr = element.data[vd.name]
                if 'datetime' in (xtype, ytype):
                    xarr = xarr.copy()
                if dims != xarr.dims:
                    xarr = xarr.transpose(*dims)
            else:
                arr = element.dimension_values(vd, flat=False)
                xarr = xr.DataArray(arr, coords=coord_dict, dims=dims)
            if xtype == "datetime":
                xarr[x.name] = [dt_to_int(v) for v in xarr[x.name].values]
            if ytype == "datetime":
                xarr[y.name] = [dt_to_int(v) for v in xarr[y.name].values]
            arrays.append(xarr)

        # Disable upsampling if requested
        (xstart, xend), (ystart, yend) = (x_range, y_range)
        xspan, yspan = (xend - xstart), (yend - ystart)
        if not self.p.upsample and self.p.target is None:
            (x0, x1), (y0, y1) = element.range(0), element.range(1)
            if isinstance(x0, datetime_types):
                x0, x1 = dt_to_int(x0), dt_to_int(x1)
            if isinstance(y0, datetime_types):
                y0, y1 = dt_to_int(y0), dt_to_int(y1)
            exspan, eyspan = (x1 - x0), (y1 - y0)
            width = min([int((xspan / exspan) * len(coords[0])), width])
            height = min([int((yspan / eyspan) * len(coords[1])), height])

        # Get expanded or bounded ranges
        cvs = ds.Canvas(plot_width=width,
                        plot_height=height,
                        x_range=x_range,
                        y_range=y_range)
        regridded = []
        for xarr in arrays:
            rarray = cvs.raster(xarr,
                                upsample_method=self.p.interpolation,
                                downsample_method=self.p.aggregator)
            if xtype == "datetime":
                rarray[x.name] = rarray[x.name].astype('datetime64[us]')
            if ytype == "datetime":
                rarray[y.name] = rarray[y.name].astype('datetime64[us]')
            regridded.append(rarray)

        regridded = xr.Dataset(
            {vd.name: xarr
             for vd, xarr in zip(element.vdims, regridded)})
        if xtype == 'datetime':
            xstart, xend = np.array([xstart, xend]).astype('datetime64[us]')
        if ytype == 'datetime':
            ystart, yend = np.array([ystart, yend]).astype('datetime64[us]')
        bbox = BoundingBox(points=[(xstart, ystart), (xend, yend)])
        return element.clone(regridded, bounds=bbox, datatype=['xarray'])
Exemple #13
0
class TestPattern(SheetPanel):

    sheet_type = GeneratorSheet

    dock = param.Boolean(False)

    edit_sheet = param.ObjectSelector(doc="""
        Sheet for which to edit pattern properties.""")

    plastic = param.Boolean(default=False,
                            doc="""
        Whether to enable plasticity during presentation.""")

    duration = param.Number(default=1.0,
                            softbounds=(0.0, 10.0),
                            doc="""
        How long to run the simulation for each presentation.""")

    Present = tk.Button(doc="""Present this pattern to the simulation.""")

    pattern_generator = param.ClassSelector(default=Constant(),
                                            class_=PatternGenerator,
                                            doc="""
        Type of pattern to present. Each type has various parameters that can be changed."""
                                            )

    def __init__(self, master, plotgroup=None, **params):
        plotgroup = plotgroup or TestPatternPlotGroup()

        super(TestPattern, self).__init__(master, plotgroup, **params)

        self.auto_refresh = True

        self.plotcommand_frame.pack_forget()
        for name in ['pre_plot_hooks', 'plot_hooks', 'Fwd', 'Back']:
            self.hide_param(name)

        edit_sheet_param = self.get_parameter_object('edit_sheet')
        edit_sheet_param.objects = self.plotgroup.sheets()

        self.pg_control_pane = Frame(self)  #,bd=1,relief="sunken")
        self.pg_control_pane.pack(side="top", expand='yes', fill='x')

        self.params_frame = tk.ParametersFrame(
            self.pg_control_pane,
            parameterized_object=self.pattern_generator,
            on_modify=self.conditional_refresh,
            msg_handler=master.status)

        self.params_frame.hide_param('Close')
        self.params_frame.hide_param('Refresh')

        # CEB: 'new_default=True' is temporary so that the current
        # behavior is the same as before; shoudl make None the
        # default and mean 'apply to all sheets'.
        self.pack_param('edit_sheet',
                        parent=self.pg_control_pane,
                        on_modify=self.switch_sheet,
                        widget_options={
                            'new_default': True,
                            'sort_fn_args': {
                                'cmp':
                                lambda x, y: cmp(-x.precedence, -y.precedence)
                            }
                        })
        self.pack_param('pattern_generator',
                        parent=self.pg_control_pane,
                        on_modify=self.change_pattern_generator,
                        side="top")

        present_frame = Frame(self)
        present_frame.pack(side='bottom')

        self.pack_param('plastic', side='bottom', parent=present_frame)
        self.params_frame.pack(side='bottom', expand='yes', fill='x')
        self.pack_param('duration', parent=present_frame, side='left')
        self.pack_param('Present',
                        parent=present_frame,
                        on_set=self.present_pattern,
                        side="right")

    def setup_plotgroup(self):
        super(TestPattern, self).setup_plotgroup()

        # CB: could copy the sheets instead (deleting connections etc)
        self.plotgroup._sheets = [
            GeneratorSheet(name=gs.name,
                           nominal_bounds=gs.nominal_bounds,
                           nominal_density=gs.nominal_density)
            for gs in topo.sim.objects(GeneratorSheet).values()
        ]
        self.plotgroup._set_name("Test Pattern")

    def switch_sheet(self):
        if self.edit_sheet is not None:
            self.pattern_generator = self.edit_sheet.input_generator
        self.change_pattern_generator()

    def change_pattern_generator(self):
        """
        Set the current PatternGenerator to the one selected and get the
        ParametersFrameWithApply to draw the relevant widgets
        """
        # CEBALERT: if pattern generator is set to None, there will be
        # an error. Need to handle None in the appropriate place
        # (presumably tk.py).
        self.params_frame.set_PO(self.pattern_generator)

        for sheet in self.plotgroup.sheets():
            if sheet == self.edit_sheet:
                sheet.set_input_generator(self.pattern_generator)

        self.conditional_refresh()

    def refresh(self, update=True):
        """
        Simply update the plots: skip all handling of history.
        """
        self.refresh_plots(update)

    def present_pattern(self):
        """
        Move the user created patterns into the GeneratorSheets, run for
        the specified length of time, then restore the original
        patterns.
        """
        input_dict = dict([(sheet.name,sheet.input_generator) \
                           for sheet in self.plotgroup.sheets()])
        pattern_present(inputs=input_dict,
                        durations=[self.duration],
                        plastic=self.plastic,
                        overwrite_previous=False,
                        install_sheetview=True,
                        restore_state=True)
        topo.guimain.auto_refresh(update=False)
Exemple #14
0
class interpolate_curve(Operation):
    """
    Resamples a Curve using the defined interpolation method, e.g.
    to represent changes in y-values as steps.
    """

    interpolation = param.ObjectSelector(
        objects=['steps-pre', 'steps-mid', 'steps-post', 'linear'],
        default='steps-mid',
        doc="""
       Controls the transition point of the step along the x-axis.""")

    _per_element = True

    @classmethod
    def pts_to_prestep(cls, x, values):
        steps = np.zeros(2 * len(x) - 1)
        value_steps = tuple(
            np.empty(2 * len(x) - 1, dtype=v.dtype) for v in values)

        steps[0::2] = x
        steps[1::2] = steps[0:-2:2]

        val_arrays = []
        for v, s in zip(values, value_steps):
            s[0::2] = v
            s[1::2] = s[2::2]
            val_arrays.append(s)

        return steps, tuple(val_arrays)

    @classmethod
    def pts_to_midstep(cls, x, values):
        steps = np.zeros(2 * len(x))
        value_steps = tuple(
            np.empty(2 * len(x), dtype=v.dtype) for v in values)

        steps[1:-1:2] = steps[2::2] = x[:-1] + (x[1:] - x[:-1]) / 2
        steps[0], steps[-1] = x[0], x[-1]

        val_arrays = []
        for v, s in zip(values, value_steps):
            s[0::2] = v
            s[1::2] = s[0::2]
            val_arrays.append(s)

        return steps, tuple(val_arrays)

    @classmethod
    def pts_to_poststep(cls, x, values):
        steps = np.zeros(2 * len(x) - 1)
        value_steps = tuple(
            np.empty(2 * len(x) - 1, dtype=v.dtype) for v in values)

        steps[0::2] = x
        steps[1::2] = steps[2::2]

        val_arrays = []
        for v, s in zip(values, value_steps):
            s[0::2] = v
            s[1::2] = s[0:-2:2]
            val_arrays.append(s)

        return steps, tuple(val_arrays)

    def _process_layer(self, element, key=None):
        INTERPOLATE_FUNCS = {
            'steps-pre': self.pts_to_prestep,
            'steps-mid': self.pts_to_midstep,
            'steps-post': self.pts_to_poststep
        }
        if self.p.interpolation not in INTERPOLATE_FUNCS:
            return element
        x = element.dimension_values(0)
        is_datetime = isdatetime(x)
        if is_datetime:
            dt_type = 'datetime64[ns]'
            x = x.astype(dt_type)
        dvals = tuple(
            element.dimension_values(d) for d in element.dimensions()[1:])
        xs, dvals = INTERPOLATE_FUNCS[self.p.interpolation](x, dvals)
        if is_datetime:
            xs = xs.astype(dt_type)
        return element.clone((xs, ) + dvals)

    def _process(self, element, key=None):
        return element.map(self._process_layer, Element)
class EchartsApp(param.Parameterized):
    """An Echarts app that showcases the Echart component"""

    title = param.String("Awesome Panel")
    plot_type = param.ObjectSelector("bar",
                                     objects=["bar", "scatter"],
                                     label="Plot Type")

    shirt = param.Integer(default=5, bounds=BOUNDS)
    cardign = param.Integer(default=20, bounds=BOUNDS)
    chiffon_shirt = param.Integer(default=36, bounds=BOUNDS)
    pants = param.Integer(default=10, bounds=BOUNDS)
    heels = param.Integer(default=10, bounds=BOUNDS)
    socks = param.Integer(default=20, bounds=BOUNDS)

    def __init__(self, **params):
        super().__init__(**params)

        self.plot = ECharts(height=500,
                            min_width=200,
                            sizing_mode="stretch_width")

        self._update_plot()

    @param.depends(
        "title",
        "plot_type",
        "shirt",
        "cardign",
        "chiffon_shirt",
        "pants",
        "heels",
        "socks",
        watch=True,
    )
    def _update_plot(self):
        echart = {
            "title": {
                "text": self.title
            },
            "tooltip": {},
            "legend": {
                "data": ["Sales"]
            },
            "xAxis": {
                "data": [
                    "shirt", "cardign", "chiffon shirt", "pants", "heels",
                    "socks"
                ]
            },
            "yAxis": {},
            "series": [{
                "name":
                "Sales",
                "type":
                self.plot_type,
                "data": [
                    self.shirt,
                    self.cardign,
                    self.chiffon_shirt,
                    self.pants,
                    self.heels,
                    self.socks,
                ],
                "itemStyle": {
                    "color": "#A01346"
                },
            }],
            "responsive":
            True,
        }
        self.plot.object = echart

    def view(self) -> pn.Column:
        """Returns the view of the application

        Returns:
            pn.Column: The view of the app
        """
        pn.config.sizing_mode = "stretch_width"
        pn.extension("echarts")
        top_app_bar = pn.Row(
            pn.pane.PNG(
                "https://echarts.apache.org/en/images/logo.png",
                sizing_mode="fixed",
                height=40,
                margin=(15, 0, 5, 25),
                embed=False,
            ),
            pn.layout.VSpacer(),
            "",
            background="lightgray",
            height=70,
        )

        settings_pane = pn.Param(
            self,
            show_name=False,
            width=200,
            sizing_mode="fixed",
            background="rgb(245, 247, 250)",
        )

        main = [
            APPLICATION.intro_section(),
            pn.Column(
                top_app_bar,
                pn.layout.HSpacer(height=50),
                pn.Row(self.plot, settings_pane, sizing_mode="stretch_width"),
            ),
        ]
        return pn.template.FastListTemplate(title="Test ECharts",
                                            theme="default",
                                            main=main,
                                            theme_toggle=False)
Exemple #16
0
class GlobalPowerPlantDatabaseApp(param.Parameterized):

    data = param.DataFrame(precedence=-1)

    opacity = param.Number(default=0.8, step=0.05, bounds=(0, 1))

    pitch = param.Number(default=0, bounds=(0, 90))

    zoom = param.Integer(default=1, bounds=(1, 22))

    view_state = param.ObjectSelector(default=VIEW_STATES["World"],
                                      objects=VIEW_STATES)

    def __init__(self, nrows: Optional[int] = None, **params):
        if "data" not in params:
            if nrows:
                params["data"] = self._get_pp_data(nrows=nrows)
            else:
                params["data"] = self._get_pp_data()

        super(GlobalPowerPlantDatabaseApp, self).__init__(**params)

        self._view_state = pdk.ViewState(
            latitude=52.2323,
            longitude=-1.415,
            zoom=self.zoom,
            min_zoom=self.param.zoom.bounds[0],
            max_zoom=self.param.zoom.bounds[1],
        )
        self._scatter = pdk.Layer(
            "ScatterplotLayer",
            data=self.data,
            get_position=["longitude", "latitude"],
            get_fill_color="[color_r, color_g, color_b, color_a]",
            get_radius="capacity_mw*10",
            pickable=True,
            opacity=self.opacity,
            filled=True,
            wireframe=True,
        )
        self._deck = pdk.Deck(
            map_style="mapbox://styles/mapbox/light-v9",
            initial_view_state=self._view_state,
            layers=[self._scatter],
            tooltip=True,
            mapbox_key=MAPBOX_KEY,
        )
        self.pane = pn.pane.DeckGL(self._deck,
                                   sizing_mode="stretch_width",
                                   height=700)
        self.param.watch(self._update, ["data", "opacity", "pitch", "zoom"])

    @staticmethod
    def _get_pp_data(nrows: Optional[int] = None):
        pp_data = pd.read_csv(POWER_PLANT_PATH, nrows=nrows)

        pp_data["primary_fuel_color"] = pp_data.primary_fuel.map(FUEL_COLORS)
        pp_data["primary_fuel_color"] = pp_data["primary_fuel_color"].fillna(
            "gray")
        pp_data["color_r"] = pp_data["primary_fuel_color"].map(COLORS_R)
        pp_data["color_g"] = pp_data["primary_fuel_color"].map(COLORS_G)
        pp_data["color_b"] = pp_data["primary_fuel_color"].map(COLORS_B)
        pp_data["color_a"] = 140

        # "name", "primary_fuel", "capacity_mw",
        pp_data = pp_data[[
            "latitude",
            "longitude",
            "name",
            "capacity_mw",
            "color_r",
            "color_g",
            "color_b",
            "color_a",
        ]]
        return pp_data

    @pn.depends("pane.hover_state", "data")
    def _info_pane(self):
        index = self.pane.hover_state.get("index", -1)
        if index == -1:
            index = slice(0, 0)
        return self.data.iloc[index][["name", "capacity_mw"]]

    @pn.depends("view_state", watch=True)
    def _update_view_state_from_selection(self):
        self._view_state.latitude = self.view_state.latitude
        self._view_state.longitude = self.view_state.longitude
        self._view_state.zoom = self.view_state.zoom
        self.pane.param.trigger("object")
        print(self._view_state)

    @pn.depends("pane.view_State", watch=True)
    def _update(self):
        print("update")
        state = self.pane.view_state
        self._view_state.longitude = state["longitude"]
        self._view_state.latitude = state["latitude"]

    def _update2(self, event):
        print(event.name)
        if event.name == "data":
            self._scatter.data = self.data
        if event.name == "opacity":
            self._scatter.opacity = self.opacity
        if event.name == "zoom":
            self._view_state.zoom = self.zoom
        if event.name == "pitch":
            self._view_state.pitch = self.pitch
        self.pane.param.trigger("object")

    def _view_state_pane(self):
        return pn.Param(
            self,
            parameters=["view_state"],
            show_name=False,
            widgets={"view_state": pn.widgets.RadioButtonGroup},
            default_layout=pn.Column,
        )

    def _settings_pane(self):
        return pn.Param(
            self,
            parameters=["opacity", "pitch", "zoom"],
            show_name=False,
            widgets={"view_state": pn.widgets.RadioButtonGroup},
        )

    def view(self):
        # self._info_pane,  does not work
        return pn.Row(
            pn.Column(self._view_state_pane, self.pane),
            pn.Column(self._settings_pane, width=300, sizing_mode="fixed"),
        )
Exemple #17
0
class VectorFieldPlot(ColorbarPlot):

    arrow_heads = param.Boolean(default=True,
                                doc="""
       Whether or not to draw arrow heads.""")

    color_index = param.ClassSelector(default=None,
                                      class_=(basestring, int),
                                      allow_None=True,
                                      doc="""
      Index of the dimension from which the color will the drawn""")

    size_index = param.ClassSelector(default=None,
                                     class_=(basestring, int),
                                     allow_None=True,
                                     doc="""
      Index of the dimension from which the sizes will the drawn.""")

    normalize_lengths = param.Boolean(default=True,
                                      doc="""
       Whether to normalize vector magnitudes automatically. If False,
       it will be assumed that the lengths have already been correctly
       normalized.""")

    pivot = param.ObjectSelector(default='mid',
                                 objects=['mid', 'tip', 'tail'],
                                 doc="""
       The point around which the arrows should pivot valid options
       include 'mid', 'tip' and 'tail'.""")

    rescale_lengths = param.Boolean(default=True,
                                    doc="""
       Whether the lengths will be rescaled to take into account the
       smallest non-zero distance between two vectors.""")

    style_opts = line_properties + ['scale', 'cmap']
    _plot_methods = dict(single='segment')

    def _get_lengths(self, element, ranges):
        mag_dim = element.get_dimension(self.size_index)
        (x0, x1), (y0, y1) = (element.range(i) for i in range(2))
        base_dist = get_min_distance(element)
        if mag_dim:
            magnitudes = element.dimension_values(mag_dim)
            _, max_magnitude = ranges[mag_dim.name]
            if self.normalize_lengths and max_magnitude != 0:
                magnitudes = magnitudes / max_magnitude
            if self.rescale_lengths:
                magnitudes *= base_dist
        else:
            magnitudes = np.ones(len(element))
            if self.rescale_lengths:
                magnitudes *= base_dist

        return magnitudes

    def _glyph_properties(self, *args):
        properties = super(VectorFieldPlot, self)._glyph_properties(*args)
        properties.pop('scale', None)
        return properties

    def get_data(self, element, ranges=None, empty=False):
        style = self.style[self.cyclic_index]
        input_scale = style.pop('scale', 1.0)

        # Get x, y, angle, magnitude and color data
        xidx, yidx = (1, 0) if self.invert_axes else (0, 1)
        rads = element.dimension_values(2)
        lens = self._get_lengths(element, ranges) / input_scale
        cdim = element.get_dimension(self.color_index)
        cdata, cmapping = self._get_color_data(element,
                                               ranges,
                                               style,
                                               name='line_color')

        # Compute segments and arrowheads
        xs = element.dimension_values(xidx)
        ys = element.dimension_values(yidx)

        # Compute offset depending on pivot option
        xoffsets = np.cos(rads) * lens / 2.
        yoffsets = np.sin(rads) * lens / 2.
        if self.pivot == 'mid':
            nxoff, pxoff = xoffsets, xoffsets
            nyoff, pyoff = yoffsets, yoffsets
        elif self.pivot == 'tip':
            nxoff, pxoff = 0, xoffsets * 2
            nyoff, pyoff = 0, yoffsets * 2
        elif self.pivot == 'tail':
            nxoff, pxoff = xoffsets * 2, 0
            nyoff, pyoff = yoffsets * 2, 0
        x0s, x1s = (xs + nxoff, xs - pxoff)
        y0s, y1s = (ys + nyoff, ys - pyoff)

        if self.arrow_heads:
            arrow_len = (lens / 4.)
            xa1s = x0s - np.cos(rads + np.pi / 4) * arrow_len
            ya1s = y0s - np.sin(rads + np.pi / 4) * arrow_len
            xa2s = x0s - np.cos(rads - np.pi / 4) * arrow_len
            ya2s = y0s - np.sin(rads - np.pi / 4) * arrow_len
            x0s = np.concatenate([x0s, x0s, x0s])
            x1s = np.concatenate([x1s, xa1s, xa2s])
            y0s = np.concatenate([y0s, y0s, y0s])
            y1s = np.concatenate([y1s, ya1s, ya2s])
            if cdim:
                color = cdata.get(cdim.name)
                color = np.concatenate([color, color, color])
        elif cdim:
            color = cdata.get(cdim.name)

        data = {'x0': x0s, 'x1': x1s, 'y0': y0s, 'y1': y1s}
        mapping = dict(x0='x0', x1='x1', y0='y0', y1='y1')
        if cdim:
            data[cdim.name] = color
            mapping.update(cmapping)

        return (data, mapping)
class bivariate_kde(Operation):
    """
    Computes a 2D kernel density estimate (KDE) of the first two
    dimensions in the input data. Kernel density estimation is a
    non-parametric way to estimate the probability density function of
    a random variable.

    The KDE works by placing 2D Gaussian kernel at each sample with
    the supplied bandwidth. These kernels are then summed to produce
    the density estimate. By default a good bandwidth is determined
    using the bw_method but it may be overridden by an explicit value.
    """

    contours = param.Boolean(default=True,
                             doc="""
        Whether to compute contours from the KDE, determines whether to
        return an Image or Contours/Polygons.""")

    bw_method = param.ObjectSelector(default='scott',
                                     objects=['scott', 'silverman'],
                                     doc="""
        Method of automatically determining KDE bandwidth""")

    bandwidth = param.Number(default=None,
                             doc="""
        Allows supplying explicit bandwidth value rather than relying
        on scott or silverman method.""")

    cut = param.Number(default=3,
                       doc="""
        Draw the estimate to cut * bw from the extreme data points.""")

    filled = param.Boolean(default=False,
                           doc="""
        Controls whether to return filled or unfilled contours.""")

    levels = param.ClassSelector(default=10,
                                 class_=(list, int),
                                 doc="""
        A list of scalar values used to specify the contour levels.""")

    n_samples = param.Integer(default=100,
                              doc="""
        Number of samples to compute the KDE over.""")

    x_range = param.NumericTuple(default=None,
                                 length=2,
                                 doc="""
       The x_range as a tuple of min and max x-value. Auto-ranges
       if set to None.""")

    y_range = param.NumericTuple(default=None,
                                 length=2,
                                 doc="""
       The x_range as a tuple of min and max y-value. Auto-ranges
       if set to None.""")

    _per_element = True

    def _process(self, element, key=None):
        try:
            from scipy import stats
        except ImportError:
            raise ImportError('%s operation requires SciPy to be installed.' %
                              type(self).__name__)

        if len(element.dimensions()) < 2:
            raise ValueError("bivariate_kde can only be computed on elements "
                             "declaring at least two dimensions.")
        xdim, ydim = element.dimensions()[:2]
        params = {}
        if isinstance(element, Bivariate):
            if element.group != type(element).__name__:
                params['group'] = element.group
            params['label'] = element.label
            vdim = element.vdims[0]
        else:
            vdim = 'Density'

        data = element.array([0, 1]).T
        xmin, xmax = self.p.x_range or element.range(0)
        ymin, ymax = self.p.y_range or element.range(1)
        if any(not isfinite(v) for v in (xmin, xmax)):
            xmin, xmax = -0.5, 0.5
        elif xmin == xmax:
            xmin, xmax = xmin - 0.5, xmax + 0.5
        if any(not isfinite(v) for v in (ymin, ymax)):
            ymin, ymax = -0.5, 0.5
        elif ymin == ymax:
            ymin, ymax = ymin - 0.5, ymax + 0.5

        data = data[:, isfinite(data).min(
            axis=0)] if data.shape[1] > 1 else np.empty((2, 0))
        if data.shape[1] > 1:
            kde = stats.gaussian_kde(data)
            if self.p.bandwidth:
                kde.set_bandwidth(self.p.bandwidth)
            bw = kde.scotts_factor() * data.std(ddof=1)
            if self.p.x_range:
                xs = np.linspace(xmin, xmax, self.p.n_samples)
            else:
                xs = _kde_support((xmin, xmax), bw, self.p.n_samples,
                                  self.p.cut, xdim.range)
            if self.p.y_range:
                ys = np.linspace(ymin, ymax, self.p.n_samples)
            else:
                ys = _kde_support((ymin, ymax), bw, self.p.n_samples,
                                  self.p.cut, ydim.range)
            xx, yy = cartesian_product([xs, ys], False)
            positions = np.vstack([xx.ravel(), yy.ravel()])
            f = np.reshape(kde(positions).T, xx.shape)
        elif self.p.contours:
            eltype = Polygons if self.p.filled else Contours
            return eltype([], kdims=[xdim, ydim], vdims=[vdim])
        else:
            xs = np.linspace(xmin, xmax, self.p.n_samples)
            ys = np.linspace(ymin, ymax, self.p.n_samples)
            f = np.zeros((self.p.n_samples, self.p.n_samples))

        img = Image((xs, ys, f.T),
                    kdims=element.dimensions()[:2],
                    vdims=[vdim],
                    **params)
        if self.p.contours:
            cntr = contours(img, filled=self.p.filled, levels=self.p.levels)
            return cntr.clone(cntr.data[1:], **params)
        return img
Exemple #19
0
class CurvePlot(ElementPlot):

    interpolation = param.ObjectSelector(
        objects=['linear', 'steps-mid', 'steps-pre', 'steps-post'],
        default='linear',
        doc="""
        Defines how the samples of the Curve are interpolated,
        default is 'linear', other options include 'steps-mid',
        'steps-pre' and 'steps-post'.""")

    style_opts = line_properties
    _plot_methods = dict(single='line', batched='multi_line')
    _batched_style_opts = line_properties

    def get_data(self, element, ranges=None, empty=False):
        if 'steps' in self.interpolation:
            element = interpolate_curve(element,
                                        interpolation=self.interpolation)
        xidx, yidx = (1, 0) if self.invert_axes else (0, 1)
        x = element.get_dimension(xidx).name
        y = element.get_dimension(yidx).name
        data = {
            x: [] if empty else element.dimension_values(xidx),
            y: [] if empty else element.dimension_values(yidx)
        }
        self._get_hover_data(data, element, empty)
        self._categorize_data(data, (x, y), element.dimensions())
        return (data, dict(x=x, y=y))

    def _hover_opts(self, element):
        if self.batched:
            dims = list(self.hmap.last.kdims)
            line_policy = 'prev'
        else:
            dims = list(self.overlay_dims.keys()) + element.dimensions()
            line_policy = 'nearest'
        return dims, dict(line_policy=line_policy)

    def get_batched_data(self, overlay, ranges=None, empty=False):
        data = defaultdict(list)

        zorders = self._updated_zorders(overlay)
        styles = self.lookup_options(overlay.last, 'style')
        styles = styles.max_cycles(len(self.ordering))

        for (key, el), zorder in zip(overlay.data.items(), zorders):
            self.set_param(**self.lookup_options(el, 'plot').options)
            eldata, elmapping = self.get_data(el, ranges, empty)
            for k, eld in eldata.items():
                data[k].append(eld)

            # Apply static styles
            style = styles[zorder]
            sdata, smapping = expand_batched_style(style,
                                                   self._batched_style_opts,
                                                   elmapping,
                                                   nvals=1)
            elmapping.update(smapping)
            for k, v in sdata.items():
                data[k].append(v[0])

            for d, k in zip(overlay.kdims, key):
                sanitized = dimension_sanitizer(d.name)
                data[sanitized].append(k)
        data = {
            opt: vals
            for opt, vals in data.items() if not any(v is None for v in vals)
        }
        mapping = {{
            'x': 'xs',
            'y': 'ys'
        }.get(k, k): v
                   for k, v in elmapping.items()}
        return data, mapping
class univariate_kde(Operation):
    """
    Computes a 1D kernel density estimate (KDE) along the supplied
    dimension. Kernel density estimation is a non-parametric way to
    estimate the probability density function of a random variable.

    The KDE works by placing a Gaussian kernel at each sample with
    the supplied bandwidth. These kernels are then summed to produce
    the density estimate. By default a good bandwidth is determined
    using the bw_method but it may be overridden by an explicit value.
    """

    bw_method = param.ObjectSelector(default='scott',
                                     objects=['scott', 'silverman'],
                                     doc="""
        Method of automatically determining KDE bandwidth""")

    bandwidth = param.Number(default=None,
                             doc="""
        Allows supplying explicit bandwidth value rather than relying on scott or silverman method."""
                             )

    cut = param.Number(default=3,
                       doc="""
        Draw the estimate to cut * bw from the extreme data points.""")

    bin_range = param.NumericTuple(default=None,
                                   length=2,
                                   doc="""
        Specifies the range within which to compute the KDE.""")

    dimension = param.String(default=None,
                             doc="""
        Along which dimension of the Element to compute the KDE.""")

    filled = param.Boolean(default=True,
                           doc="""
        Controls whether to return filled or unfilled KDE.""")

    n_samples = param.Integer(default=100,
                              doc="""
        Number of samples to compute the KDE over.""")

    groupby = param.ClassSelector(default=None,
                                  class_=(basestring, Dimension),
                                  doc="""
      Defines a dimension to group the Histogram returning an NdOverlay of Histograms."""
                                  )

    _per_element = True

    def _process(self, element, key=None):
        if self.p.groupby:
            if not isinstance(element, Dataset):
                raise ValueError(
                    'Cannot use histogram groupby on non-Dataset Element')
            grouped = element.groupby(self.p.groupby,
                                      group_type=Dataset,
                                      container_type=NdOverlay)
            self.p.groupby = None
            return grouped.map(self._process, Dataset)

        try:
            from scipy import stats
            from scipy.linalg import LinAlgError
        except ImportError:
            raise ImportError('%s operation requires SciPy to be installed.' %
                              type(self).__name__)

        params = {}
        if isinstance(element, Distribution):
            selected_dim = element.kdims[0]
            if element.group != type(element).__name__:
                params['group'] = element.group
            params['label'] = element.label
            vdim = element.vdims[0]
            vdim_name = '{}_density'.format(selected_dim.name)
            vdims = [
                vdim.clone(vdim_name, label='Density')
                if vdim.name == 'Density' else vdim
            ]
        else:
            if self.p.dimension:
                selected_dim = element.get_dimension(self.p.dimension)
            else:
                dimensions = element.vdims + element.kdims
                if not dimensions:
                    raise ValueError(
                        "%s element does not declare any dimensions "
                        "to compute the kernel density estimate on." %
                        type(element).__name__)
                selected_dim = dimensions[0]
            vdim_name = '{}_density'.format(selected_dim.name)
            vdims = [Dimension(vdim_name, label='Density')]

        data = element.dimension_values(selected_dim)
        bin_range = self.p.bin_range or element.range(selected_dim)
        if bin_range == (0, 0) or any(not isfinite(r) for r in bin_range):
            bin_range = (0, 1)
        elif bin_range[0] == bin_range[1]:
            bin_range = (bin_range[0] - 0.5, bin_range[1] + 0.5)

        element_type = Area if self.p.filled else Curve
        data = data[isfinite(data)] if len(data) else []
        if len(data) > 1:
            try:
                kde = stats.gaussian_kde(data)
            except LinAlgError:
                return element_type([], selected_dim, vdims, **params)
            if self.p.bandwidth:
                kde.set_bandwidth(self.p.bandwidth)
            bw = kde.scotts_factor() * data.std(ddof=1)
            if self.p.bin_range:
                xs = np.linspace(bin_range[0], bin_range[1], self.p.n_samples)
            else:
                xs = _kde_support(bin_range, bw, self.p.n_samples, self.p.cut,
                                  selected_dim.range)
            ys = kde.evaluate(xs)
        else:
            xs = np.linspace(bin_range[0], bin_range[1], self.p.n_samples)
            ys = np.full_like(xs, 0)

        return element_type((xs, ys),
                            kdims=[selected_dim],
                            vdims=vdims,
                            **params)
Exemple #21
0
class PlotlyRenderer(Renderer):

    backend = param.String(default='plotly', doc="The backend name.")

    fig = param.ObjectSelector(default='auto',
                               objects=['html', 'json', 'auto'],
                               doc="""
        Output render format for static figures. If None, no figure
        rendering will occur. """)

    mode_formats = {
        'fig': {
            'default': ['html', 'json']
        },
        'holomap': {
            'default': ['widgets', 'scrubber', 'auto']
        }
    }

    widgets = {
        'scrubber': PlotlyScrubberWidget,
        'widgets': PlotlySelectionWidget
    }

    comms = {'default': (JupyterComm, plotly_msg_handler)}

    _loaded = False

    def __call__(self, obj, fmt='html', divuuid=None):
        plot, fmt = self._validate(obj, fmt)
        mime_types = {'file-ext': fmt, 'mime_type': MIME_TYPES[fmt]}

        if isinstance(plot, tuple(self.widgets.values())):
            return plot(), mime_types
        elif fmt == 'html':
            return self.figure_data(plot, divuuid=divuuid), mime_types
        elif fmt == 'json':
            return self.diff(plot), mime_types

    def diff(self, plot, serialize=True):
        """
        Returns a json diff required to update an existing plot with
        the latest plot data.
        """
        diff = {
            'data': plot.state.get('data', []),
            'layout': plot.state.get('layout', {})
        }
        if serialize:
            return json.dumps(diff, cls=utils.PlotlyJSONEncoder)
        else:
            return diff

    def figure_data(self,
                    plot,
                    divuuid=None,
                    comm=True,
                    width=800,
                    height=600):
        figure = plot.state
        if divuuid is None:
            if plot.comm:
                divuuid = plot.comm.id
            else:
                divuuid = uuid.uuid4().hex

        jdata = json.dumps(figure.get('data', []), cls=utils.PlotlyJSONEncoder)
        jlayout = json.dumps(figure.get('layout', {}),
                             cls=utils.PlotlyJSONEncoder)

        config = {}
        config['showLink'] = False
        jconfig = json.dumps(config)

        header = ('<script type="text/javascript">'
                  'window.PLOTLYENV=window.PLOTLYENV || {};'
                  '</script>')

        script = '\n'.join([
            'Plotly.plot("{id}", {data}, {layout}, {config}).then(function() {{',
            '    $(".{id}.loading").remove();', '}})'
        ]).format(id=divuuid, data=jdata, layout=jlayout, config=jconfig)

        content = ('<div class="{id} loading" style="color: rgb(50,50,50);">'
                   'Drawing...</div>'
                   '<div id="{id}" style="height: {height}; width: {width};" '
                   'class="plotly-graph-div">'
                   '</div>'
                   '<script type="text/javascript">'
                   '{script}'
                   '</script>').format(id=divuuid,
                                       script=script,
                                       height=height,
                                       width=width)
        joined = '\n'.join([header, content])

        if comm and plot.comm is not None:
            comm, msg_handler = self.comms[self.mode]
            msg_handler = msg_handler.format(comm_id=plot.comm.id)
            return comm.template.format(init_frame=joined,
                                        msg_handler=msg_handler,
                                        comm_id=plot.comm.id)
        return joined

    @classmethod
    def plot_options(cls, obj, percent_size):
        factor = percent_size / 100.0
        obj = obj.last if isinstance(obj, HoloMap) else obj
        plot = Store.registry[cls.backend].get(type(obj), None)
        options = Store.lookup_options(cls.backend, obj, 'plot').options
        width = options.get('width', plot.width) * factor
        height = options.get('height', plot.height) * factor
        return dict(options, **{'width': int(width), 'height': int(height)})

    @classmethod
    def load_nb(cls, inline=True):
        """
        Loads the plotly notebook resources.
        """
        from IPython.display import display, HTML
        if not cls._loaded:
            display(HTML(PLOTLY_WARNING))
            cls._loaded = True
        display(HTML(plotly_include()))
Exemple #22
0
class BokehRenderer(Renderer):

    backend = param.String(default='bokeh', doc="The backend name.")

    fig = param.ObjectSelector(default='auto', objects=['html', 'json', 'auto', 'png'], doc="""
        Output render format for static figures. If None, no figure
        rendering will occur. """)

    holomap = param.ObjectSelector(default='auto',
                                   objects=['widgets', 'scrubber', 'server',
                                            None, 'auto'], doc="""
        Output render multi-frame (typically animated) format. If
        None, no multi-frame rendering will occur.""")

    mode = param.ObjectSelector(default='default',
                                objects=['default', 'server'], doc="""
        Whether to render the object in regular or server mode. In server
        mode a bokeh Document will be returned which can be served as a
        bokeh server app. By default renders all output is rendered to HTML.""")

    # Defines the valid output formats for each mode.
    mode_formats = {'fig': {'default': ['html', 'json', 'auto', 'png'],
                            'server': ['html', 'json', 'auto']},
                    'holomap': {'default': ['widgets', 'scrubber', 'auto', None],
                                'server': ['server', 'auto', None]}}

    webgl = param.Boolean(default=False, doc="""Whether to render plots with WebGL
        if bokeh version >=0.10""")

    widgets = {'scrubber': BokehScrubberWidget,
               'widgets': BokehSelectionWidget,
               'server': BokehServerWidgets}

    backend_dependencies = {'js': CDN.js_files if CDN.js_files else tuple(INLINE.js_raw),
                            'css': CDN.css_files if CDN.css_files else tuple(INLINE.css_raw)}

    comms = {'default': (JupyterComm, None),
             'server': (Comm, None)}

    _loaded = False

    def __call__(self, obj, fmt=None, doc=None):
        """
        Render the supplied HoloViews component using the appropriate
        backend. The output is not a file format but a suitable,
        in-memory byte stream together with any suitable metadata.
        """
        plot, fmt =  self._validate(obj, fmt)
        info = {'file-ext': fmt, 'mime_type': MIME_TYPES[fmt]}

        if self.mode == 'server':
            return self.server_doc(plot, doc), info
        elif isinstance(plot, tuple(self.widgets.values())):
            return plot(), info
        elif fmt == 'png':
            from bokeh.io.export import get_screenshot_as_png
            img = get_screenshot_as_png(plot.state, None)
            imgByteArr = BytesIO()
            img.save(imgByteArr, format='PNG')
            return imgByteArr.getvalue(), info
        elif fmt == 'html':
            html = self._figure_data(plot, doc=doc)
            html = "<div style='display: table; margin: 0 auto;'>%s</div>" % html
            return self._apply_post_render_hooks(html, obj, fmt), info
        elif fmt == 'json':
            return self.diff(plot), info

    @bothmethod
    def _save_prefix(self_or_cls, ext):
        "Hook to prefix content for instance JS when saving HTML"
        if ext == 'html':
            return '\n'.join(self_or_cls.html_assets()).encode('utf8')
        return

    @bothmethod
    def get_plot(self_or_cls, obj, doc=None, renderer=None):
        """
        Given a HoloViews Viewable return a corresponding plot instance.
        Allows supplying a document attach the plot to, useful when
        combining the bokeh model with another plot.
        """
        plot = super(BokehRenderer, self_or_cls).get_plot(obj, renderer)
        if self_or_cls.mode == 'server' and doc is None:
            doc = curdoc()
        if doc is not None:
            plot.document = doc
        return plot


    @bothmethod
    def get_widget(self_or_cls, plot, widget_type, doc=None, **kwargs):
        if not isinstance(plot, Plot):
            plot = self_or_cls.get_plot(plot, doc)
        if self_or_cls.mode == 'server':
            return BokehServerWidgets(plot, renderer=self_or_cls.instance(), **kwargs)
        else:
            return super(BokehRenderer, self_or_cls).get_widget(plot, widget_type, **kwargs)


    @bothmethod
    def app(self_or_cls, plot, show=False, new_window=False, websocket_origin=None):
        """
        Creates a bokeh app from a HoloViews object or plot. By
        default simply attaches the plot to bokeh's curdoc and returns
        the Document, if show option is supplied creates an
        Application instance and displays it either in a browser
        window or inline if notebook extension has been loaded.  Using
        the new_window option the app may be displayed in a new
        browser tab once the notebook extension has been loaded.  A
        websocket origin is required when launching from an existing
        tornado server (such as the notebook) and it is not on the
        default port ('localhost:8888').
        """
        renderer = self_or_cls.instance(mode='server')
        # If show=False and not in notebook context return document
        if not show and not self_or_cls.notebook_context:
            doc, _ = renderer(plot)
            return doc

        def modify_doc(doc):
            renderer(plot, doc=doc)
        handler = FunctionHandler(modify_doc)
        app = Application(handler)

        if not show:
            # If not showing and in notebook context return app
            return app
        elif self_or_cls.notebook_context and not new_window:
            # If in notebook, show=True and no new window requested
            # display app inline
            opts = dict(notebook_url=websocket_origin) if websocket_origin else {}
            return bkshow(app, **opts)

        # If app shown outside notebook or new_window requested
        # start server and open in new browser tab
        from tornado.ioloop import IOLoop
        loop = IOLoop.current()
        opts = dict(allow_websocket_origin=[websocket_origin]) if websocket_origin else {}
        opts['io_loop'] = loop
        server = Server({'/': app}, port=0, **opts)
        def show_callback():
            server.show('/')
        server.io_loop.add_callback(show_callback)
        server.start()
        try:
            loop.start()
        except RuntimeError:
            pass
        return server


    @bothmethod
    def server_doc(self_or_cls, obj, doc=None):
        """
        Get a bokeh Document with the plot attached. May supply
        an existing doc, otherwise bokeh.io.curdoc() is used to
        attach the plot to the global document instance.
        """
        if doc is None:
            doc = curdoc()
        if not isinstance(obj, (Plot, BokehServerWidgets)):
            renderer = self_or_cls.instance(mode='server')
            plot, _ =  renderer._validate(obj, 'auto')
        else:
            plot = obj
        root = plot.state
        if isinstance(plot, BokehServerWidgets):
            plot = plot.plot
        plot.document = doc
        plot.traverse(lambda x: attach_periodic(x), [GenericElementPlot])
        doc.add_root(root)
        return doc


    def _figure_data(self, plot, fmt='html', doc=None, **kwargs):
        model = plot.state
        doc = Document() if doc is None else doc
        for m in model.references():
            m._document = None
        doc.add_root(model)
        comm_id = plot.comm.id if plot.comm else None
        # Bokeh raises warnings about duplicate tools and empty subplots
        # but at the holoviews level these are not issues
        logger = logging.getLogger(bokeh.core.validation.check.__file__)
        logger.disabled = True
        try:
            js, div, _ = notebook_content(model, comm_id)
            html = NOTEBOOK_DIV.format(plot_script=js, plot_div=div)
            div = encode_utf8(html)
            doc.hold()
        except:
            logger.disabled = False
            raise
        logger.disabled = False
        plot.document = doc
        return div


    def diff(self, plot, binary=True):
        """
        Returns a json diff required to update an existing plot with
        the latest plot data.
        """
        events = list(plot.document._held_events)
        if not events:
            return None
        msg = Protocol("1.0").create("PATCH-DOC", events, use_buffers=binary)
        plot.document._held_events = []
        return msg


    @classmethod
    def plot_options(cls, obj, percent_size):
        """
        Given a holoviews object and a percentage size, apply heuristics
        to compute a suitable figure size. For instance, scaling layouts
        and grids linearly can result in unwieldy figure sizes when there
        are a large number of elements. As ad hoc heuristics are used,
        this functionality is kept separate from the plotting classes
        themselves.

        Used by the IPython Notebook display hooks and the save
        utility. Note that this can be overridden explicitly per object
        using the fig_size and size plot options.
        """
        obj = obj.last if isinstance(obj, HoloMap) else obj
        plot = Store.registry[cls.backend].get(type(obj), None)
        if not hasattr(plot, 'width') or not hasattr(plot, 'height'):
            from .plot import BokehPlot
            plot = BokehPlot
        options = plot.lookup_options(obj, 'plot').options
        width = options.get('width', plot.width)
        height = options.get('height', plot.height)
        return dict(options, **{'width':int(width), 'height': int(height)})


    @bothmethod
    def get_size(self_or_cls, plot):
        """
        Return the display size associated with a plot before
        rendering to any particular format. Used to generate
        appropriate HTML display.

        Returns a tuple of (width, height) in pixels.
        """
        if isinstance(plot, Plot):
            plot = plot.state
        elif not isinstance(plot, Model):
            raise ValueError('Can only compute sizes for HoloViews '
                             'and bokeh plot objects.')
        return compute_plot_size(plot)


    @classmethod
    def load_nb(cls, inline=True):
        """
        Loads the bokeh notebook resources.
        """
        from bokeh.io.notebook import curstate
        load_notebook(hide_banner=True, resources=INLINE if inline else CDN)
        curstate().output_notebook()
Exemple #23
0
class ElementPlot(GenericElementPlot, MPLPlot):

    apply_ticks = param.Boolean(default=True,
                                doc="""
        Whether to apply custom ticks.""")

    aspect = param.Parameter(default='square',
                             doc="""
        The aspect ratio mode of the plot. By default, a plot may
        select its own appropriate aspect ratio but sometimes it may
        be necessary to force a square aspect ratio (e.g. to display
        the plot as an element of a grid). The modes 'auto' and
        'equal' correspond to the axis modes of the same name in
        matplotlib, a numeric value may also be passed.""")

    bgcolor = param.ClassSelector(class_=(str, tuple),
                                  default=None,
                                  doc="""
        If set bgcolor overrides the background color of the axis.""")

    invert_axes = param.ObjectSelector(default=False,
                                       doc="""
        Inverts the axes of the plot. Note that this parameter may not
        always be respected by all plots but should be respected by
        adjoined plots when appropriate.""")

    invert_xaxis = param.Boolean(default=False,
                                 doc="""
        Whether to invert the plot x-axis.""")

    invert_yaxis = param.Boolean(default=False,
                                 doc="""
        Whether to invert the plot y-axis.""")

    invert_zaxis = param.Boolean(default=False,
                                 doc="""
        Whether to invert the plot z-axis.""")

    labelled = param.List(default=['x', 'y'],
                          doc="""
        Whether to plot the 'x' and 'y' labels.""")

    logx = param.Boolean(default=False,
                         doc="""
         Whether to apply log scaling to the x-axis of the Chart.""")

    logy = param.Boolean(default=False,
                         doc="""
         Whether to apply log scaling to the y-axis of the Chart.""")

    logz = param.Boolean(default=False,
                         doc="""
         Whether to apply log scaling to the y-axis of the Chart.""")

    show_legend = param.Boolean(default=False,
                                doc="""
        Whether to show legend for the plot.""")

    show_grid = param.Boolean(default=False,
                              doc="""
        Whether to show a Cartesian grid on the plot.""")

    xaxis = param.ObjectSelector(
        default='bottom',
        objects=['top', 'bottom', 'bare', 'top-bare', 'bottom-bare', None],
        doc="""
        Whether and where to display the xaxis, bare options allow suppressing
        all axis labels including ticks and xlabel. Valid options are 'top',
        'bottom', 'bare', 'top-bare' and 'bottom-bare'.""")

    yaxis = param.ObjectSelector(
        default='left',
        objects=['left', 'right', 'bare', 'left-bare', 'right-bare', None],
        doc="""
        Whether and where to display the yaxis, bare options allow suppressing
        all axis labels including ticks and ylabel. Valid options are 'left',
        'right', 'bare' 'left-bare' and 'right-bare'.""")

    zaxis = param.Boolean(default=True,
                          doc="""
        Whether to display the z-axis.""")

    xticks = param.Parameter(default=None,
                             doc="""
        Ticks along x-axis specified as an integer, explicit list of
        tick locations, list of tuples containing the locations and
        labels or a matplotlib tick locator object. If set to None
        default matplotlib ticking behavior is applied.""")

    xrotation = param.Integer(default=0,
                              bounds=(0, 360),
                              doc="""
        Rotation angle of the xticks.""")

    yticks = param.Parameter(default=None,
                             doc="""
        Ticks along y-axis specified as an integer, explicit list of
        tick locations, list of tuples containing the locations and
        labels or a matplotlib tick locator object. If set to None
        default matplotlib ticking behavior is applied.""")

    yrotation = param.Integer(default=0,
                              bounds=(0, 360),
                              doc="""
        Rotation angle of the yticks.""")

    zrotation = param.Integer(default=0,
                              bounds=(0, 360),
                              doc="""
        Rotation angle of the zticks.""")

    zticks = param.Parameter(default=None,
                             doc="""
        Ticks along z-axis specified as an integer, explicit list of
        tick locations, list of tuples containing the locations and
        labels or a matplotlib tick locator object. If set to None
        default matplotlib ticking behavior is applied.""")

    # Element Plots should declare the valid style options for matplotlib call
    style_opts = []

    # Whether plot has axes, disables setting axis limits, labels and ticks
    _has_axes = True

    def __init__(self, element, **params):
        super(ElementPlot, self).__init__(element, **params)
        check = self.hmap.last
        if isinstance(check, CompositeOverlay):
            check = check.values()[0]  # Should check if any are 3D plots
        if isinstance(check, Element3D):
            self.projection = '3d'

        for hook in self.initial_hooks:
            try:
                hook(self, element)
            except Exception as e:
                self.warning("Plotting hook %r could not be applied:\n\n %s" %
                             (hook, e))

    def _finalize_axis(self,
                       key,
                       element=None,
                       title=None,
                       dimensions=None,
                       ranges=None,
                       xticks=None,
                       yticks=None,
                       zticks=None,
                       xlabel=None,
                       ylabel=None,
                       zlabel=None):
        """
        Applies all the axis settings before the axis or figure is returned.
        Only plots with zorder 0 get to apply their settings.

        When the number of the frame is supplied as n, this method looks
        up and computes the appropriate title, axis labels and axis bounds.
        """
        if element is None:
            element = self._get_frame(key)
        self.current_frame = element
        if not dimensions and element and not self.subplots:
            el = element.traverse(lambda x: x, [Element])
            if el: dimensions = el[0].dimensions()
        axis = self.handles['axis']

        subplots = list(self.subplots.values()) if self.subplots else []
        if self.zorder == 0 and key is not None:
            if self.bgcolor:
                if LooseVersion(mpl.__version__) <= '1.5.9':
                    axis.set_axis_bgcolor(self.bgcolor)
                else:
                    axis.set_facecolor(self.bgcolor)

            # Apply title
            title = self._format_title(key)
            if self.show_title and title is not None:
                fontsize = self._fontsize('title')
                if 'title' in self.handles:
                    self.handles['title'].set_text(title)
                else:
                    self.handles['title'] = axis.set_title(title, **fontsize)

            # Apply subplot label
            self._subplot_label(axis)

            # Apply axis options if axes are enabled
            if element and not any(not sp._has_axes
                                   for sp in [self] + subplots):
                # Set axis labels
                if dimensions:
                    self._set_labels(axis, dimensions, xlabel, ylabel, zlabel)

                if not subplots:
                    legend = axis.get_legend()
                    if legend:
                        legend.set_visible(self.show_legend)
                        self.handles["bbox_extra_artists"] += [legend]
                    axis.xaxis.grid(self.show_grid)
                    axis.yaxis.grid(self.show_grid)

                # Apply log axes
                if self.logx:
                    axis.set_xscale('log')
                if self.logy:
                    axis.set_yscale('log')

                if not self.projection == '3d':
                    self._set_axis_position(axis, 'x', self.xaxis)
                    self._set_axis_position(axis, 'y', self.yaxis)

                # Apply ticks
                if self.apply_ticks:
                    self._finalize_ticks(axis, dimensions, xticks, yticks,
                                         zticks)

                # Set axes limits
                self._set_axis_limits(axis, element, subplots, ranges)

            # Apply aspects
            if self.aspect is not None and self.projection != 'polar' and not self.adjoined:
                self._set_aspect(axis, self.aspect)

        if not subplots and not self.drawn:
            self._finalize_artist(key)

        for hook in self.finalize_hooks:
            try:
                hook(self, element)
            except Exception as e:
                self.warning("Plotting hook %r could not be applied:\n\n %s" %
                             (hook, e))

        return super(ElementPlot, self)._finalize_axis(key)

    def _finalize_ticks(self, axis, dimensions, xticks, yticks, zticks):
        """
        Finalizes the ticks on the axes based on the supplied ticks
        and Elements. Sets the axes position as well as tick positions,
        labels and fontsize.
        """
        ndims = len(dimensions) if dimensions else 0
        xdim = dimensions[0] if ndims else None
        ydim = dimensions[1] if ndims > 1 else None

        # Tick formatting
        if xdim:
            self._set_axis_formatter(axis.xaxis, xdim)
        if ydim:
            self._set_axis_formatter(axis.yaxis, ydim)
        if self.projection == '3d':
            zdim = dimensions[2] if ndims > 2 else None
            if zdim:
                self._set_axis_formatter(axis.zaxis, zdim)

        xticks = xticks if xticks else self.xticks
        self._set_axis_ticks(axis.xaxis,
                             xticks,
                             log=self.logx,
                             rotation=self.xrotation)

        yticks = yticks if yticks else self.yticks
        self._set_axis_ticks(axis.yaxis,
                             yticks,
                             log=self.logy,
                             rotation=self.yrotation)

        if self.projection == '3d':
            zticks = zticks if zticks else self.zticks
            self._set_axis_ticks(axis.zaxis,
                                 zticks,
                                 log=self.logz,
                                 rotation=self.zrotation)

        for ax, ax_obj in zip('xy', [axis.xaxis, axis.yaxis]):
            tick_fontsize = self._fontsize('%sticks' % ax,
                                           'labelsize',
                                           common=False)
            if tick_fontsize: ax_obj.set_tick_params(**tick_fontsize)

    def _finalize_artist(self, element):
        """
        Allows extending the _finalize_axis method with Element
        specific options.
        """
        pass

    def _set_labels(self,
                    axes,
                    dimensions,
                    xlabel=None,
                    ylabel=None,
                    zlabel=None):
        """
        Sets the labels of the axes using the supplied list of dimensions.
        Optionally explicit labels may be supplied to override the dimension
        label.
        """
        xlabel, ylabel, zlabel = self._get_axis_labels(dimensions, xlabel,
                                                       ylabel, zlabel)
        if self.invert_axes:
            xlabel, ylabel = ylabel, xlabel
        if xlabel and self.xaxis and 'x' in self.labelled:
            axes.set_xlabel(xlabel, **self._fontsize('xlabel'))
        if ylabel and self.yaxis and 'y' in self.labelled:
            axes.set_ylabel(ylabel, **self._fontsize('ylabel'))
        if zlabel and self.zaxis and 'z' in self.labelled:
            axes.set_zlabel(zlabel, **self._fontsize('zlabel'))

    def _set_axis_formatter(self, axis, dim):
        """
        Set axis formatter based on dimension formatter.
        """
        if isinstance(dim, list): dim = dim[0]
        formatter = None
        if dim.value_format:
            formatter = dim.value_format
        elif dim.type in dim.type_formatters:
            formatter = dim.type_formatters[dim.type]
        if formatter:
            axis.set_major_formatter(wrap_formatter(formatter))

    def _set_aspect(self, axes, aspect):
        """
        Set the aspect on the axes based on the aspect setting.
        """
        if isinstance(aspect, util.basestring) and aspect != 'square':
            axes.set_aspect(aspect)
            return

        (x0, x1), (y0, y1) = axes.get_xlim(), axes.get_ylim()
        xsize = np.log(x1) - np.log(x0) if self.logx else x1 - x0
        ysize = np.log(y1) - np.log(y0) if self.logy else y1 - y0
        xsize = max(abs(xsize), 1e-30)
        ysize = max(abs(ysize), 1e-30)
        data_ratio = 1. / (ysize / xsize)
        if aspect != 'square':
            data_ratio = data_ratio / aspect
        axes.set_aspect(float(data_ratio))

    def _set_axis_limits(self, axis, view, subplots, ranges):
        """
        Compute extents for current view and apply as axis limits
        """
        # Extents
        scalex, scaley = True, True
        extents = self.get_extents(view, ranges)
        if extents and not self.overlaid:
            coords = [
                coord if np.isreal(coord) or isinstance(coord, np.datetime64)
                else np.NaN for coord in extents
            ]
            coords = [
                date2num(util.dt64_to_dt(c))
                if isinstance(c, np.datetime64) else c for c in coords
            ]
            valid_lim = lambda c: util.isnumeric(c) and not np.isnan(c)
            if self.projection == '3d' or len(extents) == 6:
                l, b, zmin, r, t, zmax = coords
                if self.invert_zaxis or any(p.invert_zaxis for p in subplots):
                    zmin, zmax = zmax, zmin
                if zmin != zmax:
                    if valid_lim(zmin):
                        axis.set_zlim(bottom=zmin)
                    if valid_lim(zmax):
                        axis.set_zlim(top=zmax)
            else:
                l, b, r, t = coords

            if self.invert_axes:
                l, b, r, t = b, l, t, r

            if self.invert_xaxis or any(p.invert_xaxis for p in subplots):
                r, l = l, r
            if l != r:
                lims = {}
                if valid_lim(l):
                    lims['left'] = l
                    scalex = False
                if valid_lim(r):
                    lims['right'] = r
                    scalex = False
                if lims:
                    axis.set_xlim(**lims)
            if self.invert_yaxis or any(p.invert_yaxis for p in subplots):
                t, b = b, t
            if b != t:
                lims = {}
                if valid_lim(b):
                    lims['bottom'] = b
                    scaley = False
                if valid_lim(t):
                    lims['top'] = t
                    scaley = False
                if lims:
                    axis.set_ylim(**lims)
        axis.autoscale_view(scalex=scalex, scaley=scaley)

    def _set_axis_position(self, axes, axis, option):
        """
        Set the position and visibility of the xaxis or yaxis by
        supplying the axes object, the axis to set, i.e. 'x' or 'y'
        and an option to specify the position and visibility of the axis.
        The option may be None, 'bare' or positional, i.e. 'left' and
        'right' for the yaxis and 'top' and 'bottom' for the xaxis.
        May also combine positional and 'bare' into for example 'left-bare'.
        """
        positions = {'x': ['bottom', 'top'], 'y': ['left', 'right']}[axis]
        axis = axes.xaxis if axis == 'x' else axes.yaxis
        if option is None:
            axis.set_visible(False)
            for pos in positions:
                axes.spines[pos].set_visible(False)
        else:
            if 'bare' in option:
                axis.set_ticklabels([])
                axis.set_label_text('')
            if option != 'bare':
                option = option.split('-')[0]
                axis.set_ticks_position(option)
                axis.set_label_position(option)
        if not self.overlaid and not self.show_frame and self.projection != 'polar':
            pos = (positions[1] if
                   (option and (option == 'bare' or positions[0] in option))
                   else positions[0])
            axes.spines[pos].set_visible(False)

    def _set_axis_ticks(self, axis, ticks, log=False, rotation=0):
        """
        Allows setting the ticks for a particular axis either with
        a tuple of ticks, a tick locator object, an integer number
        of ticks, a list of tuples containing positions and labels
        or a list of positions. Also supports enabling log ticking
        if an integer number of ticks is supplied and setting a
        rotation for the ticks.
        """
        if isinstance(ticks, (list, tuple)) and all(
                isinstance(l, list) for l in ticks):
            axis.set_ticks(ticks[0])
            axis.set_ticklabels(ticks[1])
        elif isinstance(ticks, ticker.Locator):
            axis.set_major_locator(ticks)
        elif not ticks and ticks is not None:
            axis.set_ticks([])
        elif isinstance(ticks, int):
            if log:
                locator = ticker.LogLocator(numticks=ticks, subs=range(1, 10))
            else:
                locator = ticker.MaxNLocator(ticks)
            axis.set_major_locator(locator)
        elif isinstance(ticks, (list, tuple)):
            labels = None
            if all(isinstance(t, tuple) for t in ticks):
                ticks, labels = zip(*ticks)
            axis.set_ticks(ticks)
            if labels:
                axis.set_ticklabels(labels)
        for tick in axis.get_ticklabels():
            tick.set_rotation(rotation)

    @mpl_rc_context
    def update_frame(self, key, ranges=None, element=None):
        """
        Set the plot(s) to the given frame number.  Operates by
        manipulating the matplotlib objects held in the self._handles
        dictionary.

        If n is greater than the number of available frames, update
        using the last available frame.
        """
        reused = isinstance(self.hmap, DynamicMap) and self.overlaid
        if not reused and element is None:
            element = self._get_frame(key)
        elif element is not None:
            self.current_key = key
            self.current_frame = element

        if element is not None:
            self.set_param(**self.lookup_options(element, 'plot').options)
        axis = self.handles['axis']

        axes_visible = element is not None or self.overlaid
        axis.xaxis.set_visible(axes_visible and self.xaxis)
        axis.yaxis.set_visible(axes_visible and self.yaxis)
        axis.patch.set_alpha(np.min([int(axes_visible), 1]))

        for hname, handle in self.handles.items():
            hideable = hasattr(handle, 'set_visible')
            if hname not in ['axis', 'fig'] and hideable:
                handle.set_visible(element is not None)
        if element is None:
            return

        ranges = self.compute_ranges(self.hmap, key, ranges)
        ranges = util.match_spec(element, ranges)

        label = element.label if self.show_legend else ''
        style = dict(label=label,
                     zorder=self.zorder,
                     **self.style[self.cyclic_index])
        axis_kwargs = self.update_handles(key, axis, element, ranges, style)
        self._finalize_axis(key,
                            element=element,
                            ranges=ranges,
                            **(axis_kwargs if axis_kwargs else {}))

    @mpl_rc_context
    def initialize_plot(self, ranges=None):
        element = self.hmap.last
        ax = self.handles['axis']
        key = list(self.hmap.data.keys())[-1]
        dim_map = dict(zip((d.name for d in self.hmap.kdims), key))
        key = tuple(dim_map.get(d.name, None) for d in self.dimensions)

        ranges = self.compute_ranges(self.hmap, key, ranges)
        ranges = util.match_spec(element, ranges)

        style = dict(zorder=self.zorder, **self.style[self.cyclic_index])
        if self.show_legend:
            style['label'] = element.label

        plot_data, plot_kwargs, axis_kwargs = self.get_data(
            element, ranges, style)

        with abbreviated_exception():
            handles = self.init_artists(ax, plot_data, plot_kwargs)
        self.handles.update(handles)

        return self._finalize_axis(self.keys[-1],
                                   element=element,
                                   ranges=ranges,
                                   **axis_kwargs)

    def init_artists(self, ax, plot_args, plot_kwargs):
        """
        Initializes the artist based on the plot method declared on
        the plot.
        """
        plot_method = self._plot_methods.get(
            'batched' if self.batched else 'single')
        plot_fn = getattr(ax, plot_method)
        artist = plot_fn(*plot_args, **plot_kwargs)
        return {
            'artist':
            artist[0]
            if isinstance(artist, list) and len(artist) == 1 else artist
        }

    def update_handles(self, key, axis, element, ranges, style):
        """
        Update the elements of the plot.
        """
        self.teardown_handles()
        plot_data, plot_kwargs, axis_kwargs = self.get_data(
            element, ranges, style)

        with abbreviated_exception():
            handles = self.init_artists(axis, plot_data, plot_kwargs)
        self.handles.update(handles)
        return axis_kwargs

    def teardown_handles(self):
        """
        If no custom update_handles method is supplied this method
        is called to tear down any previous handles before replacing
        them.
        """
        if 'artist' in self.handles:
            self.handles['artist'].remove()
Exemple #24
0
class BokehPlot(DimensionedPlot):
    """
    Plotting baseclass for the Bokeh backends, implementing the basic
    plotting interface for Bokeh based plots.
    """

    width = param.Integer(default=300,
                          doc="""
        Width of the plot in pixels""")

    height = param.Integer(default=300,
                           doc="""
        Height of the plot in pixels""")

    sizing_mode = param.ObjectSelector(default='fixed',
                                       objects=[
                                           'fixed', 'stretch_both',
                                           'scale_width', 'scale_height',
                                           'scale_both'
                                       ],
                                       doc="""
        How the item being displayed should size itself.

        "stretch_both" plots will resize to occupy all available
        space, even if this changes the aspect ratio of the element.

        "fixed" plots are not responsive and will retain their
        original width and height regardless of any subsequent browser
        window resize events.

        "scale_width" elements will responsively resize to fit to the
        width available, while maintaining the original aspect ratio.

        "scale_height" elements will responsively resize to fit to the
        height available, while maintaining the original aspect ratio.

        "scale_both" elements will responsively resize to for both the
        width and height available, while maintaining the original
        aspect ratio.""")

    shared_datasource = param.Boolean(default=True,
                                      doc="""
        Whether Elements drawing the data from the same object should
        share their Bokeh data source allowing for linked brushing
        and other linked behaviors.""")

    title_format = param.String(default="{label} {group} {dimensions}",
                                doc="""
        The formatting string for the title of this plot, allows defining
        a label group separator and dimension labels.""")

    backend = 'bokeh'

    @property
    def document(self):
        return self._document

    @property
    def id(self):
        return self.state.ref['id']

    @document.setter
    def document(self, doc):
        self._document = doc
        if self.subplots:
            for plot in self.subplots.values():
                if plot is not None:
                    plot.document = doc

    def __init__(self, *args, **params):
        super(BokehPlot, self).__init__(*args, **params)
        self._document = None
        self.root = None

    def get_data(self, element, ranges, style):
        """
        Returns the data from an element in the appropriate format for
        initializing or updating a ColumnDataSource and a dictionary
        which maps the expected keywords arguments of a glyph to
        the column in the datasource.
        """
        raise NotImplementedError

    def _construct_callbacks(self):
        """
        Initializes any callbacks for streams which have defined
        the plotted object as a source.
        """
        if isinstance(self, GenericOverlayPlot):
            zorders = []
        elif self.batched:
            zorders = list(
                range(self.zorder, self.zorder + len(self.hmap.last)))
        else:
            zorders = [self.zorder]

        if isinstance(self, GenericOverlayPlot) and not self.batched:
            sources = []
        elif not self.static or isinstance(self.hmap, DynamicMap):
            sources = [(i, o) for i, inputs in self.stream_sources.items()
                       for o in inputs if i in zorders]
        else:
            sources = [(self.zorder, self.hmap.last)]

        cb_classes = set()
        for _, source in sources:
            streams = Stream.registry.get(id(source), [])
            registry = Stream._callbacks['bokeh']
            cb_classes |= {(registry[type(stream)], stream)
                           for stream in streams
                           if type(stream) in registry and stream.linked}
        cbs = []
        sorted_cbs = sorted(cb_classes, key=lambda x: id(x[0]))
        for cb, group in groupby(sorted_cbs, lambda x: x[0]):
            cb_streams = [s for _, s in group]
            cbs.append(cb(self, cb_streams, source))
        return cbs

    def push(self):
        """
        Pushes updated plot data via the Comm.
        """
        if self.renderer.mode == 'server':
            return
        if self.comm is None:
            raise Exception('Renderer does not have a comm.')

        msg = self.renderer.diff(self, binary=True)
        if msg is None:
            return
        self.comm.send(msg.header_json)
        self.comm.send(msg.metadata_json)
        self.comm.send(msg.content_json)
        for header, payload in msg.buffers:
            self.comm.send(json.dumps(header))
            self.comm.send(buffers=[payload])

    def set_root(self, root):
        """
        Sets the current document on all subplots.
        """
        for plot in self.traverse(lambda x: x):
            plot.root = root

    def _init_datasource(self, data):
        """
        Initializes a data source to be passed into the bokeh glyph.
        """
        data = {k: decode_bytes(vs) for k, vs in data.items()}
        return ColumnDataSource(data=data)

    def _update_datasource(self, source, data):
        """
        Update datasource with data for a new frame.
        """
        data = {k: decode_bytes(vs) for k, vs in data.items()}
        if (self.streaming
                and self.streaming[0].data is self.current_frame.data
                and self._stream_data):
            stream = self.streaming[0]
            if stream._triggering:
                data = {k: v[-stream._chunk_length:] for k, v in data.items()}
                source.stream(data, stream.length)
        else:
            source.data.update(data)

    @property
    def state(self):
        """
        The plotting state that gets updated via the update method and
        used by the renderer to generate output.
        """
        return self.handles['plot']

    @property
    def current_handles(self):
        """
        Should return a list of plot objects that have changed and
        should be updated.
        """
        return []

    def _fontsize(self, key, label='fontsize', common=True):
        """
        Converts integer fontsizes to a string specifying
        fontsize in pt.
        """
        size = super(BokehPlot, self)._fontsize(key, label, common)
        return {
            k: v if isinstance(v, basestring) else '%spt' % v
            for k, v in size.items()
        }

    def sync_sources(self):
        """
        Syncs data sources between Elements, which draw data
        from the same object.
        """
        get_sources = lambda x: (id(x.current_frame.data), x)
        filter_fn = lambda x: (x.shared_datasource and x.current_frame is
                               not None and not isinstance(
                                   x.current_frame.data, np.ndarray
                               ) and 'source' in x.handles)
        data_sources = self.traverse(get_sources, [filter_fn])
        grouped_sources = groupby(sorted(data_sources, key=lambda x: x[0]),
                                  lambda x: x[0])
        shared_sources = []
        source_cols = {}
        plots = []
        for _, group in grouped_sources:
            group = list(group)
            if len(group) > 1:
                source_data = {}
                for _, plot in group:
                    source_data.update(plot.handles['source'].data)
                new_source = ColumnDataSource(source_data)
                for _, plot in group:
                    renderer = plot.handles.get('glyph_renderer')
                    for callback in plot.callbacks:
                        callback.reset()
                    if renderer is None:
                        continue
                    elif 'data_source' in renderer.properties():
                        renderer.update(data_source=new_source)
                    else:
                        renderer.update(source=new_source)
                    if hasattr(renderer, 'view'):
                        renderer.view.update(source=new_source)
                    plot.handles['source'] = new_source
                    plots.append(plot)
                shared_sources.append(new_source)
                source_cols[id(new_source)] = [c for c in new_source.data]
        for plot in plots:
            for callback in plot.callbacks:
                callback.initialize()
        self.handles['shared_sources'] = shared_sources
        self.handles['source_cols'] = source_cols
Exemple #25
0
class HoloViews(PaneBase):
    """
    HoloViews panes render any HoloViews object to a corresponding
    Bokeh model while respecting the currently selected backend.
    """

    backend = param.String(default=None,
                           doc="""
        The HoloViews backend used to render the plot (if None defaults
        to the currently selected renderer).""")

    widget_type = param.ObjectSelector(default='individual',
                                       objects=['individual', 'scrubber'],
                                       doc=""")
        Whether to generate individual widgets for each dimension or
        on global scrubber.""")

    widgets = param.Dict(default={},
                         doc="""
        A mapping from dimension name to a widget instance which will
        be used to override the default widgets.""")

    precedence = 0.8

    def __init__(self, object, **params):
        super(HoloViews, self).__init__(object, **params)
        self.widget_box = WidgetBox()
        self._update_widgets()
        self._plots = {}

    @param.depends('object', 'widgets', watch=True)
    def _update_widgets(self):
        if self.object is None:
            widgets, values = []
        else:
            widgets, values = self.widgets_from_dimensions(
                self.object, self.widgets, self.widget_type)
        self._values = values

        # Clean up anything models listening to the previous widgets
        for _, cbs in self._callbacks.items():
            for cb in list(cbs):
                if cb.inst in self.widget_box.objects:
                    print(cb)
                    cb.inst.param.unwatch(cb)
                    cbs.remove(cb)

        self.widget_box.objects = widgets
        if widgets and not self.widget_box in self.layout.objects:
            self.layout.append(self.widget_box)
        elif not widgets and self.widget_box in self.layout.objects:
            self.layout.pop(self.widget_box)

    @classmethod
    def applies(cls, obj):
        if 'holoviews' not in sys.modules:
            return False
        from holoviews.core.dimension import Dimensioned
        return isinstance(obj, Dimensioned)

    def _cleanup(self, root=None, final=False):
        """
        Traverses HoloViews object to find and clean up any streams
        connected to existing plots.
        """
        if root is not None:
            old_plot = self._plots.pop(root.ref['id'], None)
            if old_plot:
                old_plot.cleanup()
        super(HoloViews, self)._cleanup(root, final)

    def _render(self, doc, comm, root):
        from holoviews import Store, renderer
        if not Store.renderers:
            loaded_backend = (self.backend or 'bokeh')
            renderer(loaded_backend)
            Store.current_backend = loaded_backend
        backend = self.backend or Store.current_backend
        renderer = Store.renderers[backend]
        if backend == 'bokeh':
            renderer = renderer.instance(
                mode='server' if comm is None else 'default')
        kwargs = {'doc': doc, 'root': root} if backend == 'bokeh' else {}
        if comm:
            kwargs['comm'] = comm
        plot = renderer.get_plot(self.object, **kwargs)
        ref = root.ref['id']
        if ref in self._plots:
            old_plot = self._plots[ref]
            old_plot.comm = None
            old_plot.cleanup()
        self._plots[root.ref['id']] = plot
        return plot

    def _get_model(self, doc, root=None, parent=None, comm=None):
        """
        Should return the Bokeh model to be rendered.
        """
        ref = root.ref['id']
        plot = self._render(doc, comm, root)
        child_pane = Pane(plot.state, _temporary=True)
        model = child_pane._get_model(doc, root, parent, comm)
        self._models[ref] = model
        self._link_object(doc, root, parent, comm)
        if self.widget_box.objects:
            self._link_widgets(child_pane, root, comm)
        return model

    def _link_widgets(self, pane, root, comm):
        def update_plot(change):
            from holoviews.core.util import cross_index
            from holoviews.plotting.bokeh.plot import BokehPlot

            widgets = self.widget_box.objects
            if self.widget_type == 'scrubber':
                key = cross_index([v for v in self._values.values()],
                                  widgets[0].value)
            else:
                key = tuple(w.value for w in widgets)

            plot = self._plots[root.ref['id']]
            if isinstance(plot, BokehPlot):
                if comm:
                    plot.update(key)
                    plot.push()
                else:

                    def update_plot():
                        plot.update(key)

                    plot.document.add_next_tick_callback(update_plot)
            else:
                plot.update(key)
                pane.object = plot.state

        ref = root.ref['id']
        for w in self.widget_box.objects:
            watcher = w.param.watch(update_plot, 'value')
            self._callbacks[ref].append(watcher)

    @classmethod
    def widgets_from_dimensions(cls,
                                object,
                                widget_types={},
                                widgets_type='individual'):
        from holoviews.core import Dimension
        from holoviews.core.util import isnumeric, unicode
        from holoviews.core.traversal import unique_dimkeys
        from .widgets import Widget, DiscreteSlider, Select, FloatSlider

        dims, keys = unique_dimkeys(object)
        if dims == [Dimension('Frame')] and keys == [(0, )]:
            return [], {}

        nframes = 1
        values = dict(zip(dims, zip(*keys)))
        dim_values = OrderedDict()
        widgets = []
        for dim in dims:
            widget_type, widget = None, None
            vals = dim.values or values.get(dim, None)
            dim_values[dim.name] = vals
            if widgets_type == 'scrubber':
                if not vals:
                    raise ValueError(
                        'Scrubber widget may only be used if all dimensions define values.'
                    )
                nframes *= len(vals)
            elif dim.name in widget_types:
                widget = widget_types[dim.name]
                if isinstance(widget, Widget):
                    widgets.append(widget)
                    continue
                elif isinstance(widget, type) and issubclass(widget, Widget):
                    widget_type = widget
                else:
                    raise ValueError('Explicit widget definitions expected '
                                     'to be a widget instance or type, %s '
                                     'dimension widget declared as %s.' %
                                     (dim, widget))
            if vals:
                if all(isnumeric(v) for v in vals):
                    vals = sorted(vals)
                    labels = [unicode(dim.pprint_value(v)) for v in vals]
                    options = OrderedDict(zip(labels, vals))
                    widget_type = widget_type or DiscreteSlider
                else:
                    options = list(vals)
                    widget_type = widget_type or Select
                default = vals[0] if dim.default is None else dim.default
                widget = widget_type(name=dim.label,
                                     options=options,
                                     value=default)
            elif dim.range != (None, None):
                default = dim.range[0] if dim.default is None else dim.default
                step = 0.1 if dim.step is None else dim.step
                widget_type = widget_type or FloatSlider
                widget = widget_type(step=step,
                                     name=dim.label,
                                     start=dim.range[0],
                                     end=dim.range[1],
                                     value=default)
            if widget is not None:
                widgets.append(widget)
        if widgets_type == 'scrubber':
            widgets = [Player(length=nframes)]
        return widgets, dim_values
Exemple #26
0
class GridPlot(CompositePlot, GenericCompositePlot):
    """
    Plot a group of elements in a grid layout based on a GridSpace element
    object.
    """

    axis_offset = param.Integer(default=50,
                                doc="""
        Number of pixels to adjust row and column widths and height by
        to compensate for shared axes.""")

    fontsize = param.Parameter(default={'title': '16pt'},
                               allow_None=True,
                               doc="""
       Specifies various fontsizes of the displayed text.

       Finer control is available by supplying a dictionary where any
       unmentioned keys reverts to the default sizes, e.g:

          {'title': '15pt'}""")

    merge_tools = param.Boolean(default=True,
                                doc="""
        Whether to merge all the tools into a single toolbar""")

    shared_xaxis = param.Boolean(default=False,
                                 doc="""
        If enabled the x-axes of the GridSpace will be drawn from the
        objects inside the Grid rather than the GridSpace dimensions.""")

    shared_yaxis = param.Boolean(default=False,
                                 doc="""
        If enabled the x-axes of the GridSpace will be drawn from the
        objects inside the Grid rather than the GridSpace dimensions.""")

    toolbar = param.ObjectSelector(
        default='above',
        objects=["above", "below", "left", "right", None],
        doc="""
        The toolbar location, must be one of 'above', 'below',
        'left', 'right', None.""")

    xaxis = param.ObjectSelector(default=True,
                                 objects=['bottom', 'top', None, True, False],
                                 doc="""
        Whether and where to display the xaxis, supported options are
        'bottom', 'top' and None.""")

    yaxis = param.ObjectSelector(default=True,
                                 objects=['left', 'right', None, True, False],
                                 doc="""
        Whether and where to display the yaxis, supported options are
        'left', 'right' and None.""")

    xrotation = param.Integer(default=0,
                              bounds=(0, 360),
                              doc="""
        Rotation angle of the xticks.""")

    yrotation = param.Integer(default=0,
                              bounds=(0, 360),
                              doc="""
        Rotation angle of the yticks.""")

    plot_size = param.Integer(default=120,
                              doc="""
        Defines the width and height of each plot in the grid""")

    def __init__(self, layout, ranges=None, layout_num=1, keys=None, **params):
        if not isinstance(layout, GridSpace):
            raise Exception("GridPlot only accepts GridSpace.")
        super(GridPlot, self).__init__(layout=layout,
                                       layout_num=layout_num,
                                       ranges=ranges,
                                       keys=keys,
                                       **params)
        self.cols, self.rows = layout.shape
        self.subplots, self.layout = self._create_subplots(layout, ranges)
        if self.top_level:
            self.comm = self.init_comm()
            self.traverse(lambda x: setattr(x, 'comm', self.comm))
            self.traverse(lambda x: attach_streams(self, x.hmap, 2),
                          [GenericElementPlot])

    def _create_subplots(self, layout, ranges):
        subplots = OrderedDict()
        frame_ranges = self.compute_ranges(layout, None, ranges)
        frame_ranges = OrderedDict([
            (key, self.compute_ranges(layout, key, frame_ranges))
            for key in self.keys
        ])
        collapsed_layout = layout.clone(shared_data=False, id=layout.id)
        for i, coord in enumerate(layout.keys(full_grid=True)):
            r = i % self.rows
            c = i // self.rows

            if not isinstance(coord, tuple): coord = (coord, )
            view = layout.data.get(coord, None)
            # Create subplot
            if view is not None:
                vtype = view.type if isinstance(view,
                                                HoloMap) else view.__class__
                opts = self.lookup_options(view, 'plot').options
            else:
                vtype = None

            # Create axes
            offset = self.axis_offset
            kwargs = {}
            if c == 0 and r != 0:
                kwargs['xaxis'] = None
                kwargs['width'] = self.plot_size + offset
            if c != 0 and r == 0:
                kwargs['yaxis'] = None
                kwargs['height'] = self.plot_size + offset
            if c == 0 and r == 0:
                kwargs['width'] = self.plot_size + offset
                kwargs['height'] = self.plot_size + offset
            if r != 0 and c != 0:
                kwargs['xaxis'] = None
                kwargs['yaxis'] = None

            if 'width' not in kwargs or not self.shared_yaxis:
                kwargs['width'] = self.plot_size
            if 'height' not in kwargs or not self.shared_xaxis:
                kwargs['height'] = self.plot_size
            if 'border' not in kwargs:
                kwargs['border'] = 3

            kwargs['show_legend'] = False

            if not self.shared_xaxis:
                kwargs['xaxis'] = None

            if not self.shared_yaxis:
                kwargs['yaxis'] = None

            # Create subplot
            plotting_class = Store.registry[self.renderer.backend].get(
                vtype, None)
            if plotting_class is None:
                if view is not None:
                    self.warning("Bokeh plotting class for %s type not found, "
                                 "object will not be rendered." %
                                 vtype.__name__)
            else:
                subplot = plotting_class(view,
                                         dimensions=self.dimensions,
                                         show_title=False,
                                         subplot=True,
                                         renderer=self.renderer,
                                         ranges=frame_ranges,
                                         uniform=self.uniform,
                                         keys=self.keys,
                                         **dict(opts, **kwargs))
                collapsed_layout[coord] = (subplot.layout if isinstance(
                    subplot, GenericCompositePlot) else subplot.hmap)
                subplots[coord] = subplot
        return subplots, collapsed_layout

    def initialize_plot(self, ranges=None, plots=[]):
        ranges = self.compute_ranges(self.layout, self.keys[-1], None)
        passed_plots = list(plots)
        plots = [[None for c in range(self.cols)] for r in range(self.rows)]
        for i, coord in enumerate(self.layout.keys(full_grid=True)):
            r = i % self.rows
            c = i // self.rows
            subplot = self.subplots.get(wrap_tuple(coord), None)
            if subplot is not None:
                plot = subplot.initialize_plot(ranges=ranges,
                                               plots=passed_plots)
                plots[r][c] = plot
                passed_plots.append(plot)
            else:
                passed_plots.append(None)

        plot = gridplot(plots[::-1],
                        toolbar_position=self.toolbar,
                        merge_tools=self.merge_tools)
        plot = self._make_axes(plot)

        title = self._get_title(self.keys[-1])
        if title:
            plot = Column(title, plot)
            self.handles['title'] = title

        self._update_callbacks(plot)
        self.handles['plot'] = plot
        self.handles['plots'] = plots
        if self.shared_datasource:
            self.sync_sources()
        self.drawn = True

        return self.handles['plot']

    def _make_axes(self, plot):
        width, height = self.renderer.get_size(plot)
        x_axis, y_axis = None, None
        kwargs = dict(sizing_mode=self.sizing_mode)
        if self.xaxis:
            flip = self.shared_xaxis
            rotation = self.xrotation
            lsize = self._fontsize('xlabel').get('fontsize')
            tsize = self._fontsize('xticks', common=False).get('fontsize')
            xfactors = list(unique_iterator(self.layout.dimension_values(0)))
            x_axis = make_axis('x',
                               width,
                               xfactors,
                               self.layout.kdims[0],
                               flip=flip,
                               rotation=rotation,
                               label_size=lsize,
                               tick_size=tsize)
        if self.yaxis and self.layout.ndims > 1:
            flip = self.shared_yaxis
            rotation = self.yrotation
            lsize = self._fontsize('ylabel').get('fontsize')
            tsize = self._fontsize('yticks', common=False).get('fontsize')
            yfactors = list(unique_iterator(self.layout.dimension_values(1)))
            y_axis = make_axis('y',
                               height,
                               yfactors,
                               self.layout.kdims[1],
                               flip=flip,
                               rotation=rotation,
                               label_size=lsize,
                               tick_size=tsize)
        if x_axis and y_axis:
            plot = filter_toolboxes(plot)
            r1, r2 = ([y_axis, plot], [None, x_axis])
            if self.shared_xaxis:
                r1, r2 = r2, r1
            if self.shared_yaxis:
                r1, r2 = r1[::-1], r2[::-1]
            models = layout_padding([r1, r2], self.renderer)
            plot = gridplot(models, **kwargs)
        elif y_axis:
            models = [y_axis, plot]
            if self.shared_yaxis: models = models[::-1]
            plot = Row(*models, **kwargs)
        elif x_axis:
            models = [plot, x_axis]
            if self.shared_xaxis: models = models[::-1]
            plot = Column(*models, **kwargs)
        return plot

    @update_shared_sources
    def update_frame(self, key, ranges=None):
        """
        Update the internal state of the Plot to represent the given
        key tuple (where integers represent frames). Returns this
        state.
        """
        ranges = self.compute_ranges(self.layout, key, ranges)
        for coord in self.layout.keys(full_grid=True):
            subplot = self.subplots.get(wrap_tuple(coord), None)
            if subplot is not None:
                subplot.update_frame(key, ranges)
        title = self._get_title(key)
        if title:
            self.handles['title']
Exemple #27
0
class SankeyPlot(GraphPlot):

    labels = param.ClassSelector(class_=(str, dim),
                                 doc="""
        The dimension or dimension value transform used to draw labels from."""
                                 )

    show_values = param.Boolean(default=True,
                                doc="""
        Whether to show the values.""")

    label_position = param.ObjectSelector(default='right',
                                          objects=['left', 'right'],
                                          doc="""
        Whether node labels should be placed to the left or right.""")

    node_width = param.Number(default=15,
                              doc="""
        Width of the nodes.""")

    node_padding = param.Integer(default=None,
                                 doc="""
        Number of pixels of padding relative to the bounds.""")

    iterations = param.Integer(default=32,
                               doc="""
        Number of iterations to run the layout algorithm.""")

    node_sort = param.Boolean(default=True,
                              doc="""
        Sort nodes in ascending breadth.""")

    # Deprecated options

    color_index = param.ClassSelector(default=2,
                                      class_=(str, int),
                                      allow_None=True,
                                      doc="""
        Index of the dimension from which the node labels will be drawn""")

    label_index = param.ClassSelector(default=2,
                                      class_=(str, int),
                                      allow_None=True,
                                      doc="""
        Index of the dimension from which the node labels will be drawn""")

    filled = True

    style_opts = GraphPlot.style_opts + ['label_text_font_size']

    def get_extents(self, element, ranges, range_type='combined'):
        """
        A Chord plot is always drawn on a unit circle.
        """
        if range_type == 'extents':
            return element.nodes.extents
        xdim, ydim = element.nodes.kdims[:2]
        xpad = .05 if self.label_index is None else 0.25
        x0, x1 = ranges[xdim.name][range_type]
        y0, y1 = ranges[ydim.name][range_type]
        xdiff = (x1 - x0)
        ydiff = (y1 - y0)
        if self.label_position == 'right':
            x0, x1 = x0 - (0.05 * xdiff), x1 + xpad * xdiff
        else:
            x0, x1 = x0 - xpad * xdiff, x1 + (0.05 * xdiff)
        x0, x1 = max_range([xdim.range, (x0, x1)])
        y0, y1 = max_range(
            [ydim.range, (y0 - (0.05 * ydiff), y1 + (0.05 * ydiff))])
        return (x0, y0, x1, y1)

    def get_data(self, element, ranges, style):
        data, style, axis_kwargs = super().get_data(element, ranges, style)
        rects, labels = [], []

        label_dim = element.nodes.get_dimension(self.label_index)
        labels = self.labels
        if label_dim and labels:
            if self.label_index not in [2, None]:
                self.param.warning(
                    "Cannot declare style mapping for 'labels' option "
                    "and declare a label_index; ignoring the label_index.")
        elif label_dim:
            labels = label_dim
        if isinstance(labels, str):
            labels = element.nodes.get_dimension(labels)

        if labels is None:
            text = []
        if isinstance(labels, dim):
            text = labels.apply(element, flat=True)
        else:
            text = element.nodes.dimension_values(labels)
            text = [labels.pprint_value(v) for v in text]

        value_dim = element.vdims[0]
        text_labels = []
        for i, node in enumerate(element._sankey['nodes']):
            x0, x1, y0, y1 = (node[a + i] for a in 'xy' for i in '01')
            rect = {'height': y1 - y0, 'width': x1 - x0, 'xy': (x0, y0)}
            rects.append(rect)
            if len(text):
                label = text[i]
            else:
                label = ''
            if self.show_values:
                value = value_dim.pprint_value(node['value'], print_unit=True)
                if label:
                    label = '%s - %s' % (label, value)
                else:
                    label = value
            if label:
                x = x1 + (
                    x1 - x0) / 4. if self.label_position == 'right' else x0 - (
                        x1 - x0) / 4.
                text_labels.append((label, (x, (y0 + y1) / 2.)))
        data['rects'] = rects
        if text_labels:
            data['text'] = text_labels
        return data, style, axis_kwargs

    def _update_labels(self, ax, data, style):
        labels = self.handles.get('labels', [])
        for label in labels:
            try:
                label.remove()
            except:
                pass
        if 'text' not in data:
            return []

        fontsize = style.get('label_text_font_size', 8)
        align = 'left' if self.label_position == 'right' else 'right'
        labels = []
        for text in data['text']:
            label = ax.annotate(*text,
                                xycoords='data',
                                horizontalalignment=align,
                                fontsize=fontsize,
                                verticalalignment='center',
                                rotation_mode='anchor')
            labels.append(label)
        return labels

    def init_artists(self, ax, plot_args, plot_kwargs):
        fontsize = plot_kwargs.pop('label_text_font_size', 8)
        artists = super().init_artists(ax, plot_args, plot_kwargs)
        groups = [g for g in self._style_groups if g != 'node']
        node_opts = filter_styles(plot_kwargs, 'node', groups, ('s', 'node_s'))
        rects = [Rectangle(**rect) for rect in plot_args['rects']]
        if 'vmin' in node_opts:
            node_opts['clim'] = node_opts.pop('vmin'), node_opts.pop('vmax')
        if 'c' in node_opts:
            node_opts['array'] = node_opts.pop('c')
        artists['rects'] = ax.add_collection(
            PatchCollection(rects, **node_opts))
        plot_kwargs['label_text_font_size'] = fontsize
        artists['labels'] = self._update_labels(ax, plot_args, plot_kwargs)
        return artists

    def update_handles(self, key, axis, element, ranges, style):
        data, style, axis_kwargs = self.get_data(element, ranges, style)
        self._update_nodes(element, data, style)
        self._update_edges(element, data, style)
        self.handles['labels'] = self._update_labels(axis, data, style)
        rects = self.handles['rects']
        paths = [Rectangle(**r) for r in data['rects']]
        rects.set_paths(paths)
        if 'node_facecolors' in style:
            rects.set_facecolors(style['node_facecolors'])
        return axis_kwargs
Exemple #28
0
class LayoutPlot(CompositePlot, GenericLayoutPlot):

    shared_axes = param.Boolean(default=True,
                                doc="""
        Whether axes should be shared across plots""")

    shared_datasource = param.Boolean(default=False,
                                      doc="""
        Whether Elements drawing the data from the same object should
        share their Bokeh data source allowing for linked brushing
        and other linked behaviors.""")

    merge_tools = param.Boolean(default=True,
                                doc="""
        Whether to merge all the tools into a single toolbar""")

    tabs = param.Boolean(default=False,
                         doc="""
        Whether to display overlaid plots in separate panes""")

    toolbar = param.ObjectSelector(
        default='above',
        objects=["above", "below", "left", "right", None],
        doc="""
        The toolbar location, must be one of 'above', 'below',
        'left', 'right', None.""")

    def __init__(self, layout, keys=None, **params):
        super(LayoutPlot, self).__init__(layout, keys=keys, **params)
        self.layout, self.subplots, self.paths = self._init_layout(layout)
        if self.top_level:
            self.comm = self.init_comm()
            self.traverse(lambda x: setattr(x, 'comm', self.comm))
            self.traverse(lambda x: attach_streams(self, x.hmap, 2),
                          [GenericElementPlot])

    def _init_layout(self, layout):
        # Situate all the Layouts in the grid and compute the gridspec
        # indices for all the axes required by each LayoutPlot.
        layout_count = 0
        collapsed_layout = layout.clone(shared_data=False, id=layout.id)
        frame_ranges = self.compute_ranges(layout, None, None)
        frame_ranges = OrderedDict([
            (key, self.compute_ranges(layout, key, frame_ranges))
            for key in self.keys
        ])
        layout_items = layout.grid_items()
        layout_dimensions = layout.kdims if isinstance(layout,
                                                       NdLayout) else None
        layout_subplots, layouts, paths = {}, {}, {}
        for r, c in self.coords:
            # Get view at layout position and wrap in AdjointLayout
            key, view = layout_items.get((c, r) if self.transpose else (r, c),
                                         (None, None))
            view = view if isinstance(view, AdjointLayout) else AdjointLayout(
                [view])
            layouts[(r, c)] = view
            paths[r, c] = key

            # Compute the layout type from shape
            layout_lens = {1: 'Single', 2: 'Dual', 3: 'Triple'}
            layout_type = layout_lens.get(len(view), 'Single')

            # Get the AdjoinLayout at the specified coordinate
            positions = AdjointLayoutPlot.layout_dict[layout_type]['positions']

            # Create temporary subplots to get projections types
            # to create the correct subaxes for all plots in the layout
            layout_key, _ = layout_items.get((r, c), (None, None))
            if isinstance(layout, NdLayout) and layout_key:
                layout_dimensions = OrderedDict(
                    zip(layout_dimensions, layout_key))

            # Generate the axes and create the subplots with the appropriate
            # axis objects, handling any Empty objects.
            empty = isinstance(view.main, Empty)
            if empty or view.main is None:
                continue
            elif not view.traverse(lambda x: x, [Element]):
                self.warning('%s is empty, skipping subplot.' % view.main)
                continue
            else:
                layout_count += 1
            subplot_data = self._create_subplots(
                view,
                positions,
                layout_dimensions,
                frame_ranges,
                num=0 if empty else layout_count)
            subplots, adjoint_layout = subplot_data

            # Generate the AdjointLayoutsPlot which will coordinate
            # plotting of AdjointLayouts in the larger grid
            plotopts = self.lookup_options(view, 'plot').options
            layout_plot = AdjointLayoutPlot(adjoint_layout, layout_type,
                                            subplots, **plotopts)
            layout_subplots[(r, c)] = layout_plot
            if layout_key:
                collapsed_layout[layout_key] = adjoint_layout
        return collapsed_layout, layout_subplots, paths

    def _create_subplots(self,
                         layout,
                         positions,
                         layout_dimensions,
                         ranges,
                         num=0):
        """
        Plot all the views contained in the AdjointLayout Object using axes
        appropriate to the layout configuration. All the axes are
        supplied by LayoutPlot - the purpose of the call is to
        invoke subplots with correct options and styles and hide any
        empty axes as necessary.
        """
        subplots = {}
        adjoint_clone = layout.clone(shared_data=False, id=layout.id)
        main_plot = None
        for pos in positions:
            # Pos will be one of 'main', 'top' or 'right' or None
            element = layout.get(pos, None)
            if element is None or not element.traverse(lambda x: x,
                                                       [Element, Empty]):
                continue

            subplot_opts = dict(adjoined=main_plot)
            # Options common for any subplot
            vtype = element.type if isinstance(element,
                                               HoloMap) else element.__class__
            plot_type = Store.registry[self.renderer.backend].get(vtype, None)
            plotopts = self.lookup_options(element, 'plot').options
            side_opts = {}
            if pos != 'main':
                plot_type = AdjointLayoutPlot.registry.get(vtype, plot_type)
                if pos == 'right':
                    yaxis = 'right-bare' if plot_type and 'bare' in plot_type.yaxis else 'right'
                    width = plot_type.width if plot_type else 0
                    side_opts = dict(height=main_plot.height,
                                     yaxis=yaxis,
                                     width=width,
                                     invert_axes=True,
                                     labelled=['y'],
                                     xticks=1,
                                     xaxis=main_plot.xaxis)
                else:
                    xaxis = 'top-bare' if plot_type and 'bare' in plot_type.xaxis else 'top'
                    height = plot_type.height if plot_type else 0
                    side_opts = dict(width=main_plot.width,
                                     xaxis=xaxis,
                                     height=height,
                                     labelled=['x'],
                                     yticks=1,
                                     yaxis=main_plot.yaxis)

            # Override the plotopts as required
            # Customize plotopts depending on position.
            plotopts = dict(side_opts, **plotopts)
            plotopts.update(subplot_opts)

            if vtype is Empty:
                subplots[pos] = None
                continue
            elif plot_type is None:
                self.warning(
                    "Bokeh plotting class for %s type not found, object will "
                    "not be rendered." % vtype.__name__)
                continue
            num = num if len(self.coords) > 1 else 0
            subplot = plot_type(element,
                                keys=self.keys,
                                dimensions=self.dimensions,
                                layout_dimensions=layout_dimensions,
                                ranges=ranges,
                                subplot=True,
                                uniform=self.uniform,
                                layout_num=num,
                                renderer=self.renderer,
                                **dict({'shared_axes': self.shared_axes},
                                       **plotopts))
            subplots[pos] = subplot
            if isinstance(plot_type, type) and issubclass(
                    plot_type, GenericCompositePlot):
                adjoint_clone[pos] = subplots[pos].layout
            else:
                adjoint_clone[pos] = subplots[pos].hmap
            if pos == 'main':
                main_plot = subplot

        return subplots, adjoint_clone

    def initialize_plot(self, plots=None, ranges=None):
        ranges = self.compute_ranges(self.layout, self.keys[-1], None)
        passed_plots = [] if plots is None else plots
        plots = [[] for _ in range(self.rows)]
        tab_titles = {}
        insert_rows, insert_cols = [], []
        for r, c in self.coords:
            subplot = self.subplots.get((r, c), None)
            if subplot is not None:
                shared_plots = passed_plots if self.shared_axes else None
                subplots = subplot.initialize_plot(ranges=ranges,
                                                   plots=shared_plots)

                # Computes plotting offsets depending on
                # number of adjoined plots
                offset = sum(r >= ir for ir in insert_rows)
                if len(subplots) > 2:
                    # Add pad column in this position
                    insert_cols.append(c)
                    if r not in insert_rows:
                        # Insert and pad marginal row if none exists
                        plots.insert(r + offset,
                                     [None for _ in range(len(plots[r]))])
                        # Pad previous rows
                        for ir in range(r):
                            plots[ir].insert(c + 1, None)
                        # Add to row offset
                        insert_rows.append(r)
                        offset += 1
                    # Add top marginal
                    plots[r + offset - 1] += [subplots.pop(-1), None]
                elif len(subplots) > 1:
                    # Add pad column in this position
                    insert_cols.append(c)
                    # Pad previous rows
                    for ir in range(r):
                        plots[r].insert(c + 1, None)
                    # Pad top marginal if one exists
                    if r in insert_rows:
                        plots[r + offset - 1] += 2 * [None]
                else:
                    # Pad top marginal if one exists
                    if r in insert_rows:
                        plots[r + offset - 1] += [None] * (1 +
                                                           (c in insert_cols))
                plots[r + offset] += subplots
                if len(subplots) == 1 and c in insert_cols:
                    plots[r + offset].append(None)
                passed_plots.append(subplots[0])
                if self.tabs:
                    title = subplot.subplots['main']._format_title(
                        self.keys[-1], dimensions=False)
                    if not title:
                        title = ' '.join(self.paths[r, c])
                    tab_titles[r, c] = title
            else:
                plots[r + offset] += [empty_plot(0, 0)]

        # Replace None types with empty plots
        # to avoid bokeh bug
        plots = layout_padding(plots, self.renderer)

        # Wrap in appropriate layout model
        kwargs = dict(sizing_mode=self.sizing_mode)
        if self.tabs:
            panels = [
                Panel(child=child, title=str(tab_titles.get((r, c))))
                for r, row in enumerate(plots) for c, child in enumerate(row)
                if child is not None
            ]
            layout_plot = Tabs(tabs=panels)
        else:
            plots = filter_toolboxes(plots)
            plots, width = pad_plots(plots)
            layout_plot = gridplot(children=plots,
                                   width=width,
                                   toolbar_position=self.toolbar,
                                   merge_tools=self.merge_tools,
                                   **kwargs)

        title = self._get_title(self.keys[-1])
        if title:
            self.handles['title'] = title
            layout_plot = Column(title, layout_plot, **kwargs)

        self._update_callbacks(layout_plot)
        self.handles['plot'] = layout_plot
        self.handles['plots'] = plots
        if self.shared_datasource:
            self.sync_sources()

        self.drawn = True

        return self.handles['plot']

    @update_shared_sources
    def update_frame(self, key, ranges=None):
        """
        Update the internal state of the Plot to represent the given
        key tuple (where integers represent frames). Returns this
        state.
        """
        ranges = self.compute_ranges(self.layout, key, ranges)
        for r, c in self.coords:
            subplot = self.subplots.get((r, c), None)
            if subplot is not None:
                subplot.update_frame(key, ranges)
        title = self._get_title(key)
        if title:
            self.handles['title'] = title
Exemple #29
0
 class Test(param.Parameterized):
     a = param.ObjectSelector()
Exemple #30
0
class BasicDashboardComponents(param.Parameterized):
    # variables to be displayed by widgets and set by the user:
    # select data column for scatter plot x-axis:
    X = param.ObjectSelector(default=None, objects=[], label='x axis data')
    # select data column for scatter plot y-axis:
    Y = param.ObjectSelector(default=None, objects=[], label='y axis data')
    # select column by which to filter the dataframe:
    cutoff_by_column = param.ObjectSelector(default=None, objects=[])
    # set cutoff value by which to filter (keeps rows with value >=cutoff_value:
    cutoff_value = param.Number(default=0, bounds=(0, 120), allow_None=True)
    # select column for boxplot view:
    var_to_inspect = param.ObjectSelector(default=None, objects=[])

    def __init__(self, df, *args, **kwargs):
        self.df = df
        super(type(self), self).__init__(*args, **kwargs)
        # update parameters values based on instance external inputs:
        self.X = self.df.columns.tolist()[
            0]  # set the default value of the widget
        self.param.X.objects = self.df.columns.tolist(
        )  # set the list of objects to select from in the widget
        self.Y = self.df.columns.tolist()[1]
        self.param.Y.objects = self.df.columns.tolist()
        self.cutoff_by_column = self.df.columns.tolist()[0]
        self.param.cutoff_by_column.objects = self.df.columns.tolist()
        self.var_to_inspect = self.df.columns.tolist()[0]
        self.param.var_to_inspect.objects = self.df.columns.tolist()

    def filter_data_rows(self):
        '''
        filter the dataframe rows by a cutoff_value and a column set by the user.
        :return: dataframe where the values in column "cutoff_by_column" >= "cutoff_value"
        '''
        data = self.df.copy()
        data = utils.filter_df_rows(df=data,
                                    cutoff_by_column=self.cutoff_by_column,
                                    cutoff_value=self.cutoff_value)
        return data

    def metrics_table_view(self):
        '''
        :return: dataframe of regression performance metrics (mape, mdape, mse, rmse, R2) wrapped by Panel package
        '''
        data = self.filter_data_rows()
        metrics_dict = utils.calc_metrics_regression(
            data['actual'].to_numpy(), data['predicted'].to_numpy())
        metrics_df = pd.DataFrame.from_dict(metrics_dict, orient='index').T
        return pn.pane.DataFrame(metrics_df, width=1350)

    def scatter_view(self):
        '''
        create a scatter plot using the filtered dataframe (output of self.filter_data_rows) and where x and y axes are
        dataframe columns set by the user via self.x and self.y
        :return: a Plotly scatter plot wrapped by Panel package
        '''
        data = self.filter_data_rows()
        array_x = data[self.X].to_numpy()
        array_y = data[self.Y].to_numpy()
        fig = utils.plot_scatter(x=array_x,
                                 y=array_y,
                                 add_unit_line=True,
                                 add_R2=True,
                                 layout_kwargs=dict(title='',
                                                    xaxis_title=self.X,
                                                    yaxis_title=self.Y))
        return pn.pane.Plotly(
            fig)  # pn.pane.Plotly is the Panel wrapper for Plotly figures

    def error_boxplot_view(self):
        '''
        create a boxplot using the filtered dataframe (output of self.filter_data_rows) and where the data is
        dataframe column set by the user via self.var_to_inspect.
        :return: a Plotly boxplot wrapped by Panel package
        '''
        data = self.filter_data_rows()
        fig = px.box(data,
                     y=self.var_to_inspect,
                     color_discrete_sequence=['green'])
        return pn.pane.Plotly(
            fig, width=400
        )  # pn.pane.Plotly is the Panel wrapper for Plotly figures

    def error_summary_stats_table(self):
        data = self.filter_data_rows()
        stats_df = data.describe(
            percentiles=[0.025, 0.2, 0.3, 0.5, 0.7, 0.8, 0.975]).T
        stats_df = stats_df.round(3)
        return pn.pane.DataFrame(stats_df, width=1350)

    def wrap_param_var_to_inspect(self):
        '''
        change the default widget of self.var_to_inspect from drop-down menu to Radio-button.
        :return: a widget wrapped by Panel package
        '''
        return pn.Param(
            self.param,
            parameters=[
                'var_to_inspect'
            ],  # pn.Param is a wrapper of Panel package for Param objects.
            name='Choose variable to inspect distribution',
            show_name=True,
            widgets={'var_to_inspect': {
                'type': pn.widgets.RadioButtonGroup
            }},
            width=200)

    @param.depends('cutoff_by_column', watch=True)
    def _update_cutoff_value(self):
        '''
        an update method to dynamically change the range of self.cutoff_value based on the user selection of self.cutoff_by_column.
        This method is triggered each time the chosen 'cutoff_by_column' change
        '''
        self.param['cutoff_value'].bounds = (
            self.df[self.cutoff_by_column].min(),
            self.df[self.cutoff_by_column].max())
        self.cutoff_value = self.df[self.cutoff_by_column].min()