vedo.pyplot

Advanced plotting functionalities.

   1#!/usr/bin/env python3
   2# -*- coding: utf-8 -*-
   3from typing import Self, Union
   4import numpy as np
   5
   6import vedo.vtkclasses as vtki
   7
   8import vedo
   9from vedo import settings
  10from vedo.transformations import cart2spher, spher2cart
  11from vedo import addons
  12from vedo import colors
  13from vedo import utils
  14from vedo import shapes
  15from vedo.pointcloud import merge
  16from vedo.mesh import Mesh
  17from vedo.assembly import Assembly
  18
  19__docformat__ = "google"
  20
  21__doc__ = """
  22Advanced plotting functionalities.
  23
  24![](https://vedo.embl.es/images/pyplot/fitPolynomial2.png)
  25"""
  26
  27__all__ = [
  28    "Figure",
  29    "Histogram1D",
  30    "Histogram2D",
  31    "PlotXY",
  32    "PlotBars",
  33    "plot",
  34    "histogram",
  35    "fit",
  36    "donut",
  37    "violin",
  38    "whisker",
  39    "streamplot",
  40    "matrix",
  41    "DirectedGraph",
  42]
  43
  44
  45##########################################################################
  46class LabelData:
  47    """Helper internal class to hold label information."""
  48
  49    def __init__(self):
  50        """Helper internal class to hold label information."""
  51        self.text   = "dataset"
  52        self.tcolor = "black"
  53        self.marker = "s"
  54        self.mcolor = "black"
  55
  56
  57##########################################################################
  58class Figure(Assembly):
  59    """Format class for figures."""
  60
  61    def __init__(self, xlim, ylim, aspect=4 / 3, padding=(0.05, 0.05, 0.05, 0.05), **kwargs):
  62        """
  63        Create an empty formatted figure for plotting.
  64
  65        Arguments:
  66            xlim : (list)
  67                range of the x-axis as [x0, x1]
  68            ylim : (list)
  69                range of the y-axis as [y0, y1]
  70            aspect : (float, str)
  71                the desired aspect ratio of the histogram. Default is 4/3.
  72                Use `aspect="equal"` to force the same units in x and y.
  73            padding : (float, list)
  74                keep a padding space from the axes (as a fraction of the axis size).
  75                This can be a list of four numbers.
  76            xtitle : (str)
  77                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
  78            ytitle : (str)
  79                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
  80            grid : (bool)
  81                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
  82            axes : (dict)
  83                an extra dictionary of options for the `vedo.addons.Axes` object
  84        """
  85
  86        self.verbose = True  # printing to stdout on every mouse click
  87
  88        self.xlim = np.asarray(xlim)
  89        self.ylim = np.asarray(ylim)
  90        self.aspect = aspect
  91        self.padding = padding
  92        if not utils.is_sequence(self.padding):
  93            self.padding = [self.padding, self.padding, self.padding, self.padding]
  94
  95        self.force_scaling_types = (
  96            shapes.Glyph,
  97            shapes.Line,
  98            shapes.Rectangle,
  99            shapes.DashedLine,
 100            shapes.Tube,
 101            shapes.Ribbon,
 102            shapes.GeoCircle,
 103            shapes.Arc,
 104            shapes.Grid,
 105            # shapes.Arrows, # todo
 106            # shapes.Arrows2D, # todo
 107            shapes.Brace,  # todo
 108        )
 109
 110        options = dict(kwargs)
 111
 112        self.title  = options.pop("title", "")
 113        self.xtitle = options.pop("xtitle", " ")
 114        self.ytitle = options.pop("ytitle", " ")
 115        number_of_divisions = 6
 116
 117        self.legend = None
 118        self.labels = []
 119        self.label = options.pop("label", None)
 120        if self.label:
 121            self.labels = [self.label]
 122
 123        self.axopts = options.pop("axes", {})
 124        if isinstance(self.axopts, (bool, int, float)):
 125            if self.axopts:
 126                self.axopts = {}
 127        if self.axopts or isinstance(self.axopts, dict):
 128            number_of_divisions = self.axopts.pop("number_of_divisions", number_of_divisions)
 129
 130            self.axopts["xtitle"] = self.xtitle
 131            self.axopts["ytitle"] = self.ytitle
 132
 133            if "xygrid" not in self.axopts:  ## modify the default
 134                self.axopts["xygrid"] = options.pop("grid", False)
 135
 136            if "xygrid_transparent" not in self.axopts:  ## modify the default
 137                self.axopts["xygrid_transparent"] = True
 138
 139            if "xtitle_position" not in self.axopts:  ## modify the default
 140                self.axopts["xtitle_position"] = 0.5
 141                self.axopts["xtitle_justify"] = "top-center"
 142
 143            if "ytitle_position" not in self.axopts:  ## modify the default
 144                self.axopts["ytitle_position"] = 0.5
 145                self.axopts["ytitle_justify"] = "bottom-center"
 146
 147            if self.label:
 148                if "c" in self.axopts:
 149                    self.label.tcolor = self.axopts["c"]
 150
 151        x0, x1 = self.xlim
 152        y0, y1 = self.ylim
 153        dx = x1 - x0
 154        dy = y1 - y0
 155        x0lim, x1lim = (x0 - self.padding[0] * dx, x1 + self.padding[1] * dx)
 156        y0lim, y1lim = (y0 - self.padding[2] * dy, y1 + self.padding[3] * dy)
 157        dy = y1lim - y0lim
 158
 159        self.axes = None
 160        if xlim[0] >= xlim[1] or ylim[0] >= ylim[1]:
 161            vedo.logger.warning(f"Null range for Figure {self.title}... returning an empty Assembly.")
 162            super().__init__()
 163            self.yscale = 0
 164            return
 165
 166        if aspect == "equal":
 167            self.aspect = dx / dy  # so that yscale becomes 1
 168
 169        self.yscale = dx / dy / self.aspect
 170
 171        y0lim *= self.yscale
 172        y1lim *= self.yscale
 173
 174        self.x0lim = x0lim
 175        self.x1lim = x1lim
 176        self.y0lim = y0lim
 177        self.y1lim = y1lim
 178
 179        self.ztolerance = options.pop("ztolerance", None)
 180        if self.ztolerance is None:
 181            self.ztolerance = dx / 5000
 182
 183        ############## create axes
 184        if self.axopts:
 185            axes_opts = self.axopts
 186            if self.axopts is True or self.axopts == 1:
 187                axes_opts = {}
 188
 189            tp, ts = utils.make_ticks(y0lim / self.yscale, 
 190                                      y1lim / self.yscale, number_of_divisions)
 191            labs = []
 192            for i in range(1, len(tp) - 1):
 193                ynew = utils.lin_interpolate(tp[i], [0, 1], [y0lim, y1lim])
 194                labs.append([ynew, ts[i]])
 195
 196            if self.title:
 197                axes_opts["htitle"] = self.title
 198            axes_opts["y_values_and_labels"] = labs
 199            axes_opts["xrange"] = (x0lim, x1lim)
 200            axes_opts["yrange"] = (y0lim, y1lim)
 201            axes_opts["zrange"] = (0, 0)
 202            axes_opts["y_use_bounds"] = True
 203
 204            if "c" not in axes_opts and "ac" in options:
 205                axes_opts["c"] = options["ac"]
 206
 207            self.axes = addons.Axes(**axes_opts)
 208
 209        super().__init__([self.axes])
 210        self.name = "Figure"
 211
 212        vedo.last_figure = self if settings.remember_last_figure_format else None
 213
 214
 215    ##################################################################
 216    def _repr_html_(self):
 217        """
 218        HTML representation of the Figure object for Jupyter Notebooks.
 219
 220        Returns:
 221            HTML text with the image and some properties.
 222        """
 223        import io
 224        import base64
 225        from PIL import Image
 226
 227        library_name = "vedo.pyplot.Figure"
 228        help_url = "https://vedo.embl.es/docs/vedo/pyplot.html#Figure"
 229
 230        arr = self.thumbnail(zoom=1.1)
 231
 232        im = Image.fromarray(arr)
 233        buffered = io.BytesIO()
 234        im.save(buffered, format="PNG", quality=100)
 235        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
 236        url = "data:image/png;base64," + encoded
 237        image = f"<img src='{url}'></img>"
 238
 239        bounds = "<br/>".join(
 240            [
 241                vedo.utils.precision(min_x, 4) + " ... " + vedo.utils.precision(max_x, 4)
 242                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
 243            ]
 244        )
 245
 246        help_text = ""
 247        if self.name:
 248            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
 249        help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>"
 250        if self.filename:
 251            dots = ""
 252            if len(self.filename) > 30:
 253                dots = "..."
 254            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"
 255
 256        all = [
 257            "<table>",
 258            "<tr>",
 259            "<td>",
 260            image,
 261            "</td>",
 262            "<td style='text-align: center; vertical-align: center;'><br/>",
 263            help_text,
 264            "<table>",
 265            "<tr><td><b> nr. of parts </b></td><td>" + str(self.GetNumberOfPaths()) + "</td></tr>",
 266            "<tr><td><b> position </b></td><td>" + str(self.GetPosition()) + "</td></tr>",
 267            "<tr><td><b> x-limits </b></td><td>" + utils.precision(self.xlim, 4) + "</td></tr>",
 268            "<tr><td><b> y-limits </b></td><td>" + utils.precision(self.ylim, 4) + "</td></tr>",
 269            "<tr><td><b> world bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>",
 270            "</table>",
 271            "</table>",
 272        ]
 273        return "\n".join(all)
 274
 275    def __add__(self, *obj):
 276        # just to avoid confusion, supersede Assembly.__add__
 277        return self.__iadd__(*obj)
 278
 279    def __iadd__(self, *obj):
 280        if len(obj) == 1 and isinstance(obj[0], Figure):
 281            return self._check_unpack_and_insert(obj[0])
 282
 283        obj = utils.flatten(obj)
 284        return self.insert(*obj)
 285
 286    def _check_unpack_and_insert(self, fig: "Figure") -> Self:
 287
 288        if fig.label:
 289            self.labels.append(fig.label)
 290
 291        if abs(self.yscale - fig.yscale) > 0.0001:
 292
 293            colors.printc(":bomb:ERROR: adding incompatible Figure. Y-scales are different:", c='r', invert=True)
 294            colors.printc("  first  figure:", self.yscale, c='r')
 295            colors.printc("  second figure:", fig.yscale, c='r')
 296
 297            colors.printc("One or more of these parameters can be the cause:", c="r")
 298            if list(self.xlim) != list(fig.xlim):
 299                colors.printc("xlim --------------------------------------------\n",
 300                              " first  figure:", self.xlim, "\n",
 301                              " second figure:", fig.xlim, c='r')
 302            if list(self.ylim) != list(fig.ylim):
 303                colors.printc("ylim --------------------------------------------\n",
 304                              " first  figure:", self.ylim, "\n",
 305                              " second figure:", fig.ylim, c='r')
 306            if list(self.padding) != list(fig.padding):
 307                colors.printc("padding -----------------------------------------\n",
 308                              " first  figure:", self.padding,
 309                              " second figure:", fig.padding, c='r')
 310            if self.aspect != fig.aspect:
 311                colors.printc("aspect ------------------------------------------\n",
 312                              " first  figure:", self.aspect, "\n",
 313                              " second figure:", fig.aspect, c='r')
 314
 315            colors.printc("\n:idea: Consider using fig2 = histogram(..., like=fig1)", c="r")
 316            colors.printc(" Or fig += histogram(..., like=fig)\n", c="r")
 317            return self
 318
 319        offset = self.zbounds()[1] + self.ztolerance
 320
 321        for ele in fig.unpack():
 322            if "Axes" in ele.name:
 323                continue
 324            ele.z(offset)
 325            self.insert(ele, rescale=False)
 326
 327        return self
 328
 329    def insert(self, *objs, rescale=True, as3d=True, adjusted=False, cut=True) -> Self:
 330        """
 331        Insert objects into a Figure.
 332
 333        The recommended syntax is to use "+=", which calls `insert()` under the hood.
 334        If a whole Figure is added with "+=", it is unpacked and its objects are added
 335        one by one.
 336
 337        Arguments:
 338            rescale : (bool)
 339                rescale the y axis position while inserting the object.
 340            as3d : (bool)
 341                if True keep the aspect ratio of the 3d object, otherwise stretch it in y.
 342            adjusted : (bool)
 343                adjust the scaling according to the shortest axis
 344            cut : (bool)
 345                cut off the parts of the object which go beyond the axes frame.
 346        """
 347        for a in objs:
 348
 349            if a in self.objects:
 350                # should not add twice the same object in plot
 351                continue
 352
 353            if isinstance(a, vedo.Points):  # hacky way to identify Points
 354                if a.ncells == a.npoints:
 355                    poly = a.dataset
 356                    if poly.GetNumberOfPolys() == 0 and poly.GetNumberOfLines() == 0:
 357                        as3d = False
 358                        rescale = True
 359
 360            if isinstance(a, (shapes.Arrow, shapes.Arrow2D)):
 361                # discard input Arrow and substitute it with a brand new one
 362                # (because scaling would fatally distort the shape)
 363
 364                py = a.base[1]
 365                a.top[1] = (a.top[1] - py) * self.yscale + py
 366                b = shapes.Arrow2D(a.base, a.top, s=a.s, fill=a.fill).z(a.z())
 367
 368                prop = a.properties
 369                prop.LightingOff()
 370                b.actor.SetProperty(prop)
 371                b.properties = prop
 372                b.y(py * self.yscale)
 373                a = b
 374
 375            # elif isinstance(a, shapes.Rectangle) and a.radius is not None:
 376            #     # discard input Rectangle and substitute it with a brand new one
 377            #     # (because scaling would fatally distort the shape of the corners)
 378            #     py = a.corner1[1]
 379            #     rx1,ry1,rz1 = a.corner1
 380            #     rx2,ry2,rz2 = a.corner2
 381            #     ry2 = (ry2-py) * self.yscale + py
 382            #     b = shapes.Rectangle([rx1,0,rz1], [rx2,ry2,rz2], radius=a.radius).z(a.z())
 383            #     b.SetProperty(a.properties)
 384            #     b.y(py / self.yscale)
 385            #     a = b
 386
 387            else:
 388
 389                if rescale:
 390
 391                    if not isinstance(a, Figure):
 392
 393                        if as3d and not isinstance(a, self.force_scaling_types):
 394                            if adjusted:
 395                                scl = np.min([1, self.yscale])
 396                            else:
 397                                scl = self.yscale
 398
 399                            a.scale(scl)
 400
 401                        else:
 402                            a.scale([1, self.yscale, 1])
 403
 404                    # shift it in y
 405                    a.y(a.y() * self.yscale)
 406
 407            if cut:
 408                try:
 409                    bx0, bx1, by0, by1, _, _ = a.bounds()
 410                    if self.y0lim > by0:
 411                        a.cut_with_plane([0, self.y0lim, 0], [0, 1, 0])
 412                    if self.y1lim < by1:
 413                        a.cut_with_plane([0, self.y1lim, 0], [0, -1, 0])
 414                    if self.x0lim > bx0:
 415                        a.cut_with_plane([self.x0lim, 0, 0], [1, 0, 0])
 416                    if self.x1lim < bx1:
 417                        a.cut_with_plane([self.x1lim, 0, 0], [-1, 0, 0])
 418                except:
 419                    # print("insert(): cannot cut", [a])
 420                    pass
 421
 422            self.AddPart(a.actor)
 423            self.objects.append(a)
 424
 425        return self
 426
 427    def add_label(self, text: str, c=None, marker="", mc="black") -> Self:
 428        """
 429        Manually add en entry label to the legend.
 430
 431        Arguments:
 432            text : (str)
 433                text string for the label.
 434            c : (str)
 435                color of the text
 436            marker : (str), Mesh
 437                a marker char or a Mesh object to be used as marker
 438            mc : (str)
 439                color for the marker
 440        """
 441        newlabel = LabelData()
 442        newlabel.text = text.replace("\n", " ")
 443        newlabel.tcolor = c
 444        newlabel.marker = marker
 445        newlabel.mcolor = mc
 446        self.labels.append(newlabel)
 447        return self
 448
 449    def add_legend(
 450        self,
 451        pos="top-right",
 452        relative=True,
 453        font=None,
 454        s=1,
 455        c=None,
 456        vspace=1.75,
 457        padding=0.1,
 458        radius=0,
 459        alpha=1,
 460        bc="k7",
 461        lw=1,
 462        lc="k4",
 463        z=0,
 464    ) -> Self:
 465        """
 466        Add existing labels to form a legend box.
 467        Labels have been previously filled with eg: `plot(..., label="text")`
 468
 469        Arguments:
 470            pos : (str, list)
 471                A string or 2D coordinates. The default is "top-right".
 472            relative : (bool)
 473                control whether `pos` is absolute or relative, e.i. normalized
 474                to the x and y ranges so that x and y in `pos=[x,y]` should be
 475                both in the range [0,1].
 476                This flag is ignored if a string despcriptor is passed.
 477                Default is True.
 478            font : (str, int)
 479                font name or number.
 480                Check [available fonts here](https://vedo.embl.es/fonts).
 481            s : (float)
 482                global size of the legend
 483            c : (str)
 484                color of the text
 485            vspace : (float)
 486                vertical spacing of lines
 487            padding : (float)
 488                padding of the box as a fraction of the text size
 489            radius : (float)
 490                border radius of the box
 491            alpha : (float)
 492                opacity of the box. Values below 1 may cause poor rendering
 493                because of antialiasing.
 494                Use alpha = 0 to remove the box.
 495            bc : (str)
 496                box color
 497            lw : (int)
 498                border line width of the box in pixel units
 499            lc : (int)
 500                border line color of the box
 501            z : (float)
 502                set the zorder as z position (useful to avoid overlap)
 503        """
 504        sx = self.x1lim - self.x0lim
 505        s = s * sx / 55  # so that input can be about 1
 506
 507        ds = 0
 508        texts = []
 509        mks = []
 510        for i, t in enumerate(self.labels):
 511            label = self.labels[i]
 512            t = label.text
 513
 514            if label.tcolor is not None:
 515                c = label.tcolor
 516
 517            tx = vedo.shapes.Text3D(t, s=s, c=c, justify="center-left", font=font)
 518            y0, y1 = tx.ybounds()
 519            ds = max(y1 - y0, ds)
 520            texts.append(tx)
 521
 522            mk = label.marker
 523            if isinstance(mk, vedo.Points):
 524                mk = mk.clone(deep=False).lighting("off")
 525                cm = mk.center_of_mass()
 526                ty0, ty1 = tx.ybounds()
 527                oby0, oby1 = mk.ybounds()
 528                mk.shift(-cm)
 529                mk.SetOrigin(cm)
 530                mk.scale((ty1 - ty0) / (oby1 - oby0))
 531                mk.scale([1.1, 1.1, 0.01])
 532            elif mk == "-":
 533                mk = vedo.shapes.Marker(mk, s=s * 2)
 534                mk.color(label.mcolor)
 535            else:
 536                mk = vedo.shapes.Marker(mk, s=s)
 537                mk.color(label.mcolor)
 538            mks.append(mk)
 539
 540        for i, tx in enumerate(texts):
 541            tx.shift(0, -(i + 0) * ds * vspace)
 542
 543        for i, mk in enumerate(mks):
 544            mk.shift(-ds * 1.75, -(i + 0) * ds * vspace, 0)
 545
 546        acts = texts + mks
 547
 548        aleg = Assembly(acts)  # .show(axes=1).close()
 549        x0, x1, y0, y1, _, _ = aleg.GetBounds()
 550
 551        if alpha:
 552            dx = x1 - x0
 553            dy = y1 - y0
 554
 555            if not utils.is_sequence(padding):
 556                padding = [padding] * 4
 557            padding = min(padding)
 558            padding = min(padding * dx, padding * dy)
 559            if len(self.labels) == 1:
 560                padding *= 4
 561            x0 -= padding
 562            x1 += padding
 563            y0 -= padding
 564            y1 += padding
 565
 566            box = shapes.Rectangle([x0, y0], [x1, y1], radius=radius, c=bc, alpha=alpha)
 567            box.shift(0, 0, -dy / 100).pickable(False)
 568            if lc:
 569                box.lc(lc).lw(lw)
 570            aleg.AddPart(box.actor)
 571            aleg.objects.append(box)
 572
 573        xlim = self.xlim
 574        ylim = self.ylim
 575        if isinstance(pos, str):
 576            px, py = 0.0, 0.0
 577            rx, ry = (xlim[1] + xlim[0]) / 2, (ylim[1] + ylim[0]) / 2
 578            shx, shy = 0.0, 0.0
 579            if "top" in pos:
 580                if "cent" in pos:
 581                    px, py = rx, ylim[1]
 582                    shx, shy = (x0 + x1) / 2, y1
 583                elif "left" in pos:
 584                    px, py = xlim[0], ylim[1]
 585                    shx, shy = x0, y1
 586                else:  # "right"
 587                    px, py = xlim[1], ylim[1]
 588                    shx, shy = x1, y1
 589            elif "bot" in pos:
 590                if "left" in pos:
 591                    px, py = xlim[0], ylim[0]
 592                    shx, shy = x0, y0
 593                elif "right" in pos:
 594                    px, py = xlim[1], ylim[0]
 595                    shx, shy = x1, y0
 596                else:  # "cent"
 597                    px, py = rx, ylim[0]
 598                    shx, shy = (x0 + x1) / 2, y0
 599            elif "cent" in pos:
 600                if "left" in pos:
 601                    px, py = xlim[0], ry
 602                    shx, shy = x0, (y0 + y1) / 2
 603                elif "right" in pos:
 604                    px, py = xlim[1], ry
 605                    shx, shy = x1, (y0 + y1) / 2
 606            else:
 607                vedo.logger.error(f"in add_legend(), cannot understand {pos}")
 608                raise RuntimeError
 609
 610        else:
 611
 612            if relative:
 613                rx, ry = pos[0], pos[1]
 614                px = (xlim[1] - xlim[0]) * rx + xlim[0]
 615                py = (ylim[1] - ylim[0]) * ry + ylim[0]
 616                z *= xlim[1] - xlim[0]
 617            else:
 618                px, py = pos[0], pos[1]
 619            shx, shy = x0, y1
 620
 621        zpos = aleg.pos()[2]
 622        aleg.pos(px - shx, py * self.yscale - shy, zpos + sx / 50 + z)
 623
 624        self.insert(aleg, rescale=False, cut=False)
 625        self.legend = aleg
 626        aleg.name = "Legend"
 627        return self
 628
 629
 630#########################################################################################
 631class Histogram1D(Figure):
 632    "1D histogramming."
 633
 634    def __init__(
 635        self,
 636        data,
 637        weights=None,
 638        bins=None,
 639        errors=False,
 640        density=False,
 641        logscale=False,
 642        max_entries=None,
 643        fill=True,
 644        radius=0.075,
 645        c="olivedrab",
 646        gap=0.0,
 647        alpha=1,
 648        outline=False,
 649        lw=2,
 650        lc="k",
 651        texture="",
 652        marker="",
 653        ms=None,
 654        mc=None,
 655        ma=None,
 656        # Figure and axes options:
 657        like=None,
 658        xlim=None,
 659        ylim=(0, None),
 660        aspect=4 / 3,
 661        padding=(0.0, 0.0, 0.0, 0.05),
 662        title="",
 663        xtitle=" ",
 664        ytitle=" ",
 665        ac="k",
 666        grid=False,
 667        ztolerance=None,
 668        label="",
 669        **fig_kwargs,
 670    ):
 671        """
 672        Creates a `Histogram1D(Figure)` object.
 673
 674        Arguments:
 675            weights : (list)
 676                An array of weights, of the same shape as `data`. Each value in `data`
 677                only contributes its associated weight towards the bin count (instead of 1).
 678            bins : (int)
 679                number of bins
 680            density : (bool)
 681                normalize the area to 1 by dividing by the nr of entries and bin size
 682            logscale : (bool)
 683                use logscale on y-axis
 684            max_entries : (int)
 685                if `data` is larger than `max_entries`, a random sample of `max_entries` is used
 686            fill : (bool)
 687                fill bars with solid color `c`
 688            gap : (float)
 689                leave a small space btw bars
 690            radius : (float)
 691                border radius of the top of the histogram bar. Default value is 0.1.
 692            texture : (str)
 693                url or path to an image to be used as texture for the bin
 694            outline : (bool)
 695                show outline of the bins
 696            errors : (bool)
 697                show error bars
 698            xtitle : (str)
 699                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
 700            ytitle : (str)
 701                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
 702            padding : (float), list
 703                keep a padding space from the axes (as a fraction of the axis size).
 704                This can be a list of four numbers.
 705            aspect : (float)
 706                the desired aspect ratio of the histogram. Default is 4/3.
 707            grid : (bool)
 708                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
 709            ztolerance : (float)
 710                a tolerance factor to superimpose objects (along the z-axis).
 711
 712        Examples:
 713            - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
 714            - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
 715            - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
 716            - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)
 717
 718            ![](https://vedo.embl.es/images/pyplot/histo_1D.png)
 719        """
 720
 721        if max_entries and data.shape[0] > max_entries:
 722            data = np.random.choice(data, int(max_entries))
 723
 724        # purge NaN from data
 725        valid_ids = np.all(np.logical_not(np.isnan(data)))
 726        data = np.asarray(data[valid_ids]).ravel()
 727
 728        # if data.dtype is integer try to center bins by default
 729        if like is None and bins is None and np.issubdtype(data.dtype, np.integer):
 730            if xlim is None and ylim == (0, None):
 731                x1, x0 = data.max(), data.min()
 732                if 0 < x1 - x0 <= 100:
 733                    bins = x1 - x0 + 1
 734                    xlim = (x0 - 0.5, x1 + 0.5)
 735
 736        if like is None and vedo.last_figure is not None:
 737            if xlim is None and ylim == (0, None):
 738                like = vedo.last_figure
 739
 740        if like is not None:
 741            xlim = like.xlim
 742            ylim = like.ylim
 743            aspect = like.aspect
 744            padding = like.padding
 745            if bins is None:
 746                bins = like.bins
 747        if bins is None:
 748            bins = 20
 749
 750        if utils.is_sequence(xlim):
 751            # deal with user passing eg [x0, None]
 752            _x0, _x1 = xlim
 753            if _x0 is None:
 754                _x0 = data.min()
 755            if _x1 is None:
 756                _x1 = data.max()
 757            xlim = [_x0, _x1]
 758
 759        fs, edges = np.histogram(data, bins=bins, weights=weights, range=xlim)
 760        binsize = edges[1] - edges[0]
 761        ntot = data.shape[0]
 762
 763        fig_kwargs["title"] = title
 764        fig_kwargs["xtitle"] = xtitle
 765        fig_kwargs["ytitle"] = ytitle
 766        fig_kwargs["ac"] = ac
 767        fig_kwargs["ztolerance"] = ztolerance
 768        fig_kwargs["grid"] = grid
 769
 770        unscaled_errors = np.sqrt(fs)
 771        if density:
 772            scaled_errors = unscaled_errors / (ntot * binsize)
 773            fs = fs / (ntot * binsize)
 774            if ytitle == " ":
 775                ytitle = f"counts / ({ntot} x {utils.precision(binsize,3)})"
 776                fig_kwargs["ytitle"] = ytitle
 777        elif logscale:
 778            se_up = np.log10(fs + unscaled_errors / 2 + 1)
 779            se_dw = np.log10(fs - unscaled_errors / 2 + 1)
 780            scaled_errors = np.c_[se_up, se_dw]
 781            fs = np.log10(fs + 1)
 782            if ytitle == " ":
 783                ytitle = "log_10 (counts+1)"
 784                fig_kwargs["ytitle"] = ytitle
 785
 786        x0, x1 = np.min(edges), np.max(edges)
 787        y0, y1 = ylim[0], np.max(fs)
 788
 789        _errors = []
 790        if errors:
 791            if density:
 792                y1 += max(scaled_errors) / 2
 793                _errors = scaled_errors
 794            elif logscale:
 795                y1 = max(scaled_errors[:, 0])
 796                _errors = scaled_errors
 797            else:
 798                y1 += max(unscaled_errors) / 2
 799                _errors = unscaled_errors
 800
 801        if like is None:
 802            ylim = list(ylim)
 803            if xlim is None:
 804                xlim = [x0, x1]
 805            if ylim[1] is None:
 806                ylim[1] = y1
 807            if ylim[0] != 0:
 808                ylim[0] = y0
 809
 810        self.title = title
 811        self.xtitle = xtitle
 812        self.ytitle = ytitle
 813        self.entries = ntot
 814        self.frequencies = fs
 815        self.errors = _errors
 816        self.edges = edges
 817        self.centers = (edges[0:-1] + edges[1:]) / 2
 818        self.mean = data.mean()
 819        self.mode = self.centers[np.argmax(fs)]
 820        self.std = data.std()
 821        self.bins = edges  # internally used by "like"
 822
 823        ############################### stats legend as htitle
 824        addstats = False
 825        if not title:
 826            if "axes" not in fig_kwargs:
 827                addstats = True
 828                axes_opts = {}
 829                fig_kwargs["axes"] = axes_opts
 830            elif fig_kwargs["axes"] is False:
 831                pass
 832            else:
 833                axes_opts = fig_kwargs["axes"]
 834                if "htitle" not in axes_opts:
 835                    addstats = True
 836
 837        if addstats:
 838            htitle = f"Entries:~~{int(self.entries)}  "
 839            htitle += f"Mean:~~{utils.precision(self.mean, 4)}  "
 840            htitle += f"STD:~~{utils.precision(self.std, 4)}  "
 841
 842            axes_opts["htitle"] = htitle
 843            axes_opts["htitle_justify"] = "bottom-left"
 844            axes_opts["htitle_size"] = 0.016
 845            # axes_opts["htitle_offset"] = [-0.49, 0.01, 0]
 846
 847        if mc is None:
 848            mc = lc
 849        if ma is None:
 850            ma = alpha
 851
 852        if label:
 853            nlab = LabelData()
 854            nlab.text = label
 855            nlab.tcolor = ac
 856            nlab.marker = marker
 857            nlab.mcolor = mc
 858            if not marker:
 859                nlab.marker = "s"
 860                nlab.mcolor = c
 861            fig_kwargs["label"] = nlab
 862
 863        ############################################### Figure init
 864        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
 865
 866        if not self.yscale:
 867            return
 868
 869        if utils.is_sequence(bins):
 870            myedges = np.array(bins)
 871            bins = len(bins) - 1
 872        else:
 873            myedges = edges
 874
 875        bin_centers = []
 876        for i in range(bins):
 877            x = (myedges[i] + myedges[i + 1]) / 2
 878            bin_centers.append([x, fs[i], 0])
 879
 880        rs = []
 881        maxheigth = 0
 882        if not fill and not outline and not errors and not marker:
 883            outline = True  # otherwise it's empty..
 884
 885        if fill:  #####################
 886            if outline:
 887                gap = 0
 888
 889            for i in range(bins):
 890                F = fs[i]
 891                if not F:
 892                    continue
 893                p0 = (myedges[i] + gap * binsize, 0, 0)
 894                p1 = (myedges[i + 1] - gap * binsize, F, 0)
 895
 896                if radius:
 897                    if gap:
 898                        rds = np.array([0, 0, radius, radius])
 899                    else:
 900                        rd1 = 0 if i < bins - 1 and fs[i + 1] >= F else radius / 2
 901                        rd2 = 0 if i > 0 and fs[i - 1] >= F else radius / 2
 902                        rds = np.array([0, 0, rd1, rd2])
 903                    p1_yscaled = [p1[0], p1[1] * self.yscale, 0]
 904                    r = shapes.Rectangle(p0, p1_yscaled, radius=rds * binsize, res=6)
 905                    r.scale([1, 1 / self.yscale, 1])
 906                    r.radius = None  # so it doesnt get recreated and rescaled by insert()
 907                else:
 908                    r = shapes.Rectangle(p0, p1)
 909
 910                if texture:
 911                    r.texture(texture)
 912                    c = "w"
 913
 914                r.actor.PickableOff()
 915                maxheigth = max(maxheigth, p1[1])
 916                if c in colors.cmaps_names:
 917                    col = colors.color_map((p0[0] + p1[0]) / 2, c, myedges[0], myedges[-1])
 918                else:
 919                    col = c
 920                r.color(col).alpha(alpha).lighting("off")
 921                r.z(self.ztolerance)
 922                rs.append(r)
 923
 924        if outline:  #####################
 925            lns = [[myedges[0], 0, 0]]
 926            for i in range(bins):
 927                lns.append([myedges[i], fs[i], 0])
 928                lns.append([myedges[i + 1], fs[i], 0])
 929                maxheigth = max(maxheigth, fs[i])
 930            lns.append([myedges[-1], 0, 0])
 931            outl = shapes.Line(lns, c=lc, alpha=alpha, lw=lw)
 932            outl.z(self.ztolerance * 2)
 933            rs.append(outl)
 934
 935        if errors:  #####################
 936            for i in range(bins):
 937                x = self.centers[i]
 938                f = fs[i]
 939                if not f:
 940                    continue
 941                err = _errors[i]
 942                if utils.is_sequence(err):
 943                    el = shapes.Line([x, err[0], 0], [x, err[1], 0], c=lc, alpha=alpha, lw=lw)
 944                else:
 945                    el = shapes.Line(
 946                        [x, f - err / 2, 0], [x, f + err / 2, 0], c=lc, alpha=alpha, lw=lw
 947                    )
 948                el.z(self.ztolerance * 3)
 949                rs.append(el)
 950
 951        if marker:  #####################
 952
 953            # remove empty bins (we dont want a marker there)
 954            bin_centers = np.array(bin_centers)
 955            bin_centers = bin_centers[bin_centers[:, 1] > 0]
 956
 957            if utils.is_sequence(ms):  ### variable point size
 958                mk = shapes.Marker(marker, s=1)
 959                mk.scale([1, 1 / self.yscale, 1])
 960                msv = np.zeros_like(bin_centers)
 961                msv[:, 0] = ms
 962                marked = shapes.Glyph(
 963                    bin_centers, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
 964                )
 965            else:  ### fixed point size
 966
 967                if ms is None:
 968                    ms = (xlim[1] - xlim[0]) / 100.0
 969                else:
 970                    ms = (xlim[1] - xlim[0]) / 100.0 * ms
 971
 972                if utils.is_sequence(mc):
 973                    mk = shapes.Marker(marker, s=ms)
 974                    mk.scale([1, 1 / self.yscale, 1])
 975                    msv = np.zeros_like(bin_centers)
 976                    msv[:, 0] = 1
 977                    marked = shapes.Glyph(
 978                        bin_centers, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
 979                    )
 980                else:
 981                    mk = shapes.Marker(marker, s=ms)
 982                    mk.scale([1, 1 / self.yscale, 1])
 983                    marked = shapes.Glyph(bin_centers, mk, c=mc)
 984
 985            marked.alpha(ma)
 986            marked.z(self.ztolerance * 4)
 987            rs.append(marked)
 988
 989        self.insert(*rs, as3d=False)
 990        self.name = "Histogram1D"
 991
 992    def print(self, **kwargs) -> None:
 993        """Print infos about this histogram"""
 994        txt = (
 995            f"{self.name}  {self.title}\n"
 996            f"    xtitle  = '{self.xtitle}'\n"
 997            f"    ytitle  = '{self.ytitle}'\n"
 998            f"    entries = {self.entries}\n"
 999            f"    mean    = {self.mean}\n"
1000            f"    std     = {self.std}"
1001        )
1002        colors.printc(txt, **kwargs)
1003
1004
1005#########################################################################################
1006class Histogram2D(Figure):
1007    """2D histogramming."""
1008
1009    def __init__(
1010        self,
1011        xvalues,
1012        yvalues=None,
1013        bins=25,
1014        weights=None,
1015        cmap="cividis",
1016        alpha=1,
1017        gap=0,
1018        scalarbar=True,
1019        # Figure and axes options:
1020        like=None,
1021        xlim=None,
1022        ylim=(None, None),
1023        zlim=(None, None),
1024        aspect=1,
1025        title="",
1026        xtitle=" ",
1027        ytitle=" ",
1028        ztitle="",
1029        ac="k",
1030        **fig_kwargs,
1031    ):
1032        """
1033        Input data formats `[(x1,x2,..), (y1,y2,..)] or [(x1,y1), (x2,y2),..]`
1034        are both valid.
1035
1036        Use keyword `like=...` if you want to use the same format of a previously
1037        created Figure (useful when superimposing Figures) to make sure
1038        they are compatible and comparable. If they are not compatible
1039        you will receive an error message.
1040
1041        Arguments:
1042            bins : (list)
1043                binning as (nx, ny)
1044            weights : (list)
1045                array of weights to assign to each entry
1046            cmap : (str, lookuptable)
1047                color map name or look up table
1048            alpha : (float)
1049                opacity of the histogram
1050            gap : (float)
1051                separation between adjacent bins as a fraction for their size
1052            scalarbar : (bool)
1053                add a scalarbar to right of the histogram
1054            like : (Figure)
1055                grab and use the same format of the given Figure (for superimposing)
1056            xlim : (list)
1057                [x0, x1] range of interest. If left to None will automatically
1058                choose the minimum or the maximum of the data range.
1059                Data outside the range are completely ignored.
1060            ylim : (list)
1061                [y0, y1] range of interest. If left to None will automatically
1062                choose the minimum or the maximum of the data range.
1063                Data outside the range are completely ignored.
1064            aspect : (float)
1065                the desired aspect ratio of the figure.
1066            title : (str)
1067                title of the plot to appear on top.
1068                If left blank some statistics will be shown.
1069            xtitle : (str)
1070                x axis title
1071            ytitle : (str)
1072                y axis title
1073            ztitle : (str)
1074                title for the scalar bar
1075            ac : (str)
1076                axes color, additional keyword for Axes can also be added
1077                using e.g. `axes=dict(xygrid=True)`
1078
1079        Examples:
1080            - [histo_2d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_a.py)
1081            - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py)
1082
1083            ![](https://vedo.embl.es/images/pyplot/histo_2D.png)
1084        """
1085        xvalues = np.asarray(xvalues)
1086        if yvalues is None:
1087            # assume [(x1,y1), (x2,y2) ...] format
1088            yvalues = xvalues[:, 1]
1089            xvalues = xvalues[:, 0]
1090        else:
1091            yvalues = np.asarray(yvalues)
1092
1093        padding = [0, 0, 0, 0]
1094
1095        if like is None and vedo.last_figure is not None:
1096            if xlim is None and ylim == (None, None) and zlim == (None, None):
1097                like = vedo.last_figure
1098
1099        if like is not None:
1100            xlim = like.xlim
1101            ylim = like.ylim
1102            aspect = like.aspect
1103            padding = like.padding
1104            if bins is None:
1105                bins = like.bins
1106        if bins is None:
1107            bins = 20
1108
1109        if isinstance(bins, int):
1110            bins = (bins, bins)
1111
1112        if utils.is_sequence(xlim):
1113            # deal with user passing eg [x0, None]
1114            _x0, _x1 = xlim
1115            if _x0 is None:
1116                _x0 = xvalues.min()
1117            if _x1 is None:
1118                _x1 = xvalues.max()
1119            xlim = [_x0, _x1]
1120
1121        if utils.is_sequence(ylim):
1122            # deal with user passing eg [x0, None]
1123            _y0, _y1 = ylim
1124            if _y0 is None:
1125                _y0 = yvalues.min()
1126            if _y1 is None:
1127                _y1 = yvalues.max()
1128            ylim = [_y0, _y1]
1129
1130        H, xedges, yedges = np.histogram2d(
1131            xvalues, yvalues, weights=weights, bins=bins, range=(xlim, ylim)
1132        )
1133
1134        xlim = np.min(xedges), np.max(xedges)
1135        ylim = np.min(yedges), np.max(yedges)
1136        dx, dy = xlim[1] - xlim[0], ylim[1] - ylim[0]
1137
1138        fig_kwargs["title"] = title
1139        fig_kwargs["xtitle"] = xtitle
1140        fig_kwargs["ytitle"] = ytitle
1141        fig_kwargs["ac"] = ac
1142
1143        self.entries = len(xvalues)
1144        self.frequencies = H
1145        self.edges = (xedges, yedges)
1146        self.mean = (xvalues.mean(), yvalues.mean())
1147        self.std = (xvalues.std(), yvalues.std())
1148        self.bins = bins  # internally used by "like"
1149
1150        ############################### stats legend as htitle
1151        addstats = False
1152        if not title:
1153            if "axes" not in fig_kwargs:
1154                addstats = True
1155                axes_opts = {}
1156                fig_kwargs["axes"] = axes_opts
1157            elif fig_kwargs["axes"] is False:
1158                pass
1159            else:
1160                axes_opts = fig_kwargs["axes"]
1161                if "htitle" not in fig_kwargs["axes"]:
1162                    addstats = True
1163
1164        if addstats:
1165            htitle = f"Entries:~~{int(self.entries)}  "
1166            htitle += f"Mean:~~{utils.precision(self.mean, 3)}  "
1167            htitle += f"STD:~~{utils.precision(self.std, 3)}  "
1168            axes_opts["htitle"] = htitle
1169            axes_opts["htitle_justify"] = "bottom-left"
1170            axes_opts["htitle_size"] = 0.0175
1171
1172        ############################################### Figure init
1173        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1174
1175        if self.yscale:
1176            ##################### the grid
1177            acts = []
1178            g = shapes.Grid(
1179                pos=[(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, 0], s=(dx, dy), res=bins[:2]
1180            )
1181            g.alpha(alpha).lw(0).wireframe(False).flat().lighting("off")
1182            g.cmap(cmap, np.ravel(H.T), on="cells", vmin=zlim[0], vmax=zlim[1])
1183            if gap:
1184                g.shrink(abs(1 - gap))
1185
1186            if scalarbar:
1187                sc = g.add_scalarbar3d(ztitle, c=ac).scalarbar
1188
1189                # print(" g.GetBounds()[0]", g.bounds()[:2])
1190                # print("sc.GetBounds()[0]",sc.GetBounds()[:2])
1191                delta = sc.GetBounds()[0] - g.bounds()[1]
1192
1193                sc_size = sc.GetBounds()[1] - sc.GetBounds()[0]
1194
1195                sc.SetOrigin(sc.GetBounds()[0], 0, 0)
1196                sc.scale([self.yscale, 1, 1])  ## prescale trick
1197                sc.shift(-delta + 0.25*sc_size*self.yscale)
1198
1199                acts.append(sc)
1200            acts.append(g)
1201
1202            self.insert(*acts, as3d=False)
1203            self.name = "Histogram2D"
1204
1205
1206#########################################################################################
1207class PlotBars(Figure):
1208    """Creates a `PlotBars(Figure)` object."""
1209
1210    def __init__(
1211        self,
1212        data,
1213        errors=False,
1214        logscale=False,
1215        fill=True,
1216        gap=0.02,
1217        radius=0.05,
1218        c="olivedrab",
1219        alpha=1,
1220        texture="",
1221        outline=False,
1222        lw=2,
1223        lc="k",
1224        # Figure and axes options:
1225        like=None,
1226        xlim=(None, None),
1227        ylim=(0, None),
1228        aspect=4 / 3,
1229        padding=(0.025, 0.025, 0, 0.05),
1230        #
1231        title="",
1232        xtitle=" ",
1233        ytitle=" ",
1234        ac="k",
1235        grid=False,
1236        ztolerance=None,
1237        **fig_kwargs,
1238    ):
1239        """
1240        Input must be in format `[counts, labels, colors, edges]`.
1241        Either or both `edges` and `colors` are optional and can be omitted.
1242
1243        Use keyword `like=...` if you want to use the same format of a previously
1244        created Figure (useful when superimposing Figures) to make sure
1245        they are compatible and comparable. If they are not compatible
1246        you will receive an error message.
1247
1248        Arguments:
1249            errors : (bool)
1250                show error bars
1251            logscale : (bool)
1252                use logscale on y-axis
1253            fill : (bool)
1254                fill bars with solid color `c`
1255            gap : (float)
1256                leave a small space btw bars
1257            radius : (float)
1258                border radius of the top of the histogram bar. Default value is 0.1.
1259            texture : (str)
1260                url or path to an image to be used as texture for the bin
1261            outline : (bool)
1262                show outline of the bins
1263            xtitle : (str)
1264                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1265            ytitle : (str)
1266                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1267            ac : (str)
1268                axes color
1269            padding : (float, list)
1270                keep a padding space from the axes (as a fraction of the axis size).
1271                This can be a list of four numbers.
1272            aspect : (float)
1273                the desired aspect ratio of the figure. Default is 4/3.
1274            grid : (bool)
1275                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1276
1277        Examples:
1278            - [plot_bars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_bars.py)
1279
1280               ![](https://vedo.embl.es/images/pyplot/plot_bars.png)
1281        """
1282        ndata = len(data)
1283        if ndata == 4:
1284            counts, xlabs, cols, edges = data
1285        elif ndata == 3:
1286            counts, xlabs, cols = data
1287            edges = np.array(range(len(counts) + 1)) + 0.5
1288        elif ndata == 2:
1289            counts, xlabs = data
1290            edges = np.array(range(len(counts) + 1)) + 0.5
1291            cols = [c] * len(counts)
1292        else:
1293            m = "barplot error: data must be given as [counts, labels, colors, edges] not\n"
1294            vedo.logger.error(m + f" {data}\n     bin edges and colors are optional.")
1295            raise RuntimeError()
1296
1297        # sanity checks
1298        assert len(counts) == len(xlabs)
1299        assert len(counts) == len(cols)
1300        assert len(counts) == len(edges) - 1
1301
1302        counts = np.asarray(counts)
1303        edges = np.asarray(edges)
1304
1305        if logscale:
1306            counts = np.log10(counts + 1)
1307            if ytitle == " ":
1308                ytitle = "log_10 (counts+1)"
1309
1310        if like is None and vedo.last_figure is not None:
1311            if xlim == (None, None) and ylim == (0, None):
1312                like = vedo.last_figure
1313
1314        if like is not None:
1315            xlim = like.xlim
1316            ylim = like.ylim
1317            aspect = like.aspect
1318            padding = like.padding
1319
1320        if utils.is_sequence(xlim):
1321            # deal with user passing eg [x0, None]
1322            _x0, _x1 = xlim
1323            if _x0 is None:
1324                _x0 = np.min(edges)
1325            if _x1 is None:
1326                _x1 = np.max(edges)
1327            xlim = [_x0, _x1]
1328
1329        x0, x1 = np.min(edges), np.max(edges)
1330        y0, y1 = ylim[0], np.max(counts)
1331
1332        if like is None:
1333            ylim = list(ylim)
1334            if xlim is None:
1335                xlim = [x0, x1]
1336            if ylim[1] is None:
1337                ylim[1] = y1
1338            if ylim[0] != 0:
1339                ylim[0] = y0
1340
1341        fig_kwargs["title"] = title
1342        fig_kwargs["xtitle"] = xtitle
1343        fig_kwargs["ytitle"] = ytitle
1344        fig_kwargs["ac"] = ac
1345        fig_kwargs["ztolerance"] = ztolerance
1346        fig_kwargs["grid"] = grid
1347
1348        centers = (edges[0:-1] + edges[1:]) / 2
1349        binsizes = (centers - edges[0:-1]) * 2
1350
1351        if "axes" not in fig_kwargs:
1352            fig_kwargs["axes"] = {}
1353
1354        _xlabs = []
1355        for center, xlb in zip(centers, xlabs):
1356            _xlabs.append([center, str(xlb)])
1357        fig_kwargs["axes"]["x_values_and_labels"] = _xlabs
1358
1359        ############################################### Figure
1360        self.statslegend = ""
1361        self.edges = edges
1362        self.centers = centers
1363        self.bins = edges  # internal used by "like"
1364        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1365        if not self.yscale:
1366            return
1367
1368        rs = []
1369        maxheigth = 0
1370        if fill:  #####################
1371            if outline:
1372                gap = 0
1373
1374            for i in range(len(centers)):
1375                binsize = binsizes[i]
1376                p0 = (edges[i] + gap * binsize, 0, 0)
1377                p1 = (edges[i + 1] - gap * binsize, counts[i], 0)
1378
1379                if radius:
1380                    rds = np.array([0, 0, radius, radius])
1381                    p1_yscaled = [p1[0], p1[1] * self.yscale, 0]
1382                    r = shapes.Rectangle(p0, p1_yscaled, radius=rds * binsize, res=6)
1383                    r.scale([1, 1 / self.yscale, 1])
1384                    r.radius = None  # so it doesnt get recreated and rescaled by insert()
1385                else:
1386                    r = shapes.Rectangle(p0, p1)
1387
1388                if texture:
1389                    r.texture(texture)
1390                    c = "w"
1391
1392                r.actor.PickableOff()
1393                maxheigth = max(maxheigth, p1[1])
1394                if c in colors.cmaps_names:
1395                    col = colors.color_map((p0[0] + p1[0]) / 2, c, edges[0], edges[-1])
1396                else:
1397                    col = cols[i]
1398                r.color(col).alpha(alpha).lighting("off")
1399                r.name = f"bar_{i}"
1400                r.z(self.ztolerance)
1401                rs.append(r)
1402
1403        elif outline:  #####################
1404            lns = [[edges[0], 0, 0]]
1405            for i in range(len(centers)):
1406                lns.append([edges[i], counts[i], 0])
1407                lns.append([edges[i + 1], counts[i], 0])
1408                maxheigth = max(maxheigth, counts[i])
1409            lns.append([edges[-1], 0, 0])
1410            outl = shapes.Line(lns, c=lc, alpha=alpha, lw=lw).z(self.ztolerance)
1411            outl.name = f"bar_outline_{i}"
1412            rs.append(outl)
1413
1414        if errors:  #####################
1415            for x, f in centers:
1416                err = np.sqrt(f)
1417                el = shapes.Line([x, f - err / 2, 0], [x, f + err / 2, 0], c=lc, alpha=alpha, lw=lw)
1418                el.z(self.ztolerance * 2)
1419                rs.append(el)
1420
1421        self.insert(*rs, as3d=False)
1422        self.name = "PlotBars"
1423
1424
1425#########################################################################################
1426class PlotXY(Figure):
1427    """Creates a `PlotXY(Figure)` object."""
1428
1429    def __init__(
1430        self,
1431        #
1432        data,
1433        xerrors=None,
1434        yerrors=None,
1435        #
1436        lw=2,
1437        lc=None,
1438        la=1,
1439        dashed=False,
1440        splined=False,
1441        #
1442        elw=2,  # error line width
1443        ec=None,  # error line or band color
1444        error_band=False,  # errors in x are ignored
1445        #
1446        marker="",
1447        ms=None,
1448        mc=None,
1449        ma=None,
1450        # Figure and axes options:
1451        like=None,
1452        xlim=None,
1453        ylim=(None, None),
1454        aspect=4 / 3,
1455        padding=0.05,
1456        #
1457        title="",
1458        xtitle=" ",
1459        ytitle=" ",
1460        ac="k",
1461        grid=True,
1462        ztolerance=None,
1463        label="",
1464        **fig_kwargs,
1465    ):
1466        """
1467        Arguments:
1468            xerrors : (bool)
1469                show error bars associated to each point in x
1470            yerrors : (bool)
1471                show error bars associated to each point in y
1472            lw : (int)
1473                width of the line connecting points in pixel units.
1474                Set it to 0 to remove the line.
1475            lc : (str)
1476                line color
1477            la : (float)
1478                line "alpha", opacity of the line
1479            dashed : (bool)
1480                draw a dashed line instead of a continuous line
1481            splined : (bool)
1482                spline the line joining the point as a countinous curve
1483            elw : (int)
1484                width of error bar lines in units of pixels
1485            ec : (color)
1486                color of error bar, by default the same as marker color
1487            error_band : (bool)
1488                represent errors on y as a filled error band.
1489                Use `ec` keyword to modify its color.
1490            marker : (str, int)
1491                use a marker for the data points
1492            ms : (float)
1493                marker size
1494            mc : (color)
1495                color of the marker
1496            ma : (float)
1497                opacity of the marker
1498            xlim : (list)
1499                set limits to the range for the x variable
1500            ylim : (list)
1501                set limits to the range for the y variable
1502            aspect : (float, str)
1503                Desired aspect ratio.
1504                Use `aspect="equal"` to force the same units in x and y.
1505                Scaling factor is saved in Figure.yscale.
1506            padding : (float, list)
1507                keep a padding space from the axes (as a fraction of the axis size).
1508                This can be a list of four numbers.
1509            title : (str)
1510                title to appear on the top of the frame, like a header.
1511            xtitle : (str)
1512                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1513            ytitle : (str)
1514                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1515            ac : (str)
1516                axes color
1517            grid : (bool)
1518                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1519            ztolerance : (float)
1520                a tolerance factor to superimpose objects (along the z-axis).
1521
1522        Example:
1523            ```python
1524            import numpy as np
1525            from vedo.pyplot import plot
1526            x = np.arange(0, np.pi, 0.1)
1527            fig = plot(x, np.sin(2*x), 'r0-', aspect='equal')
1528            fig+= plot(x, np.cos(2*x), 'blue4 o-', like=fig)
1529            fig.show().close()
1530            ```
1531            ![](https://vedo.embl.es/images/feats/plotxy.png)
1532
1533        Examples:
1534            - [plot_errbars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errbars.py)
1535            - [plot_errband.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errband.py)
1536            - [plot_pip.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_pip.py)
1537
1538                ![](https://vedo.embl.es/images/pyplot/plot_pip.png)
1539
1540            - [scatter1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter1.py)
1541            - [scatter2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter2.py)
1542
1543                ![](https://vedo.embl.es/images/pyplot/scatter2.png)
1544        """
1545        line = False
1546        if lw > 0:
1547            line = True
1548        if marker == "" and not line and not splined:
1549            marker = "o"
1550
1551        if like is None and vedo.last_figure is not None:
1552            if xlim is None and ylim == (None, None):
1553                like = vedo.last_figure
1554
1555        if like is not None:
1556            xlim = like.xlim
1557            ylim = like.ylim
1558            aspect = like.aspect
1559            padding = like.padding
1560
1561        if utils.is_sequence(xlim):
1562            # deal with user passing eg [x0, None]
1563            _x0, _x1 = xlim
1564            if _x0 is None:
1565                _x0 = data.min()
1566            if _x1 is None:
1567                _x1 = data.max()
1568            xlim = [_x0, _x1]
1569
1570        # purge NaN from data
1571        validIds = np.all(np.logical_not(np.isnan(data)))
1572        data = np.array(data[validIds])[0]
1573
1574        fig_kwargs["title"] = title
1575        fig_kwargs["xtitle"] = xtitle
1576        fig_kwargs["ytitle"] = ytitle
1577        fig_kwargs["ac"] = ac
1578        fig_kwargs["ztolerance"] = ztolerance
1579        fig_kwargs["grid"] = grid
1580
1581        x0, y0 = np.min(data, axis=0)
1582        x1, y1 = np.max(data, axis=0)
1583        if xerrors is not None and not error_band:
1584            x0 = min(data[:, 0] - xerrors)
1585            x1 = max(data[:, 0] + xerrors)
1586        if yerrors is not None:
1587            y0 = min(data[:, 1] - yerrors)
1588            y1 = max(data[:, 1] + yerrors)
1589
1590        if like is None:
1591            if xlim is None:
1592                xlim = (None, None)
1593            xlim = list(xlim)
1594            if xlim[0] is None:
1595                xlim[0] = x0
1596            if xlim[1] is None:
1597                xlim[1] = x1
1598            ylim = list(ylim)
1599            if ylim[0] is None:
1600                ylim[0] = y0
1601            if ylim[1] is None:
1602                ylim[1] = y1
1603
1604        self.entries = len(data)
1605        self.mean = data.mean()
1606        self.std = data.std()
1607        
1608        self.ztolerance = 0
1609        
1610        ######### the PlotXY marker
1611        # fall back solutions logic for colors
1612        if "c" in fig_kwargs:
1613            if mc is None:
1614                mc = fig_kwargs["c"]
1615            if lc is None:
1616                lc = fig_kwargs["c"]
1617            if ec is None:
1618                ec = fig_kwargs["c"]
1619        if lc is None:
1620            lc = "k"
1621        if mc is None:
1622            mc = lc
1623        if ma is None:
1624            ma = la
1625        if ec is None:
1626            if mc is None:
1627                ec = lc
1628            else:
1629                ec = mc
1630
1631        if label:
1632            nlab = LabelData()
1633            nlab.text = label
1634            nlab.tcolor = ac
1635            nlab.marker = marker
1636            if line and marker == "":
1637                nlab.marker = "-"
1638            nlab.mcolor = mc
1639            fig_kwargs["label"] = nlab
1640
1641        ############################################### Figure init
1642        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1643
1644        if not self.yscale:
1645            return
1646
1647        acts = []
1648
1649        ######### the PlotXY Line or Spline
1650        if dashed:
1651            l = shapes.DashedLine(data, c=lc, alpha=la, lw=lw)
1652            acts.append(l)
1653        elif splined:
1654            l = shapes.KSpline(data).lw(lw).c(lc).alpha(la)
1655            acts.append(l)
1656        elif line:
1657            l = shapes.Line(data, c=lc, alpha=la).lw(lw)
1658            acts.append(l)
1659
1660        if marker:
1661
1662            pts = np.c_[data, np.zeros(len(data))]
1663
1664            if utils.is_sequence(ms):
1665                ### variable point size
1666                mk = shapes.Marker(marker, s=1)
1667                mk.scale([1, 1 / self.yscale, 1])
1668                msv = np.zeros_like(pts)
1669                msv[:, 0] = ms
1670                marked = shapes.Glyph(
1671                    pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
1672                )
1673            else:
1674                ### fixed point size
1675                if ms is None:
1676                    ms = (xlim[1] - xlim[0]) / 100.0
1677
1678                if utils.is_sequence(mc):
1679                    fig_kwargs["marker_color"] = None  # for labels
1680                    mk = shapes.Marker(marker, s=ms)
1681                    mk.scale([1, 1 / self.yscale, 1])
1682                    msv = np.zeros_like(pts)
1683                    msv[:, 0] = 1
1684                    marked = shapes.Glyph(
1685                        pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
1686                    )
1687                else:
1688                    mk = shapes.Marker(marker, s=ms)
1689                    mk.scale([1, 1 / self.yscale, 1])
1690                    marked = shapes.Glyph(pts, mk, c=mc)
1691
1692            marked.name = "Marker"
1693            marked.alpha(ma)
1694            marked.z(3 * self.ztolerance)
1695            acts.append(marked)
1696
1697        ######### the PlotXY marker errors
1698        ztol = self.ztolerance
1699
1700        if error_band:
1701            yerrors = np.abs(yerrors)
1702            du = np.array(data)
1703            dd = np.array(data)
1704            du[:, 1] += yerrors
1705            dd[:, 1] -= yerrors
1706            if splined:
1707                res = len(data) * 20
1708                band1 = shapes.KSpline(du, res=res)
1709                band2 = shapes.KSpline(dd, res=res)
1710                band = shapes.Ribbon(band1, band2, res=(res, 2))
1711            else:
1712                dd = list(reversed(dd.tolist()))
1713                band = shapes.Line(du.tolist() + dd, closed=True)
1714                band.triangulate().lw(0)
1715            if ec is None:
1716                band.c(lc)
1717            else:
1718                band.c(ec)
1719            band.lighting("off").alpha(la).z(ztol / 20)
1720            acts.append(band)
1721
1722        else:
1723
1724            ## xerrors
1725            if xerrors is not None:
1726                if len(xerrors) == len(data):
1727                    errs = []
1728                    for i, val in enumerate(data):
1729                        xval, yval = val
1730                        xerr = xerrors[i] / 2
1731                        el = shapes.Line((xval - xerr, yval, ztol), (xval + xerr, yval, ztol))
1732                        el.lw(elw)
1733                        errs.append(el)
1734                    mxerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol)
1735                    acts.append(mxerrs)
1736                else:
1737                    vedo.logger.error("in PlotXY(xerrors=...): mismatch in array length")
1738
1739            ## yerrors
1740            if yerrors is not None:
1741                if len(yerrors) == len(data):
1742                    errs = []
1743                    for i, val in enumerate(data):
1744                        xval, yval = val
1745                        yerr = yerrors[i]
1746                        el = shapes.Line((xval, yval - yerr, ztol), (xval, yval + yerr, ztol))
1747                        el.lw(elw)
1748                        errs.append(el)
1749                    myerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol)
1750                    acts.append(myerrs)
1751                else:
1752                    vedo.logger.error("in PlotXY(yerrors=...): mismatch in array length")
1753
1754        self.insert(*acts, as3d=False)
1755        self.name = "PlotXY"
1756
1757
1758def plot(*args, **kwargs):
1759    """
1760    Draw a 2D line plot, or scatter plot, of variable x vs variable y.
1761    Input format can be either `[allx], [allx, ally] or [(x1,y1), (x2,y2), ...]`
1762
1763    Use `like=...` if you want to use the same format of a previously
1764    created Figure (useful when superimposing Figures) to make sure
1765    they are compatible and comparable. If they are not compatible
1766    you will receive an error message.
1767
1768    Arguments:
1769        xerrors : (bool)
1770            show error bars associated to each point in x
1771        yerrors : (bool)
1772            show error bars associated to each point in y
1773        lw : (int)
1774            width of the line connecting points in pixel units.
1775            Set it to 0 to remove the line.
1776        lc : (str)
1777            line color
1778        la : (float)
1779            line "alpha", opacity of the line
1780        dashed : (bool)
1781            draw a dashed line instead of a continuous line
1782        splined : (bool)
1783            spline the line joining the point as a countinous curve
1784        elw : (int)
1785            width of error bar lines in units of pixels
1786        ec : (color)
1787            color of error bar, by default the same as marker color
1788        error_band : (bool)
1789            represent errors on y as a filled error band.
1790            Use `ec` keyword to modify its color.
1791        marker : (str, int)
1792            use a marker for the data points
1793        ms : (float)
1794            marker size
1795        mc : (color)
1796            color of the marker
1797        ma : (float)
1798            opacity of the marker
1799        xlim : (list)
1800            set limits to the range for the x variable
1801        ylim : (list)
1802            set limits to the range for the y variable
1803        aspect : (float)
1804            Desired aspect ratio.
1805            If None, it is automatically calculated to get a reasonable aspect ratio.
1806            Scaling factor is saved in Figure.yscale
1807        padding : (float, list)
1808            keep a padding space from the axes (as a fraction of the axis size).
1809            This can be a list of four numbers.
1810        title : (str)
1811            title to appear on the top of the frame, like a header.
1812        xtitle : (str)
1813            title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1814        ytitle : (str)
1815            title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1816        ac : (str)
1817            axes color
1818        grid : (bool)
1819            show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1820        ztolerance : (float)
1821            a tolerance factor to superimpose objects (along the z-axis).
1822
1823    Example:
1824        ```python
1825        import numpy as np
1826        from vedo.pyplot import plot
1827        from vedo import settings
1828        settings.remember_last_figure_format = True #############
1829        x = np.linspace(0, 6.28, num=50)
1830        fig = plot(np.sin(x), 'r-')
1831        fig+= plot(np.cos(x), 'bo-') # no need to specify like=...
1832        fig.show().close()
1833        ```
1834        <img src="https://user-images.githubusercontent.com/32848391/74363882-c3638300-4dcb-11ea-8a78-eb492ad9711f.png" width="600">
1835
1836    Examples:
1837        - [plot_errbars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errbars.py)
1838        - [plot_errband.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errband.py)
1839        - [plot_pip.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_pip.py)
1840
1841            ![](https://vedo.embl.es/images/pyplot/plot_pip.png)
1842
1843        - [scatter1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter1.py)
1844        - [scatter2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter2.py)
1845
1846
1847
1848    -------------------------------------------------------------------------
1849    .. note:: mode="bar"
1850
1851    Creates a `PlotBars(Figure)` object.
1852
1853    Input must be in format `[counts, labels, colors, edges]`.
1854    Either or both `edges` and `colors` are optional and can be omitted.
1855
1856    Arguments:
1857        errors : (bool)
1858            show error bars
1859        logscale : (bool)
1860            use logscale on y-axis
1861        fill : (bool)
1862            fill bars with solid color `c`
1863        gap : (float)
1864            leave a small space btw bars
1865        radius : (float)
1866            border radius of the top of the histogram bar. Default value is 0.1.
1867        texture : (str)
1868            url or path to an image to be used as texture for the bin
1869        outline : (bool)
1870            show outline of the bins
1871        xtitle : (str)
1872            title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1873        ytitle : (str)
1874            title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1875        ac : (str)
1876            axes color
1877        padding : (float, list)
1878            keep a padding space from the axes (as a fraction of the axis size).
1879            This can be a list of four numbers.
1880        aspect : (float)
1881            the desired aspect ratio of the figure. Default is 4/3.
1882        grid : (bool)
1883            show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1884
1885    Examples:
1886        - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
1887        - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
1888        - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
1889        - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)
1890
1891        ![](https://vedo.embl.es/images/pyplot/histo_1D.png)
1892
1893
1894    ----------------------------------------------------------------------
1895    .. note:: 2D functions
1896
1897    If input is an external function or a formula, draw the surface
1898    representing the function `f(x,y)`.
1899
1900    Arguments:
1901        x : (float)
1902            x range of values
1903        y : (float)
1904            y range of values
1905        zlimits : (float)
1906            limit the z range of the independent variable
1907        zlevels : (int)
1908            will draw the specified number of z-levels contour lines
1909        show_nan : (bool)
1910            show where the function does not exist as red points
1911        bins : (list)
1912            number of bins in x and y
1913
1914    Examples:
1915        - [plot_fxy1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy1.py)
1916
1917            ![](https://vedo.embl.es/images/pyplot/plot_fxy.png)
1918        
1919        - [plot_fxy2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy2.py)
1920
1921
1922    --------------------------------------------------------------------
1923    .. note:: mode="complex"
1924
1925    If `mode='complex'` draw the real value of the function and color map the imaginary part.
1926
1927    Arguments:
1928        cmap : (str)
1929            diverging color map (white means `imag(z)=0`)
1930        lw : (float)
1931            line with of the binning
1932        bins : (list)
1933            binning in x and y
1934
1935    Examples:
1936        - [plot_fxy.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy.py)
1937
1938            ![](https://user-images.githubusercontent.com/32848391/73392962-1709a300-42db-11ea-9278-30c9d6e5eeaa.png)
1939
1940
1941    --------------------------------------------------------------------
1942    .. note:: mode="polar"
1943
1944    If `mode='polar'` input arrays are interpreted as a list of polar angles and radii.
1945    Build a polar (radar) plot by joining the set of points in polar coordinates.
1946
1947    Arguments:
1948        title : (str)
1949            plot title
1950        tsize : (float)
1951            title size
1952        bins : (int)
1953            number of bins in phi
1954        r1 : (float)
1955            inner radius
1956        r2 : (float)
1957            outer radius
1958        lsize : (float)
1959            label size
1960        c : (color)
1961            color of the line
1962        ac : (color)
1963            color of the frame and labels
1964        alpha : (float)
1965            opacity of the frame
1966        ps : (int)
1967            point size in pixels, if ps=0 no point is drawn
1968        lw : (int)
1969            line width in pixels, if lw=0 no line is drawn
1970        deg : (bool)
1971            input array is in degrees
1972        vmax : (float)
1973            normalize radius to this maximum value
1974        fill : (bool)
1975            fill convex area with solid color
1976        splined : (bool)
1977            interpolate the set of input points
1978        show_disc : (bool)
1979            draw the outer ring axis
1980        nrays : (int)
1981            draw this number of axis rays (continuous and dashed)
1982        show_lines : (bool)
1983            draw lines to the origin
1984        show_angles : (bool)
1985            draw angle values
1986
1987    Examples:
1988        - [histo_polar.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_polar.py)
1989
1990            ![](https://user-images.githubusercontent.com/32848391/64992590-7fc82400-d8d4-11e9-9c10-795f4756a73f.png)
1991
1992
1993    --------------------------------------------------------------------
1994    .. note:: mode="spheric"
1995
1996    If `mode='spheric'` input must be an external function rho(theta, phi).
1997    A surface is created in spherical coordinates.
1998
1999    Return an `Figure(Assembly)` of 2 objects: the unit
2000    sphere (in wireframe representation) and the surface `rho(theta, phi)`.
2001
2002    Arguments:
2003        rfunc : function
2004            handle to a user defined function `rho(theta, phi)`.
2005        normalize : (bool)
2006            scale surface to fit inside the unit sphere
2007        res : (int)
2008            grid resolution of the unit sphere
2009        scalarbar : (bool)
2010            add a 3D scalarbar to the plot for radius
2011        c : (color)
2012            color of the unit sphere
2013        alpha : (float)
2014            opacity of the unit sphere
2015        cmap : (str)
2016            color map for the surface
2017
2018    Examples:
2019        - [plot_spheric.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_spheric.py)
2020
2021            ![](https://vedo.embl.es/images/pyplot/plot_spheric.png)
2022    """
2023    mode = kwargs.pop("mode", "")
2024    if "spher" in mode:
2025        return _plot_spheric(args[0], **kwargs)
2026
2027    if "bar" in mode:
2028        return PlotBars(args[0], **kwargs)
2029
2030    if isinstance(args[0], str) or "function" in str(type(args[0])):
2031        if "complex" in mode:
2032            return _plot_fz(args[0], **kwargs)
2033        return _plot_fxy(args[0], **kwargs)
2034
2035    # grab the matplotlib-like options
2036    optidx = None
2037    for i, a in enumerate(args):
2038        if i > 0 and isinstance(a, str):
2039            optidx = i
2040            break
2041    if optidx:
2042        opts = args[optidx].replace(" ", "")
2043        if "--" in opts:
2044            opts = opts.replace("--", "")
2045            kwargs["dashed"] = True
2046        elif "-" in opts:
2047            opts = opts.replace("-", "")
2048        else:
2049            kwargs["lw"] = 0
2050
2051        symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"]
2052
2053        allcols = list(colors.colors.keys()) + list(colors.color_nicks.keys())
2054        for cc in allcols:
2055            if cc == "o":
2056                continue
2057            if cc in opts:
2058                opts = opts.replace(cc, "")
2059                kwargs["lc"] = cc
2060                kwargs["mc"] = cc
2061                break
2062
2063        for ss in symbs:
2064            if ss in opts:
2065                opts = opts.replace(ss, "", 1)
2066                kwargs["marker"] = ss
2067                break
2068
2069        opts.replace(" ", "")
2070        if opts:
2071            vedo.logger.error(f"in plot(), could not understand option(s): {opts}")
2072
2073    if optidx == 1 or optidx is None:
2074        if utils.is_sequence(args[0][0]) and len(args[0][0]) > 1:
2075            # print('------------- case 1', 'plot([(x,y),..])')
2076            data = np.asarray(args[0])  # (x,y)
2077            x = np.asarray(data[:, 0])
2078            y = np.asarray(data[:, 1])
2079
2080        elif len(args) == 1 or optidx == 1:
2081            # print('------------- case 2', 'plot(x)')
2082            if "pandas" in str(type(args[0])):
2083                if "ytitle" not in kwargs:
2084                    kwargs.update({"ytitle": args[0].name.replace("_", "_ ")})
2085            x = np.linspace(0, len(args[0]), num=len(args[0]))
2086            y = np.asarray(args[0]).ravel()
2087
2088        elif utils.is_sequence(args[1]):
2089            # print('------------- case 3', 'plot(allx,ally)',str(type(args[0])))
2090            if "pandas" in str(type(args[0])):
2091                if "xtitle" not in kwargs:
2092                    kwargs.update({"xtitle": args[0].name.replace("_", "_ ")})
2093            if "pandas" in str(type(args[1])):
2094                if "ytitle" not in kwargs:
2095                    kwargs.update({"ytitle": args[1].name.replace("_", "_ ")})
2096            x = np.asarray(args[0]).ravel()
2097            y = np.asarray(args[1]).ravel()
2098
2099        elif utils.is_sequence(args[0]) and utils.is_sequence(args[0][0]):
2100            # print('------------- case 4', 'plot([allx,ally])')
2101            x = np.asarray(args[0][0]).ravel()
2102            y = np.asarray(args[0][1]).ravel()
2103
2104    elif optidx == 2:
2105        # print('------------- case 5', 'plot(x,y)')
2106        x = np.asarray(args[0]).ravel()
2107        y = np.asarray(args[1]).ravel()
2108
2109    else:
2110        vedo.logger.error(f"plot(): Could not understand input arguments {args}")
2111        return None
2112
2113    if "polar" in mode:
2114        return _plot_polar(np.c_[x, y], **kwargs)
2115
2116    return PlotXY(np.c_[x, y], **kwargs)
2117
2118
2119def histogram(*args, **kwargs):
2120    """
2121    Histogramming for 1D and 2D data arrays.
2122
2123    This is meant as a convenience function that creates the appropriate object
2124    based on the shape of the provided input data.
2125
2126    Use keyword `like=...` if you want to use the same format of a previously
2127    created Figure (useful when superimposing Figures) to make sure
2128    they are compatible and comparable. If they are not compatible
2129    you will receive an error message.
2130
2131    -------------------------------------------------------------------------
2132    .. note:: default mode, for 1D arrays
2133
2134    Creates a `Histogram1D(Figure)` object.
2135
2136    Arguments:
2137        weights : (list)
2138            An array of weights, of the same shape as `data`. Each value in `data`
2139            only contributes its associated weight towards the bin count (instead of 1).
2140        bins : (int)
2141            number of bins
2142        vrange : (list)
2143            restrict the range of the histogram
2144        density : (bool)
2145            normalize the area to 1 by dividing by the nr of entries and bin size
2146        logscale : (bool)
2147            use logscale on y-axis
2148        fill : (bool)
2149            fill bars with solid color `c`
2150        gap : (float)
2151            leave a small space btw bars
2152        radius : (float)
2153            border radius of the top of the histogram bar. Default value is 0.1.
2154        texture : (str)
2155            url or path to an image to be used as texture for the bin
2156        outline : (bool)
2157            show outline of the bins
2158        errors : (bool)
2159            show error bars
2160        xtitle : (str)
2161            title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
2162        ytitle : (str)
2163            title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
2164        padding : (float, list)
2165            keep a padding space from the axes (as a fraction of the axis size).
2166            This can be a list of four numbers.
2167        aspect : (float)
2168            the desired aspect ratio of the histogram. Default is 4/3.
2169        grid : (bool)
2170            show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
2171        ztolerance : (float)
2172            a tolerance factor to superimpose objects (along the z-axis).
2173
2174    Examples:
2175        - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
2176        - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
2177        - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
2178        - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)
2179
2180        ![](https://vedo.embl.es/images/pyplot/histo_1D.png)
2181
2182
2183    -------------------------------------------------------------------------
2184    .. note:: default mode, for 2D arrays
2185
2186    Input data formats `[(x1,x2,..), (y1,y2,..)] or [(x1,y1), (x2,y2),..]`
2187    are both valid.
2188
2189    Arguments:
2190        bins : (list)
2191            binning as (nx, ny)
2192        weights : (list)
2193            array of weights to assign to each entry
2194        cmap : (str, lookuptable)
2195            color map name or look up table
2196        alpha : (float)
2197            opacity of the histogram
2198        gap : (float)
2199            separation between adjacent bins as a fraction for their size.
2200            Set gap=-1 to generate a quad surface.
2201        scalarbar : (bool)
2202            add a scalarbar to right of the histogram
2203        like : (Figure)
2204            grab and use the same format of the given Figure (for superimposing)
2205        xlim : (list)
2206            [x0, x1] range of interest. If left to None will automatically
2207            choose the minimum or the maximum of the data range.
2208            Data outside the range are completely ignored.
2209        ylim : (list)
2210            [y0, y1] range of interest. If left to None will automatically
2211            choose the minimum or the maximum of the data range.
2212            Data outside the range are completely ignored.
2213        aspect : (float)
2214            the desired aspect ratio of the figure.
2215        title : (str)
2216            title of the plot to appear on top.
2217            If left blank some statistics will be shown.
2218        xtitle : (str)
2219            x axis title
2220        ytitle : (str)
2221            y axis title
2222        ztitle : (str)
2223            title for the scalar bar
2224        ac : (str)
2225            axes color, additional keyword for Axes can also be added
2226            using e.g. `axes=dict(xygrid=True)`
2227
2228    Examples:
2229        - [histo_2d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_a.py)
2230        - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py)
2231
2232        ![](https://vedo.embl.es/images/pyplot/histo_2D.png)
2233
2234
2235    -------------------------------------------------------------------------
2236    .. note:: mode="3d"
2237
2238    If `mode='3d'`, build a 2D histogram as 3D bars from a list of x and y values.
2239
2240    Arguments:
2241        xtitle : (str)
2242            x axis title
2243        bins : (int)
2244            nr of bins for the smaller range in x or y
2245        vrange : (list)
2246            range in x and y in format `[(xmin,xmax), (ymin,ymax)]`
2247        norm : (float)
2248            sets a scaling factor for the z axis (frequency axis)
2249        fill : (bool)
2250            draw solid hexagons
2251        cmap : (str)
2252            color map name for elevation
2253        gap : (float)
2254            keep a internal empty gap between bins [0,1]
2255        zscale : (float)
2256            rescale the (already normalized) zaxis for visual convenience
2257
2258    Examples:
2259        - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/histo_2d_b.py)
2260
2261
2262    -------------------------------------------------------------------------
2263    .. note:: mode="hexbin"
2264
2265    If `mode='hexbin'`, build a hexagonal histogram from a list of x and y values.
2266
2267    Arguments:
2268        xtitle : (str)
2269            x axis title
2270        bins : (int)
2271            nr of bins for the smaller range in x or y
2272        vrange : (list)
2273            range in x and y in format `[(xmin,xmax), (ymin,ymax)]`
2274        norm : (float)
2275            sets a scaling factor for the z axis (frequency axis)
2276        fill : (bool)
2277            draw solid hexagons
2278        cmap : (str)
2279            color map name for elevation
2280
2281    Examples:
2282        - [histo_hexagonal.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_hexagonal.py)
2283
2284        ![](https://vedo.embl.es/images/pyplot/histo_hexagonal.png)
2285
2286
2287    -------------------------------------------------------------------------
2288    .. note:: mode="polar"
2289
2290    If `mode='polar'` assume input is polar coordinate system (rho, theta):
2291
2292    Arguments:
2293        weights : (list)
2294            Array of weights, of the same shape as the input.
2295            Each value only contributes its associated weight towards the bin count (instead of 1).
2296        title : (str)
2297            histogram title
2298        tsize : (float)
2299            title size
2300        bins : (int)
2301            number of bins in phi
2302        r1 : (float)
2303            inner radius
2304        r2 : (float)
2305            outer radius
2306        phigap : (float)
2307            gap angle btw 2 radial bars, in degrees
2308        rgap : (float)
2309            gap factor along radius of numeric angle labels
2310        lpos : (float)
2311            label gap factor along radius
2312        lsize : (float)
2313            label size
2314        c : (color)
2315            color of the histogram bars, can be a list of length `bins`
2316        bc : (color)
2317            color of the frame and labels
2318        alpha : (float)
2319            opacity of the frame
2320        cmap : (str)
2321            color map name
2322        deg : (bool)
2323            input array is in degrees
2324        vmin : (float)
2325            minimum value of the radial axis
2326        vmax : (float)
2327            maximum value of the radial axis
2328        labels : (list)
2329            list of labels, must be of length `bins`
2330        show_disc : (bool)
2331            show the outer ring axis
2332        nrays : (int)
2333            draw this number of axis rays (continuous and dashed)
2334        show_lines : (bool)
2335            show lines to the origin
2336        show_angles : (bool)
2337            show angular values
2338        show_errors : (bool)
2339            show error bars
2340
2341    Examples:
2342        - [histo_polar.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_polar.py)
2343
2344        ![](https://vedo.embl.es/images/pyplot/histo_polar.png)
2345
2346
2347    -------------------------------------------------------------------------
2348    .. note:: mode="spheric"
2349
2350    If `mode='spheric'`, build a histogram from list of theta and phi values.
2351
2352    Arguments:
2353        rmax : (float)
2354            maximum radial elevation of bin
2355        res : (int)
2356            sphere resolution
2357        cmap : (str)
2358            color map name
2359        lw : (int)
2360            line width of the bin edges
2361
2362    Examples:
2363        - [histo_spheric.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_spheric.py)
2364
2365        ![](https://vedo.embl.es/images/pyplot/histo_spheric.png)
2366    """
2367    mode = kwargs.pop("mode", "")
2368    if len(args) == 2:  # x, y
2369
2370        if "spher" in mode:
2371            return _histogram_spheric(args[0], args[1], **kwargs)
2372
2373        if "hex" in mode:
2374            return _histogram_hex_bin(args[0], args[1], **kwargs)
2375
2376        if "3d" in mode.lower():
2377            return _histogram_quad_bin(args[0], args[1], **kwargs)
2378
2379        return Histogram2D(args[0], args[1], **kwargs)
2380
2381    elif len(args) == 1:
2382
2383        if isinstance(args[0], vedo.Volume):
2384            data = args[0].pointdata[0]
2385        elif isinstance(args[0], vedo.Points):
2386            pd0 = args[0].pointdata[0]
2387            if pd0 is not None:
2388                data = pd0.ravel()
2389            else:
2390                data = args[0].celldata[0].ravel()
2391        else:
2392            try:
2393                if "pandas" in str(type(args[0])):
2394                    if "xtitle" not in kwargs:
2395                        kwargs.update({"xtitle": args[0].name.replace("_", "_ ")})
2396            except:
2397                pass
2398            data = np.asarray(args[0])
2399
2400        if "spher" in mode:
2401            return _histogram_spheric(args[0][:, 0], args[0][:, 1], **kwargs)
2402
2403        if data.ndim == 1:
2404            if "polar" in mode:
2405                return _histogram_polar(data, **kwargs)
2406            return Histogram1D(data, **kwargs)
2407
2408        if "hex" in mode:
2409            return _histogram_hex_bin(args[0][:, 0], args[0][:, 1], **kwargs)
2410
2411        if "3d" in mode.lower():
2412            return _histogram_quad_bin(args[0][:, 0], args[0][:, 1], **kwargs)
2413
2414        return Histogram2D(args[0], **kwargs)
2415
2416    vedo.logger.error(f"in histogram(): could not understand input {args[0]}")
2417    return None
2418
2419
2420def fit(
2421    points, deg=1, niter=0, nstd=3, xerrors=None, yerrors=None, vrange=None, res=250, lw=3, c="red4"
2422) -> "vedo.shapes.Line":
2423    """
2424    Polynomial fitting with parameter error and error bands calculation.
2425    Errors bars in both x and y are supported.
2426
2427    Returns a `vedo.shapes.Line` object.
2428
2429    Additional information about the fitting output can be accessed with:
2430
2431    `fitd = fit(pts)`
2432
2433    - `fitd.coefficients` will contain the coefficients of the polynomial fit
2434    - `fitd.coefficient_errors`, errors on the fitting coefficients
2435    - `fitd.monte_carlo_coefficients`, fitting coefficient set from MC generation
2436    - `fitd.covariance_matrix`, covariance matrix as a numpy array
2437    - `fitd.reduced_chi2`, reduced chi-square of the fitting
2438    - `fitd.ndof`, number of degrees of freedom
2439    - `fitd.data_sigma`, mean data dispersion from the central fit assuming `Chi2=1`
2440    - `fitd.error_lines`, a `vedo.shapes.Line` object for the upper and lower error band
2441    - `fitd.error_band`, the `vedo.mesh.Mesh` object representing the error band
2442
2443    Errors on x and y can be specified. If left to `None` an estimate is made from
2444    the statistical spread of the dataset itself. Errors are always assumed gaussian.
2445
2446    Arguments:
2447        deg : (int)
2448            degree of the polynomial to be fitted
2449        niter : (int)
2450            number of monte-carlo iterations to compute error bands.
2451            If set to 0, return the simple least-squares fit with naive error estimation
2452            on coefficients only. A reasonable non-zero value to set is about 500, in
2453            this case *error_lines*, *error_band* and the other class attributes are filled
2454        nstd : (float)
2455            nr. of standard deviation to use for error calculation
2456        xerrors : (list)
2457            array of the same length of points with the errors on x
2458        yerrors : (list)
2459            array of the same length of points with the errors on y
2460        vrange : (list)
2461            specify the domain range of the fitting line
2462            (only affects visualization, but can be used to extrapolate the fit
2463            outside the data range)
2464        res : (int)
2465            resolution of the output fitted line and error lines
2466
2467    Examples:
2468        - [fit_polynomial1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fit_polynomial1.py)
2469
2470        ![](https://vedo.embl.es/images/pyplot/fitPolynomial1.png)
2471    """
2472    if isinstance(points, vedo.pointcloud.Points):
2473        points = points.vertices
2474    points = np.asarray(points)
2475    if len(points) == 2:  # assume user is passing [x,y]
2476        points = np.c_[points[0], points[1]]
2477    x = points[:, 0]
2478    y = points[:, 1]  # ignore z
2479
2480    n = len(x)
2481    ndof = n - deg - 1
2482    if vrange is not None:
2483        x0, x1 = vrange
2484    else:
2485        x0, x1 = np.min(x), np.max(x)
2486        if xerrors is not None:
2487            x0 -= xerrors[0] / 2
2488            x1 += xerrors[-1] / 2
2489
2490    tol = (x1 - x0) / 10000
2491    xr = np.linspace(x0, x1, res)
2492
2493    # project x errs on y
2494    if xerrors is not None:
2495        xerrors = np.asarray(xerrors)
2496        if yerrors is not None:
2497            yerrors = np.asarray(yerrors)
2498            w = 1.0 / yerrors
2499            coeffs = np.polyfit(x, y, deg, w=w, rcond=None)
2500        else:
2501            coeffs = np.polyfit(x, y, deg, rcond=None)
2502        # update yerrors, 1 bootstrap iteration is enough
2503        p1d = np.poly1d(coeffs)
2504        der = (p1d(x + tol) - p1d(x)) / tol
2505        yerrors = np.sqrt(yerrors * yerrors + np.power(der * xerrors, 2))
2506
2507    if yerrors is not None:
2508        yerrors = np.asarray(yerrors)
2509        w = 1.0 / yerrors
2510        coeffs, V = np.polyfit(x, y, deg, w=w, rcond=None, cov=True)
2511    else:
2512        w = 1
2513        coeffs, V = np.polyfit(x, y, deg, rcond=None, cov=True)
2514
2515    p1d = np.poly1d(coeffs)
2516    theor = p1d(xr)
2517    fitl = shapes.Line(np.c_[xr, theor], lw=lw, c=c).z(tol * 2)
2518    fitl.coefficients = coeffs
2519    fitl.covariance_matrix = V
2520    residuals2_sum = np.sum(np.power(p1d(x) - y, 2)) / ndof
2521    sigma = np.sqrt(residuals2_sum)
2522    fitl.reduced_chi2 = np.sum(np.power((p1d(x) - y) * w, 2)) / ndof
2523    fitl.ndof = ndof
2524    fitl.data_sigma = sigma  # worked out from data using chi2=1 hypo
2525    fitl.name = "LinearPolynomialFit"
2526
2527    if not niter:
2528        fitl.coefficient_errors = np.sqrt(np.diag(V))
2529        return fitl  ################################
2530
2531    if yerrors is not None:
2532        sigma = yerrors
2533    else:
2534        w = None
2535        fitl.reduced_chi2 = 1
2536
2537    Theors, all_coeffs = [], []
2538    for i in range(niter):
2539        noise = np.random.randn(n) * sigma
2540        coeffs = np.polyfit(x, y + noise, deg, w=w, rcond=None)
2541        all_coeffs.append(coeffs)
2542        P1d = np.poly1d(coeffs)
2543        Theor = P1d(xr)
2544        Theors.append(Theor)
2545    # all_coeffs = np.array(all_coeffs)
2546    fitl.monte_carlo_coefficients = np.array(all_coeffs)
2547
2548    stds = np.std(Theors, axis=0)
2549    fitl.coefficient_errors = np.std(all_coeffs, axis=0)
2550
2551    # check distributions on the fly
2552    # for i in range(deg+1):
2553    #     histogram(all_coeffs[:,i],title='par'+str(i)).show(new=1)
2554    # histogram(all_coeffs[:,0], all_coeffs[:,1],
2555    #           xtitle='param0', ytitle='param1',scalarbar=1).show(new=1)
2556    # histogram(all_coeffs[:,1], all_coeffs[:,2],
2557    #           xtitle='param1', ytitle='param2').show(new=1)
2558    # histogram(all_coeffs[:,0], all_coeffs[:,2],
2559    #           xtitle='param0', ytitle='param2').show(new=1)
2560
2561    error_lines = []
2562    for i in [nstd, -nstd]:
2563        pp = np.c_[xr, theor + stds * i]
2564        el = shapes.Line(pp, lw=1, alpha=0.2, c="k").z(tol)
2565        error_lines.append(el)
2566        el.name = "ErrorLine for sigma=" + str(i)
2567
2568    fitl.error_lines = error_lines
2569    l1 = error_lines[0].vertices.tolist()
2570    cband = l1 + list(reversed(error_lines[1].vertices.tolist())) + [l1[0]]
2571    fitl.error_band = shapes.Line(cband).triangulate().lw(0).c("k", 0.15)
2572    fitl.error_band.name = "PolynomialFitErrorBand"
2573    return fitl
2574
2575
2576def _plot_fxy(
2577    z,
2578    xlim=(0, 3),
2579    ylim=(0, 3),
2580    zlim=(None, None),
2581    show_nan=True,
2582    zlevels=10,
2583    c=None,
2584    bc="aqua",
2585    alpha=1,
2586    texture="",
2587    bins=(100, 100),
2588    axes=True,
2589):
2590    import warnings
2591
2592    if c is not None:
2593        texture = None  # disable
2594
2595    ps = vtki.new("PlaneSource")
2596    ps.SetResolution(bins[0], bins[1])
2597    ps.SetNormal([0, 0, 1])
2598    ps.Update()
2599    poly = ps.GetOutput()
2600    dx = xlim[1] - xlim[0]
2601    dy = ylim[1] - ylim[0]
2602    todel, nans = [], []
2603
2604    for i in range(poly.GetNumberOfPoints()):
2605        px, py, _ = poly.GetPoint(i)
2606        xv = (px + 0.5) * dx + xlim[0]
2607        yv = (py + 0.5) * dy + ylim[0]
2608        try:
2609            with warnings.catch_warnings():
2610                warnings.simplefilter("ignore")
2611                zv = z(xv, yv)
2612                if np.isnan(zv) or np.isinf(zv) or np.iscomplex(zv):
2613                    zv = 0
2614                    todel.append(i)
2615                    nans.append([xv, yv, 0])
2616        except:
2617            zv = 0
2618            todel.append(i)
2619            nans.append([xv, yv, 0])
2620        poly.GetPoints().SetPoint(i, [xv, yv, zv])
2621
2622    if todel:
2623        cellIds = vtki.vtkIdList()
2624        poly.BuildLinks()
2625        for i in todel:
2626            poly.GetPointCells(i, cellIds)
2627            for j in range(cellIds.GetNumberOfIds()):
2628                poly.DeleteCell(cellIds.GetId(j))  # flag cell
2629        poly.RemoveDeletedCells()
2630        cl = vtki.new("CleanPolyData")
2631        cl.SetInputData(poly)
2632        cl.Update()
2633        poly = cl.GetOutput()
2634
2635    if not poly.GetNumberOfPoints():
2636        vedo.logger.error("function is not real in the domain")
2637        return None
2638
2639    if zlim[0]:
2640        poly = Mesh(poly).cut_with_plane((0, 0, zlim[0]), (0, 0, 1)).dataset
2641    if zlim[1]:
2642        poly = Mesh(poly).cut_with_plane((0, 0, zlim[1]), (0, 0, -1)).dataset
2643
2644    cmap = ""
2645    if c in colors.cmaps_names:
2646        cmap = c
2647        c = None
2648        bc = None
2649
2650    mesh = Mesh(poly, c, alpha).compute_normals().lighting("plastic")
2651
2652    if cmap:
2653        mesh.compute_elevation().cmap(cmap)
2654    if bc:
2655        mesh.bc(bc)
2656    if texture:
2657        mesh.texture(texture)
2658
2659    acts = [mesh]
2660    if zlevels:
2661        elevation = vtki.new("ElevationFilter")
2662        elevation.SetInputData(poly)
2663        bounds = poly.GetBounds()
2664        elevation.SetLowPoint(0, 0, bounds[4])
2665        elevation.SetHighPoint(0, 0, bounds[5])
2666        elevation.Update()
2667        bcf = vtki.new("BandedPolyDataContourFilter")
2668        bcf.SetInputData(elevation.GetOutput())
2669        bcf.SetScalarModeToValue()
2670        bcf.GenerateContourEdgesOn()
2671        bcf.GenerateValues(zlevels, elevation.GetScalarRange())
2672        bcf.Update()
2673        zpoly = bcf.GetContourEdgesOutput()
2674        zbandsact = Mesh(zpoly, "k", alpha).lw(1).lighting("off")
2675        zbandsact.mapper.SetResolveCoincidentTopologyToPolygonOffset()
2676        acts.append(zbandsact)
2677
2678    if show_nan and todel:
2679        bb = mesh.bounds()
2680        if bb[4] <= 0 and bb[5] >= 0:
2681            zm = 0.0
2682        else:
2683            zm = (bb[4] + bb[5]) / 2
2684        nans = np.array(nans) + [0, 0, zm]
2685        nansact = shapes.Points(nans, r=2, c="red5", alpha=alpha)
2686        nansact.properties.RenderPointsAsSpheresOff()
2687        acts.append(nansact)
2688
2689    if isinstance(axes, dict):
2690        axs = addons.Axes(mesh, **axes)
2691        acts.append(axs)
2692    elif axes:
2693        axs = addons.Axes(mesh)
2694        acts.append(axs)
2695
2696    assem = Assembly(acts)
2697    assem.name = "PlotFxy"
2698    return assem
2699
2700
2701def _plot_fz(
2702    z,
2703    x=(-1, 1),
2704    y=(-1, 1),
2705    zlimits=(None, None),
2706    cmap="PiYG",
2707    alpha=1,
2708    lw=0.1,
2709    bins=(75, 75),
2710    axes=True,
2711):
2712    ps = vtki.new("PlaneSource")
2713    ps.SetResolution(bins[0], bins[1])
2714    ps.SetNormal([0, 0, 1])
2715    ps.Update()
2716    poly = ps.GetOutput()
2717    dx = x[1] - x[0]
2718    dy = y[1] - y[0]
2719
2720    arrImg = []
2721    for i in range(poly.GetNumberOfPoints()):
2722        px, py, _ = poly.GetPoint(i)
2723        xv = (px + 0.5) * dx + x[0]
2724        yv = (py + 0.5) * dy + y[0]
2725        try:
2726            zv = z(complex(xv), complex(yv))
2727        except:
2728            zv = 0
2729        poly.GetPoints().SetPoint(i, [xv, yv, np.real(zv)])
2730        arrImg.append(np.imag(zv))
2731
2732    mesh = Mesh(poly, alpha).lighting("plastic")
2733    v = max(abs(np.min(arrImg)), abs(np.max(arrImg)))
2734    mesh.cmap(cmap, arrImg, vmin=-v, vmax=v)
2735    mesh.compute_normals().lw(lw)
2736
2737    if zlimits[0]:
2738        mesh.cut_with_plane((0, 0, zlimits[0]), (0, 0, 1))
2739    if zlimits[1]:
2740        mesh.cut_with_plane((0, 0, zlimits[1]), (0, 0, -1))
2741
2742    acts = [mesh]
2743    if axes:
2744        axs = addons.Axes(mesh, ztitle="Real part")
2745        acts.append(axs)
2746    asse = Assembly(acts)
2747    asse.name = "PlotFz"
2748    if isinstance(z, str):
2749        asse.name += " " + z
2750    return asse
2751
2752
2753def _plot_polar(
2754    rphi,
2755    title="",
2756    tsize=0.1,
2757    lsize=0.05,
2758    r1=0,
2759    r2=1,
2760    c="blue",
2761    bc="k",
2762    alpha=1,
2763    ps=5,
2764    lw=3,
2765    deg=False,
2766    vmax=None,
2767    fill=False,
2768    splined=False,
2769    nrays=8,
2770    show_disc=True,
2771    show_lines=True,
2772    show_angles=True,
2773):
2774    if len(rphi) == 2:
2775        rphi = np.stack((rphi[0], rphi[1]), axis=1)
2776
2777    rphi = np.array(rphi, dtype=float)
2778    thetas = rphi[:, 0]
2779    radii = rphi[:, 1]
2780
2781    k = 180 / np.pi
2782    if deg:
2783        thetas = np.array(thetas, dtype=float) / k
2784
2785    vals = []
2786    for v in thetas:  # normalize range
2787        t = np.arctan2(np.sin(v), np.cos(v))
2788        if t < 0:
2789            t += 2 * np.pi
2790        vals.append(t)
2791    thetas = np.array(vals, dtype=float)
2792
2793    if vmax is None:
2794        vmax = np.max(radii)
2795
2796    angles = []
2797    points = []
2798    for t, r in zip(thetas, radii):
2799        r = r / vmax * r2 + r1
2800        ct, st = np.cos(t), np.sin(t)
2801        points.append([r * ct, r * st, 0])
2802    p0 = points[0]
2803    points.append(p0)
2804
2805    r2e = r1 + r2
2806    lines = None
2807    if splined:
2808        lines = shapes.KSpline(points, closed=True)
2809        lines.c(c).lw(lw).alpha(alpha)
2810    elif lw:
2811        lines = shapes.Line(points)
2812        lines.c(c).lw(lw).alpha(alpha)
2813
2814    points.pop()
2815
2816    ptsact = None
2817    if ps:
2818        ptsact = shapes.Points(points, r=ps, c=c, alpha=alpha)
2819
2820    filling = None
2821    if fill and lw:
2822        faces = []
2823        coords = [[0, 0, 0]] + lines.vertices.tolist()
2824        for i in range(1, lines.npoints):
2825            faces.append([0, i, i + 1])
2826        filling = Mesh([coords, faces]).c(c).alpha(alpha)
2827
2828    back = None
2829    back2 = None
2830    if show_disc:
2831        back = shapes.Disc(r1=r2e, r2=r2e * 1.01, c=bc, res=(1, 360))
2832        back.z(-0.01).lighting("off").alpha(alpha)
2833        back2 = shapes.Disc(r1=r2e / 2, r2=r2e / 2 * 1.005, c=bc, res=(1, 360))
2834        back2.z(-0.01).lighting("off").alpha(alpha)
2835
2836    ti = None
2837    if title:
2838        ti = shapes.Text3D(title, (0, 0, 0), s=tsize, depth=0, justify="top-center")
2839        ti.pos(0, -r2e * 1.15, 0.01)
2840
2841    rays = []
2842    if show_disc:
2843        rgap = 0.05
2844        for t in np.linspace(0, 2 * np.pi, num=nrays, endpoint=False):
2845            ct, st = np.cos(t), np.sin(t)
2846            if show_lines:
2847                l = shapes.Line((0, 0, -0.01), (r2e * ct * 1.03, r2e * st * 1.03, -0.01))
2848                rays.append(l)
2849                ct2, st2 = np.cos(t + np.pi / nrays), np.sin(t + np.pi / nrays)
2850                lm = shapes.DashedLine((0, 0, -0.01), (r2e * ct2, r2e * st2, -0.01), spacing=0.25)
2851                rays.append(lm)
2852            elif show_angles:  # just the ticks
2853                l = shapes.Line(
2854                    (r2e * ct * 0.98, r2e * st * 0.98, -0.01),
2855                    (r2e * ct * 1.03, r2e * st * 1.03, -0.01),
2856                )
2857            if show_angles:
2858                if 0 <= t < np.pi / 2:
2859                    ju = "bottom-left"
2860                elif t == np.pi / 2:
2861                    ju = "bottom-center"
2862                elif np.pi / 2 < t <= np.pi:
2863                    ju = "bottom-right"
2864                elif np.pi < t < np.pi * 3 / 2:
2865                    ju = "top-right"
2866                elif t == np.pi * 3 / 2:
2867                    ju = "top-center"
2868                else:
2869                    ju = "top-left"
2870                a = shapes.Text3D(int(t * k), pos=(0, 0, 0), s=lsize, depth=0, justify=ju)
2871                a.pos(r2e * ct * (1 + rgap), r2e * st * (1 + rgap), -0.01)
2872                angles.append(a)
2873
2874    mrg = merge(back, back2, angles, rays, ti)
2875    if mrg:
2876        mrg.color(bc).alpha(alpha).lighting("off")
2877    rh = Assembly([lines, ptsact, filling] + [mrg])
2878    rh.name = "PlotPolar"
2879    return rh
2880
2881
2882def _plot_spheric(rfunc, normalize=True, res=33, scalarbar=True, c="grey", alpha=0.05, cmap="jet"):
2883    sg = shapes.Sphere(res=res, quads=True)
2884    sg.alpha(alpha).c(c).wireframe()
2885
2886    cgpts = sg.vertices
2887    r, theta, phi = cart2spher(*cgpts.T)
2888
2889    newr, inans = [], []
2890    for i in range(len(r)):
2891        try:
2892            ri = rfunc(theta[i], phi[i])
2893            if np.isnan(ri):
2894                inans.append(i)
2895                newr.append(1)
2896            else:
2897                newr.append(ri)
2898        except:
2899            inans.append(i)
2900            newr.append(1)
2901
2902    newr = np.array(newr, dtype=float)
2903    if normalize:
2904        newr = newr / np.max(newr)
2905        newr[inans] = 1
2906
2907    nanpts = []
2908    if inans:
2909        redpts = spher2cart(newr[inans], theta[inans], phi[inans]).T
2910        nanpts.append(shapes.Points(redpts, r=4, c="r"))
2911
2912    pts = spher2cart(newr, theta, phi).T
2913    ssurf = sg.clone()
2914    ssurf.vertices = pts
2915    if inans:
2916        ssurf.delete_cells_by_point_index(inans)
2917
2918    ssurf.alpha(1).wireframe(0).lw(0.1)
2919
2920    ssurf.cmap(cmap, newr)
2921    ssurf.compute_normals()
2922
2923    if scalarbar:
2924        xm = np.max([np.max(pts[0]), 1])
2925        ym = np.max([np.abs(np.max(pts[1])), 1])
2926        ssurf.mapper.SetScalarRange(np.min(newr), np.max(newr))
2927        sb3d = ssurf.add_scalarbar3d(size=(xm * 0.07, ym), c="k").scalarbar
2928        sb3d.rotate_x(90).pos(xm * 1.1, 0, -0.5)
2929    else:
2930        sb3d = None
2931
2932    sg.pickable(False)
2933    asse = Assembly([ssurf, sg] + nanpts + [sb3d])
2934    asse.name = "PlotSpheric"
2935    return asse
2936
2937
2938def _histogram_quad_bin(x, y, **kwargs):
2939    # generate a histogram with 3D bars
2940    #
2941    histo = Histogram2D(x, y, **kwargs)
2942
2943    gap = kwargs.pop("gap", 0)
2944    zscale = kwargs.pop("zscale", 1)
2945    cmap = kwargs.pop("cmap", "Blues_r")
2946
2947    gr = histo.objects[2]
2948    d = gr.diagonal_size()
2949    tol = d / 1_000_000  # tolerance
2950    if gap >= 0:
2951        gr.shrink(1 - gap - tol)
2952    gr.map_cells_to_points()
2953
2954    faces = np.array(gr.cells)
2955    s = 1 / histo.entries * len(faces) * zscale
2956    zvals = gr.pointdata["Scalars"] * s
2957
2958    pts1 = gr.vertices
2959    pts2 = np.copy(pts1)
2960    pts2[:, 2] = zvals + tol
2961    newpts = np.vstack([pts1, pts2])
2962    newzvals = np.hstack([zvals, zvals]) / s
2963
2964    n = pts1.shape[0]
2965    newfaces = []
2966    for f in faces:
2967        f0, f1, f2, f3 = f
2968        f0n, f1n, f2n, f3n = f + n
2969        newfaces.extend(
2970            [
2971                [f0, f1, f2, f3],
2972                [f0n, f1n, f2n, f3n],
2973                [f0, f1, f1n, f0n],
2974                [f1, f2, f2n, f1n],
2975                [f2, f3, f3n, f2n],
2976                [f3, f0, f0n, f3n],
2977            ]
2978        )
2979
2980    msh = Mesh([newpts, newfaces]).pickable(False)
2981    msh.cmap(cmap, newzvals, name="Frequency")
2982    msh.lw(1).lighting("ambient")
2983
2984    histo.objects[2] = msh
2985    histo.RemovePart(gr.actor)
2986    histo.AddPart(msh.actor)
2987    histo.objects.append(msh)
2988    return histo
2989
2990
2991def _histogram_hex_bin(
2992    xvalues, yvalues, bins=12, norm=1, fill=True, c=None, cmap="terrain_r", alpha=1
2993) -> "Assembly":
2994    xmin, xmax = np.min(xvalues), np.max(xvalues)
2995    ymin, ymax = np.min(yvalues), np.max(yvalues)
2996    dx, dy = xmax - xmin, ymax - ymin
2997
2998    if utils.is_sequence(bins):
2999        n, m = bins
3000    else:
3001        if xmax - xmin < ymax - ymin:
3002            n = bins
3003            m = np.rint(dy / dx * n / 1.2 + 0.5).astype(int)
3004        else:
3005            m = bins
3006            n = np.rint(dx / dy * m * 1.2 + 0.5).astype(int)
3007
3008    values = np.stack((xvalues, yvalues), axis=1)
3009    zs = [[0.0]] * len(values)
3010    values = np.append(values, zs, axis=1)
3011    cloud = vedo.Points(values)
3012
3013    col = None
3014    if c is not None:
3015        col = colors.get_color(c)
3016
3017    hexs, binmax = [], 0
3018    ki, kj = 1.33, 1.12
3019    r = 0.47 / n * 1.2 * dx
3020    for i in range(n + 3):
3021        for j in range(m + 2):
3022            cyl = vtki.new("CylinderSource")
3023            cyl.SetResolution(6)
3024            cyl.CappingOn()
3025            cyl.SetRadius(0.5)
3026            cyl.SetHeight(0.1)
3027            cyl.Update()
3028            t = vtki.vtkTransform()
3029            if not i % 2:
3030                p = (i / ki, j / kj, 0)
3031            else:
3032                p = (i / ki, j / kj + 0.45, 0)
3033            q = (p[0] / n * 1.2 * dx + xmin, p[1] / m * dy + ymin, 0)
3034            ne = len(cloud.closest_point(q, radius=r))
3035            if fill:
3036                t.Translate(p[0], p[1], ne / 2)
3037                t.Scale(1, 1, ne * 10)
3038            else:
3039                t.Translate(p[0], p[1], ne)
3040            t.RotateX(90)  # put it along Z
3041            tf = vtki.new("TransformPolyDataFilter")
3042            tf.SetInputData(cyl.GetOutput())
3043            tf.SetTransform(t)
3044            tf.Update()
3045            if c is None:
3046                col = i
3047            h = Mesh(tf.GetOutput(), c=col, alpha=alpha).flat()
3048            h.lighting("plastic")
3049            h.actor.PickableOff()
3050            hexs.append(h)
3051            if ne > binmax:
3052                binmax = ne
3053
3054    if cmap is not None:
3055        for h in hexs:
3056            z = h.bounds()[5]
3057            col = colors.color_map(z, cmap, 0, binmax)
3058            h.color(col)
3059
3060    asse = Assembly(hexs)
3061    asse.scale([1.2 / n * dx, 1 / m * dy, norm / binmax * (dx + dy) / 4])
3062    asse.pos([xmin, ymin, 0])
3063    asse.name = "HistogramHexBin"
3064    return asse
3065
3066
3067def _histogram_polar(
3068    values,
3069    weights=None,
3070    title="",
3071    tsize=0.1,
3072    bins=16,
3073    r1=0.25,
3074    r2=1,
3075    phigap=0.5,
3076    rgap=0.05,
3077    lpos=1,
3078    lsize=0.04,
3079    c="grey",
3080    bc="k",
3081    alpha=1,
3082    cmap=None,
3083    deg=False,
3084    vmin=None,
3085    vmax=None,
3086    labels=(),
3087    show_disc=True,
3088    nrays=8,
3089    show_lines=True,
3090    show_angles=True,
3091    show_errors=False,
3092):
3093    k = 180 / np.pi
3094    if deg:
3095        values = np.array(values, dtype=float) / k
3096    else:
3097        values = np.array(values, dtype=float)
3098
3099    vals = []
3100    for v in values:  # normalize range
3101        t = np.arctan2(np.sin(v), np.cos(v))
3102        if t < 0:
3103            t += 2 * np.pi
3104        vals.append(t + 0.00001)
3105
3106    histodata, edges = np.histogram(vals, weights=weights, bins=bins, range=(0, 2 * np.pi))
3107
3108    thetas = []
3109    for i in range(bins):
3110        thetas.append((edges[i] + edges[i + 1]) / 2)
3111
3112    if vmin is None:
3113        vmin = np.min(histodata)
3114    if vmax is None:
3115        vmax = np.max(histodata)
3116
3117    errors = np.sqrt(histodata)
3118    r2e = r1 + r2
3119    if show_errors:
3120        r2e += np.max(errors) / vmax * 1.5
3121
3122    back = None
3123    if show_disc:
3124        back = shapes.Disc(r1=r2e, r2=r2e * 1.01, c=bc, res=(1, 360))
3125        back.z(-0.01)
3126
3127    slices = []
3128    lines = []
3129    angles = []
3130    errbars = []
3131
3132    for i, t in enumerate(thetas):
3133        r = histodata[i] / vmax * r2
3134        d = shapes.Disc((0, 0, 0), r1, r1 + r, res=(1, 360))
3135        delta = np.pi / bins - np.pi / 2 - phigap / k
3136        d.cut_with_plane(normal=(np.cos(t + delta), np.sin(t + delta), 0))
3137        d.cut_with_plane(normal=(np.cos(t - delta), np.sin(t - delta), 0))
3138        if cmap is not None:
3139            cslice = colors.color_map(histodata[i], cmap, vmin, vmax)
3140            d.color(cslice)
3141        else:
3142            if c is None:
3143                d.color(i)
3144            elif utils.is_sequence(c) and len(c) == bins:
3145                d.color(c[i])
3146            else:
3147                d.color(c)
3148        d.alpha(alpha).lighting("off")
3149        slices.append(d)
3150
3151        ct, st = np.cos(t), np.sin(t)
3152
3153        if show_errors:
3154            show_lines = False
3155            err = np.sqrt(histodata[i]) / vmax * r2
3156            errl = shapes.Line(
3157                ((r1 + r - err) * ct, (r1 + r - err) * st, 0.01),
3158                ((r1 + r + err) * ct, (r1 + r + err) * st, 0.01),
3159            )
3160            errl.alpha(alpha).lw(3).color(bc)
3161            errbars.append(errl)
3162
3163    labs = []
3164    rays = []
3165    if show_disc:
3166        outerdisc = shapes.Disc(r1=r2e, r2=r2e * 1.01, c=bc, res=(1, 360))
3167        outerdisc.z(-0.01)
3168        innerdisc = shapes.Disc(r1=r2e / 2, r2=r2e / 2 * 1.005, c=bc, res=(1, 360))
3169        innerdisc.z(-0.01)
3170        rays.append(outerdisc)
3171        rays.append(innerdisc)
3172
3173        rgap = 0.05
3174        for t in np.linspace(0, 2 * np.pi, num=nrays, endpoint=False):
3175            ct, st = np.cos(t), np.sin(t)
3176            if show_lines:
3177                l = shapes.Line((0, 0, -0.01), (r2e * ct * 1.03, r2e * st * 1.03, -0.01))
3178                rays.append(l)
3179                ct2, st2 = np.cos(t + np.pi / nrays), np.sin(t + np.pi / nrays)
3180                lm = shapes.DashedLine((0, 0, -0.01), (r2e * ct2, r2e * st2, -0.01), spacing=0.25)
3181                rays.append(lm)
3182            elif show_angles:  # just the ticks
3183                l = shapes.Line(
3184                    (r2e * ct * 0.98, r2e * st * 0.98, -0.01),
3185                    (r2e * ct * 1.03, r2e * st * 1.03, -0.01),
3186                )
3187            if show_angles:
3188                if 0 <= t < np.pi / 2:
3189                    ju = "bottom-left"
3190                elif t == np.pi / 2:
3191                    ju = "bottom-center"
3192                elif np.pi / 2 < t <= np.pi:
3193                    ju = "bottom-right"
3194                elif np.pi < t < np.pi * 3 / 2:
3195                    ju = "top-right"
3196                elif t == np.pi * 3 / 2:
3197                    ju = "top-center"
3198                else:
3199                    ju = "top-left"
3200                a = shapes.Text3D(int(t * k), pos=(0, 0, 0), s=lsize, depth=0, justify=ju)
3201                a.pos(r2e * ct * (1 + rgap), r2e * st * (1 + rgap), -0.01)
3202                angles.append(a)
3203
3204    ti = None
3205    if title:
3206        ti = shapes.Text3D(title, (0, 0, 0), s=tsize, depth=0, justify="top-center")
3207        ti.pos(0, -r2e * 1.15, 0.01)
3208
3209    for i, t in enumerate(thetas):
3210        if i < len(labels):
3211            lab = shapes.Text3D(
3212                labels[i], (0, 0, 0), s=lsize, depth=0, justify="center"  # font="VTK",
3213            )
3214            lab.pos(
3215                r2e * np.cos(t) * (1 + rgap) * lpos / 2,
3216                r2e * np.sin(t) * (1 + rgap) * lpos / 2,
3217                0.01,
3218            )
3219            labs.append(lab)
3220
3221    mrg = merge(lines, angles, rays, ti, labs)
3222    if mrg:
3223        mrg.color(bc).lighting("off")
3224
3225    acts = slices + errbars + [mrg]
3226    asse = Assembly(acts)
3227    asse.frequencies = histodata
3228    asse.bins = edges
3229    asse.name = "HistogramPolar"
3230    return asse
3231
3232
3233def _histogram_spheric(thetavalues, phivalues, rmax=1.2, res=8, cmap="rainbow", gap=0.1):
3234
3235    x, y, z = spher2cart(np.ones_like(thetavalues) * 1.1, thetavalues, phivalues)
3236    ptsvals = np.c_[x, y, z]
3237
3238    sg = shapes.Sphere(res=res, quads=True).shrink(1 - gap)
3239    sgfaces = sg.cells
3240    sgpts = sg.vertices
3241
3242    cntrs = sg.cell_centers
3243    counts = np.zeros(len(cntrs))
3244    for p in ptsvals:
3245        cell = sg.closest_point(p, return_cell_id=True)
3246        counts[cell] += 1
3247    acounts = np.array(counts, dtype=float)
3248    counts *= (rmax - 1) / np.max(counts)
3249
3250    for cell, cn in enumerate(counts):
3251        if not cn:
3252            continue
3253        fs = sgfaces[cell]
3254        pts = sgpts[fs]
3255        _, t1, p1 = cart2spher(pts[:, 0], pts[:, 1], pts[:, 2])
3256        x, y, z = spher2cart(1 + cn, t1, p1)
3257        sgpts[fs] = np.c_[x, y, z]
3258
3259    sg.vertices = sgpts
3260    sg.cmap(cmap, acounts, on="cells")
3261    vals = sg.celldata["Scalars"]
3262
3263    faces = sg.cells
3264    points = sg.vertices.tolist() + [[0.0, 0.0, 0.0]]
3265    lp = len(points) - 1
3266    newfaces = []
3267    newvals = []
3268    for i, f in enumerate(faces):
3269        p0, p1, p2, p3 = f
3270        newfaces.append(f)
3271        newfaces.append([p0, lp, p1])
3272        newfaces.append([p1, lp, p2])
3273        newfaces.append([p2, lp, p3])
3274        newfaces.append([p3, lp, p0])
3275        for _ in range(5):
3276            newvals.append(vals[i])
3277
3278    newsg = Mesh([points, newfaces]).cmap(cmap, newvals, on="cells")
3279    newsg.compute_normals().flat()
3280    newsg.name = "HistogramSpheric"
3281    return newsg
3282
3283
3284def donut(
3285    fractions,
3286    title="",
3287    tsize=0.3,
3288    r1=1.7,
3289    r2=1,
3290    phigap=0,
3291    lpos=0.8,
3292    lsize=0.15,
3293    c=None,
3294    bc="k",
3295    alpha=1,
3296    labels=(),
3297    show_disc=False,
3298) -> "Assembly":
3299    """
3300    Donut plot or pie chart.
3301
3302    Arguments:
3303        title : (str)
3304            plot title
3305        tsize : (float)
3306            title size
3307        r1 : (float) inner radius
3308        r2 : (float)
3309            outer radius, starting from r1
3310        phigap : (float)
3311            gap angle btw 2 radial bars, in degrees
3312        lpos : (float)
3313            label gap factor along radius
3314        lsize : (float)
3315            label size
3316        c : (color)
3317            color of the plot slices
3318        bc : (color)
3319            color of the disc frame
3320        alpha : (float)
3321            opacity of the disc frame
3322        labels : (list)
3323            list of labels
3324        show_disc : (bool)
3325            show the outer ring axis
3326
3327    Examples:
3328        - [donut.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/donut.py)
3329
3330            ![](https://vedo.embl.es/images/pyplot/donut.png)
3331    """
3332    fractions = np.array(fractions, dtype=float)
3333    angles = np.add.accumulate(2 * np.pi * fractions)
3334    angles[-1] = 2 * np.pi
3335    if angles[-2] > 2 * np.pi:
3336        print("Error in donut(): fractions must sum to 1.")
3337        raise RuntimeError
3338
3339    cols = []
3340    for i, th in enumerate(np.linspace(0, 2 * np.pi, 360, endpoint=False)):
3341        for ia, a in enumerate(angles):
3342            if th < a:
3343                cols.append(c[ia])
3344                break
3345    labs = []
3346    if labels:
3347        angles = np.concatenate([[0], angles])
3348        labs = [""] * 360
3349        for i in range(len(labels)):
3350            a = (angles[i + 1] + angles[i]) / 2
3351            j = int(a / np.pi * 180)
3352            labs[j] = labels[i]
3353
3354    data = np.linspace(0, 2 * np.pi, 360, endpoint=False) + 0.005
3355    dn = _histogram_polar(
3356        data,
3357        title=title,
3358        bins=360,
3359        r1=r1,
3360        r2=r2,
3361        phigap=phigap,
3362        lpos=lpos,
3363        lsize=lsize,
3364        tsize=tsize,
3365        c=cols,
3366        bc=bc,
3367        alpha=alpha,
3368        vmin=0,
3369        vmax=1,
3370        labels=labs,
3371        show_disc=show_disc,
3372        show_lines=0,
3373        show_angles=0,
3374        show_errors=0,
3375    )
3376    dn.name = "Donut"
3377    return dn
3378
3379
3380def violin(
3381    values,
3382    bins=10,
3383    vlim=None,
3384    x=0,
3385    width=3,
3386    splined=True,
3387    fill=True,
3388    c="violet",
3389    alpha=1,
3390    outline=True,
3391    centerline=True,
3392    lc="darkorchid",
3393    lw=3,
3394) -> "Assembly":
3395    """
3396    Violin style histogram.
3397
3398    Arguments:
3399        bins : (int)
3400            number of bins
3401        vlim : (list)
3402            input value limits. Crop values outside range
3403        x : (float)
3404            x-position of the violin axis
3405        width : (float)
3406            width factor of the normalized distribution
3407        splined : (bool)
3408            spline the outline
3409        fill : (bool)
3410            fill violin with solid color
3411        outline : (bool)
3412            add the distribution outline
3413        centerline : (bool)
3414            add the vertical centerline at x
3415        lc : (color)
3416            line color
3417
3418    Examples:
3419        - [histo_violin.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/histo_violin.py)
3420
3421            ![](https://vedo.embl.es/images/pyplot/histo_violin.png)
3422    """
3423    fs, edges = np.histogram(values, bins=bins, range=vlim)
3424    mine, maxe = np.min(edges), np.max(edges)
3425    fs = fs.astype(float) / len(values) * width
3426
3427    rs = []
3428
3429    if splined:
3430        lnl, lnr = [(0, edges[0], 0)], [(0, edges[0], 0)]
3431        for i in range(bins):
3432            xc = (edges[i] + edges[i + 1]) / 2
3433            yc = fs[i]
3434            lnl.append([-yc, xc, 0])
3435            lnr.append([yc, xc, 0])
3436        lnl.append((0, edges[-1], 0))
3437        lnr.append((0, edges[-1], 0))
3438        spl = shapes.KSpline(lnl).x(x)
3439        spr = shapes.KSpline(lnr).x(x)
3440        spl.color(lc).alpha(alpha).lw(lw)
3441        spr.color(lc).alpha(alpha).lw(lw)
3442        if outline:
3443            rs.append(spl)
3444            rs.append(spr)
3445        if fill:
3446            rb = shapes.Ribbon(spl, spr, c=c, alpha=alpha).lighting("off")
3447            rs.append(rb)
3448
3449    else:
3450        lns1 = [[0, mine, 0]]
3451        for i in range(bins):
3452            lns1.append([fs[i], edges[i], 0])
3453            lns1.append([fs[i], edges[i + 1], 0])
3454        lns1.append([0, maxe, 0])
3455
3456        lns2 = [[0, mine, 0]]
3457        for i in range(bins):
3458            lns2.append([-fs[i], edges[i], 0])
3459            lns2.append([-fs[i], edges[i + 1], 0])
3460        lns2.append([0, maxe, 0])
3461
3462        if outline:
3463            rs.append(shapes.Line(lns1, c=lc, alpha=alpha, lw=lw).x(x))
3464            rs.append(shapes.Line(lns2, c=lc, alpha=alpha, lw=lw).x(x))
3465
3466        if fill:
3467            for i in range(bins):
3468                p0 = (-fs[i], edges[i], 0)
3469                p1 = (fs[i], edges[i + 1], 0)
3470                r = shapes.Rectangle(p0, p1).x(p0[0] + x)
3471                r.color(c).alpha(alpha).lighting("off")
3472                rs.append(r)
3473
3474    if centerline:
3475        cl = shapes.Line([0, mine, 0.01], [0, maxe, 0.01], c=lc, alpha=alpha, lw=2).x(x)
3476        rs.append(cl)
3477
3478    asse = Assembly(rs)
3479    asse.name = "Violin"
3480    return asse
3481
3482
3483def whisker(data, s=0.25, c="k", lw=2, bc="blue", alpha=0.25, r=5, jitter=True, horizontal=False) -> "Assembly":
3484    """
3485    Generate a "whisker" bar from a 1-dimensional dataset.
3486
3487    Arguments:
3488        s : (float)
3489            size of the box
3490        c : (color)
3491            color of the lines
3492        lw : (float)
3493            line width
3494        bc : (color)
3495            color of the box
3496        alpha : (float)
3497            transparency of the box
3498        r : (float)
3499            point radius in pixels (use value 0 to disable)
3500        jitter : (bool)
3501            add some randomness to points to avoid overlap
3502        horizontal : (bool)
3503            set horizontal layout
3504
3505    Examples:
3506        - [whiskers.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/whiskers.py)
3507
3508            ![](https://vedo.embl.es/images/pyplot/whiskers.png)
3509    """
3510    xvals = np.zeros_like(np.asarray(data))
3511    if jitter:
3512        xjit = np.random.randn(len(xvals)) * s / 9
3513        xjit = np.clip(xjit, -s / 2.1, s / 2.1)
3514        xvals += xjit
3515
3516    dmean = np.mean(data)
3517    dq05 = np.quantile(data, 0.05)
3518    dq25 = np.quantile(data, 0.25)
3519    dq75 = np.quantile(data, 0.75)
3520    dq95 = np.quantile(data, 0.95)
3521
3522    pts = None
3523    if r:
3524        pts = shapes.Points(np.array([xvals, data]).T, c=c, r=r)
3525
3526    rec = shapes.Rectangle([-s / 2, dq25], [s / 2, dq75], c=bc, alpha=alpha)
3527    rec.properties.LightingOff()
3528    rl = shapes.Line([[-s / 2, dq25], [s / 2, dq25], [s / 2, dq75], [-s / 2, dq75]], closed=True)
3529    l1 = shapes.Line([0, dq05, 0], [0, dq25, 0], c=c, lw=lw)
3530    l2 = shapes.Line([0, dq75, 0], [0, dq95, 0], c=c, lw=lw)
3531    lm = shapes.Line([-s / 2, dmean], [s / 2, dmean])
3532    lns = merge(l1, l2, lm, rl)
3533    asse = Assembly([lns, rec, pts])
3534    if horizontal:
3535        asse.rotate_z(-90)
3536    asse.name = "Whisker"
3537    asse.info["mean"] = dmean
3538    asse.info["quantile_05"] = dq05
3539    asse.info["quantile_25"] = dq25
3540    asse.info["quantile_75"] = dq75
3541    asse.info["quantile_95"] = dq95
3542    return asse
3543
3544
3545def streamplot(
3546    X, Y, U, V, direction="both", max_propagation=None, lw=2, cmap="viridis", probes=()
3547) -> Union["vedo.shapes.Lines", None]:
3548    """
3549    Generate a streamline plot of a vectorial field (U,V) defined at positions (X,Y).
3550    Returns a `Mesh` object.
3551
3552    Arguments:
3553        direction : (str)
3554            either "forward", "backward" or "both"
3555        max_propagation : (float)
3556            maximum physical length of the streamline
3557        lw : (float)
3558            line width in absolute units
3559
3560    Examples:
3561        - [plot_stream.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/plot_stream.py)
3562
3563            ![](https://vedo.embl.es/images/pyplot/plot_stream.png)
3564    """
3565    n = len(X)
3566    m = len(Y[0])
3567    if n != m:
3568        print("Limitation in streamplot(): only square grids are allowed.", n, m)
3569        raise RuntimeError()
3570
3571    xmin, xmax = X[0][0], X[-1][-1]
3572    ymin, ymax = Y[0][0], Y[-1][-1]
3573
3574    field = np.sqrt(U * U + V * V)
3575
3576    vol = vedo.Volume(field, dims=(n, n, 1))
3577
3578    uf = np.ravel(U, order="F")
3579    vf = np.ravel(V, order="F")
3580    vects = np.c_[uf, vf, np.zeros_like(uf)]
3581    vol.pointdata["StreamPlotField"] = vects
3582
3583    if len(probes) == 0:
3584        probe = shapes.Grid(pos=((n - 1) / 2, (n - 1) / 2, 0), s=(n - 1, n - 1), res=(n - 1, n - 1))
3585    else:
3586        if isinstance(probes, vedo.Points):
3587            probes = probes.vertices
3588        else:
3589            probes = np.array(probes, dtype=float)
3590            if len(probes[0]) == 2:
3591                probes = np.c_[probes[:, 0], probes[:, 1], np.zeros(len(probes))]
3592        sv = [(n - 1) / (xmax - xmin), (n - 1) / (ymax - ymin), 1]
3593        probes = probes - [xmin, ymin, 0]
3594        probes = np.multiply(probes, sv)
3595        probe = vedo.Points(probes)
3596
3597    stream = vol.compute_streamlines(probe, direction=direction, max_propagation=max_propagation)
3598    if stream:
3599        stream.lw(lw).cmap(cmap).lighting("off")
3600        stream.scale([1 / (n - 1) * (xmax - xmin), 1 / (n - 1) * (ymax - ymin), 1])
3601        stream.shift(xmin, ymin)
3602    return stream
3603
3604
3605def matrix(
3606    M,
3607    title="Matrix",
3608    xtitle="",
3609    ytitle="",
3610    xlabels=(),
3611    ylabels=(),
3612    xrotation=0,
3613    cmap="Reds",
3614    vmin=None,
3615    vmax=None,
3616    precision=2,
3617    font="Theemim",
3618    scale=0,
3619    scalarbar=True,
3620    lc="white",
3621    lw=0,
3622    c="black",
3623    alpha=1,
3624) -> "Assembly":
3625    """
3626    Generate a matrix, or a 2D color-coded plot with bin labels.
3627
3628    Returns an `Assembly` object.
3629
3630    Arguments:
3631        M : (list, numpy array)
3632            the input array to visualize
3633        title : (str)
3634            title of the plot
3635        xtitle : (str)
3636            title of the horizontal colmuns
3637        ytitle : (str)
3638            title of the vertical rows
3639        xlabels : (list)
3640            individual string labels for each column. Must be of length m
3641        ylabels : (list)
3642            individual string labels for each row. Must be of length n
3643        xrotation : (float)
3644            rotation of the horizontal labels
3645        cmap : (str)
3646            color map name
3647        vmin : (float)
3648            minimum value of the colormap range
3649        vmax : (float)
3650            maximum value of the colormap range
3651        precision : (int)
3652            number of digits for the matrix entries or bins
3653        font : (str)
3654            font name. Check [available fonts here](https://vedo.embl.es/fonts).
3655
3656        scale : (float)
3657            size of the numeric entries or bin values
3658        scalarbar : (bool)
3659            add a scalar bar to the right of the plot
3660        lc : (str)
3661            color of the line separating the bins
3662        lw : (float)
3663            Width of the line separating the bins
3664        c : (str)
3665            text color
3666        alpha : (float)
3667            plot transparency
3668
3669    Examples:
3670        - [np_matrix.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/np_matrix.py)
3671
3672            ![](https://vedo.embl.es/images/pyplot/np_matrix.png)
3673    """
3674    M = np.asarray(M)
3675    n, m = M.shape
3676    gr = shapes.Grid(res=(m, n), s=(m / (m + n) * 2, n / (m + n) * 2), c=c, alpha=alpha)
3677    gr.wireframe(False).lc(lc).lw(lw)
3678
3679    matr = np.flip(np.flip(M), axis=1).ravel(order="C")
3680    gr.cmap(cmap, matr, on="cells", vmin=vmin, vmax=vmax)
3681    sbar = None
3682    if scalarbar:
3683        gr.add_scalarbar3d(title_font=font, label_font=font)
3684        sbar = gr.scalarbar
3685    labs = None
3686    if scale != 0:
3687        labs = gr.labels(
3688            on="cells",
3689            scale=scale / max(m, n),
3690            precision=precision,
3691            font=font,
3692            justify="center",
3693            c=c,
3694        )
3695        labs.z(0.001)
3696    t = None
3697    if title:
3698        if title == "Matrix":
3699            title += " " + str(n) + "x" + str(m)
3700        t = shapes.Text3D(title, font=font, s=0.04, justify="bottom-center", c=c)
3701        t.shift(0, n / (m + n) * 1.05)
3702
3703    xlabs = None
3704    if len(xlabels) == m:
3705        xlabs = []
3706        jus = "top-center"
3707        if xrotation > 44:
3708            jus = "right-center"
3709        for i in range(m):
3710            xl = shapes.Text3D(xlabels[i], font=font, s=0.02, justify=jus, c=c).rotate_z(xrotation)
3711            xl.shift((2 * i - m + 1) / (m + n), -n / (m + n) * 1.05)
3712            xlabs.append(xl)
3713
3714    ylabs = None
3715    if len(ylabels) == n:
3716        ylabels = list(reversed(ylabels))
3717        ylabs = []
3718        for i in range(n):
3719            yl = shapes.Text3D(ylabels[i], font=font, s=0.02, justify="right-center", c=c)
3720            yl.shift(-m / (m + n) * 1.05, (2 * i - n + 1) / (m + n))
3721            ylabs.append(yl)
3722
3723    xt = None
3724    if xtitle:
3725        xt = shapes.Text3D(xtitle, font=font, s=0.035, justify="top-center", c=c)
3726        xt.shift(0, -n / (m + n) * 1.05)
3727        if xlabs is not None:
3728            y0, y1 = xlabs[0].ybounds()
3729            xt.shift(0, -(y1 - y0) - 0.55 / (m + n))
3730    yt = None
3731    if ytitle:
3732        yt = shapes.Text3D(ytitle, font=font, s=0.035, justify="bottom-center", c=c).rotate_z(90)
3733        yt.shift(-m / (m + n) * 1.05, 0)
3734        if ylabs is not None:
3735            x0, x1 = ylabs[0].xbounds()
3736            yt.shift(-(x1 - x0) - 0.55 / (m + n), 0)
3737    asse = Assembly(gr, sbar, labs, t, xt, yt, xlabs, ylabs)
3738    asse.name = "Matrix"
3739    return asse
3740
3741
3742def CornerPlot(points, pos=1, s=0.2, title="", c="b", bg="k", lines=True, dots=True):
3743    """
3744    Return a `vtkXYPlotActor` that is a plot of `x` versus `y`,
3745    where `points` is a list of `(x,y)` points.
3746
3747    Assign position following this convention:
3748
3749        - 1, topleft,
3750        - 2, topright,
3751        - 3, bottomleft,
3752        - 4, bottomright.
3753    """
3754    if len(points) == 2:  # passing [allx, ally]
3755        points = np.stack((points[0], points[1]), axis=1)
3756
3757    c = colors.get_color(c)  # allow different codings
3758    array_x = vtki.vtkFloatArray()
3759    array_y = vtki.vtkFloatArray()
3760    array_x.SetNumberOfTuples(len(points))
3761    array_y.SetNumberOfTuples(len(points))
3762    for i, p in enumerate(points):
3763        array_x.InsertValue(i, p[0])
3764        array_y.InsertValue(i, p[1])
3765    field = vtki.vtkFieldData()
3766    field.AddArray(array_x)
3767    field.AddArray(array_y)
3768    data = vtki.vtkDataObject()
3769    data.SetFieldData(field)
3770
3771    xyplot = vtki.new("XYPlotActor")
3772    xyplot.AddDataObjectInput(data)
3773    xyplot.SetDataObjectXComponent(0, 0)
3774    xyplot.SetDataObjectYComponent(0, 1)
3775    xyplot.SetXValuesToValue()
3776    xyplot.SetAdjustXLabels(0)
3777    xyplot.SetAdjustYLabels(0)
3778    xyplot.SetNumberOfXLabels(3)
3779
3780    xyplot.GetProperty().SetPointSize(5)
3781    xyplot.GetProperty().SetLineWidth(2)
3782    xyplot.GetProperty().SetColor(colors.get_color(bg))
3783    xyplot.SetPlotColor(0, c[0], c[1], c[2])
3784
3785    xyplot.SetXTitle(title)
3786    xyplot.SetYTitle("")
3787    xyplot.ExchangeAxesOff()
3788    xyplot.SetPlotPoints(dots)
3789
3790    if not lines:
3791        xyplot.PlotLinesOff()
3792
3793    if isinstance(pos, str):
3794        spos = 2
3795        if "top" in pos:
3796            if "left" in pos:
3797                spos = 1
3798            elif "right" in pos:
3799                spos = 2
3800        elif "bottom" in pos:
3801            if "left" in pos:
3802                spos = 3
3803            elif "right" in pos:
3804                spos = 4
3805        pos = spos
3806    if pos == 1:
3807        xyplot.GetPositionCoordinate().SetValue(0.0, 0.8, 0)
3808    elif pos == 2:
3809        xyplot.GetPositionCoordinate().SetValue(0.76, 0.8, 0)
3810    elif pos == 3:
3811        xyplot.GetPositionCoordinate().SetValue(0.0, 0.0, 0)
3812    elif pos == 4:
3813        xyplot.GetPositionCoordinate().SetValue(0.76, 0.0, 0)
3814    else:
3815        xyplot.GetPositionCoordinate().SetValue(pos[0], pos[1], 0)
3816
3817    xyplot.GetPosition2Coordinate().SetValue(s, s, 0)
3818    return xyplot
3819
3820
3821def CornerHistogram(
3822    values,
3823    bins=20,
3824    vrange=None,
3825    minbin=0,
3826    logscale=False,
3827    title="",
3828    c="g",
3829    bg="k",
3830    alpha=1,
3831    pos="bottom-left",
3832    s=0.175,
3833    lines=True,
3834    dots=False,
3835    nmax=None,
3836):
3837    """
3838    Build a histogram from a list of values in n bins.
3839    The resulting object is a 2D actor.
3840
3841    Use `vrange` to restrict the range of the histogram.
3842
3843    Use `nmax` to limit the sampling to this max nr of entries
3844
3845    Use `pos` to assign its position:
3846        - 1, topleft,
3847        - 2, topright,
3848        - 3, bottomleft,
3849        - 4, bottomright,
3850        - (x, y), as fraction of the rendering window
3851    """
3852    if hasattr(values, "dataset"):
3853        values = utils.vtk2numpy(values.dataset.GetPointData().GetScalars())
3854
3855    n = values.shape[0]
3856    if nmax and nmax < n:
3857        # subsample:
3858        idxs = np.linspace(0, n, num=int(nmax), endpoint=False).astype(int)
3859        values = values[idxs]
3860
3861    fs, edges = np.histogram(values, bins=bins, range=vrange)
3862
3863    if minbin:
3864        fs = fs[minbin:-1]
3865    if logscale:
3866        fs = np.log10(fs + 1)
3867    pts = []
3868    for i in range(len(fs)):
3869        pts.append([(edges[i] + edges[i + 1]) / 2, fs[i]])
3870
3871    cplot = CornerPlot(pts, pos, s, title, c, bg, lines, dots)
3872    cplot.SetNumberOfYLabels(2)
3873    cplot.SetNumberOfXLabels(3)
3874    tprop = vtki.vtkTextProperty()
3875    tprop.SetColor(colors.get_color(bg))
3876    tprop.SetFontFamily(vtki.VTK_FONT_FILE)
3877    tprop.SetFontFile(utils.get_font_path("Calco"))
3878    tprop.SetOpacity(alpha)
3879    cplot.SetAxisTitleTextProperty(tprop)
3880    cplot.GetProperty().SetOpacity(alpha)
3881    cplot.GetXAxisActor2D().SetLabelTextProperty(tprop)
3882    cplot.GetXAxisActor2D().SetTitleTextProperty(tprop)
3883    cplot.GetXAxisActor2D().SetFontFactor(0.55)
3884    cplot.GetYAxisActor2D().SetLabelFactor(0.0)
3885    cplot.GetYAxisActor2D().LabelVisibilityOff()
3886    return cplot
3887
3888
3889class DirectedGraph(Assembly):
3890    """
3891    Support for Directed Graphs.
3892    """
3893
3894    def __init__(self, **kargs):
3895        """
3896        A graph consists of a collection of nodes (without postional information)
3897        and a collection of edges connecting pairs of nodes.
3898        The task is to determine the node positions only based on their connections.
3899
3900        This class is derived from class `Assembly`, and it assembles 4 Mesh objects
3901        representing the graph, the node labels, edge labels and edge arrows.
3902
3903        Arguments:
3904            c : (color)
3905                Color of the Graph
3906            n : (int)
3907                number of the initial set of nodes
3908            layout : (int, str)
3909                layout in
3910                `['2d', 'fast2d', 'clustering2d', 'circular', 'circular3d', 'cone', 'force', 'tree']`.
3911                Each of these layouts has different available options.
3912
3913        ---------------------------------------------------------------
3914        .. note:: Options for layouts '2d', 'fast2d' and 'clustering2d'
3915
3916        Arguments:
3917            seed : (int)
3918                seed of the random number generator used to jitter point positions
3919            rest_distance : (float)
3920                manually set the resting distance
3921            nmax : (int)
3922                the maximum number of iterations to be used
3923            zrange : (list)
3924                expand 2d graph along z axis.
3925
3926        ---------------------------------------------------------------
3927        .. note:: Options for layouts 'circular', and 'circular3d':
3928
3929        Arguments:
3930            radius : (float)
3931                set the radius of the circles
3932            height : (float)
3933                set the vertical (local z) distance between the circles
3934            zrange : (float)
3935                expand 2d graph along z axis
3936
3937        ---------------------------------------------------------------
3938        .. note:: Options for layout 'cone'
3939
3940        Arguments:
3941            compactness : (float)
3942                ratio between the average width of a cone in the tree,
3943                and the height of the cone.
3944            compression : (bool)
3945                put children closer together, possibly allowing sub-trees to overlap.
3946                This is useful if the tree is actually the spanning tree of a graph.
3947            spacing : (float)
3948                space between layers of the tree
3949
3950        ---------------------------------------------------------------
3951        .. note:: Options for layout 'force'
3952
3953        Arguments:
3954            seed : (int)
3955                seed the random number generator used to jitter point positions
3956            bounds : (list)
3957                set the region in space in which to place the final graph
3958            nmax : (int)
3959                the maximum number of iterations to be used
3960            three_dimensional : (bool)
3961                allow optimization in the 3rd dimension too
3962            random_initial_points : (bool)
3963                use random positions within the graph bounds as initial points
3964
3965        Examples:
3966            - [lineage_graph.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/lineage_graph.py)
3967
3968                ![](https://vedo.embl.es/images/pyplot/graph_lineage.png)
3969
3970            - [graph_network.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/graph_network.py)
3971
3972                ![](https://vedo.embl.es/images/pyplot/graph_network.png)
3973        """
3974
3975        super().__init__()
3976
3977        self.nodes = []
3978        self.edges = []
3979
3980        self._node_labels = []  # holds strings
3981        self._edge_labels = []
3982        self.edge_orientations = []
3983        self.edge_glyph_position = 0.6
3984
3985        self.zrange = 0.0
3986
3987        self.rotX = 0
3988        self.rotY = 0
3989        self.rotZ = 0
3990
3991        self.arrow_scale = 0.15
3992        self.node_label_scale = None
3993        self.node_label_justify = "bottom-left"
3994
3995        self.edge_label_scale = None
3996
3997        self.mdg = vtki.new("MutableDirectedGraph")
3998
3999        n = kargs.pop("n", 0)
4000        for _ in range(n):
4001            self.add_node()
4002
4003        self._c = kargs.pop("c", (0.3, 0.3, 0.3))
4004
4005        self.gl = vtki.new("GraphLayout")
4006
4007        self.font = kargs.pop("font", "")
4008
4009        s = kargs.pop("layout", "2d")
4010        if isinstance(s, int):
4011            ss = ["2d", "fast2d", "clustering2d", "circular", "circular3d", "cone", "force", "tree"]
4012            s = ss[s]
4013        self.layout = s
4014
4015        if "2d" in s:
4016            if "clustering" in s:
4017                self.strategy = vtki.new("Clustering2DLayoutStrategy")
4018            elif "fast" in s:
4019                self.strategy = vtki.new("Fast2DLayoutStrategy")
4020            else:
4021                self.strategy = vtki.new("Simple2DLayoutStrategy")
4022            self.rotX = 180
4023            opt = kargs.pop("rest_distance", None)
4024            if opt is not None:
4025                self.strategy.SetRestDistance(opt)
4026            opt = kargs.pop("seed", None)
4027            if opt is not None:
4028                self.strategy.SetRandomSeed(opt)
4029            opt = kargs.pop("nmax", None)
4030            if opt is not None:
4031                self.strategy.SetMaxNumberOfIterations(opt)
4032            self.zrange = kargs.pop("zrange", 0)
4033
4034        elif "circ" in s:
4035            if "3d" in s:
4036                self.strategy = vtki.new("Simple3DCirclesStrategy")
4037                self.strategy.SetDirection(0, 0, -1)
4038                self.strategy.SetAutoHeight(True)
4039                self.strategy.SetMethod(1)
4040                self.rotX = -90
4041                opt = kargs.pop("radius", None)  # float
4042                if opt is not None:
4043                    self.strategy.SetMethod(0)
4044                    self.strategy.SetRadius(opt)  # float
4045                opt = kargs.pop("height", None)
4046                if opt is not None:
4047                    self.strategy.SetAutoHeight(False)
4048                    self.strategy.SetHeight(opt)  # float
4049            else:
4050                self.strategy = vtki.new("CircularLayoutStrategy")
4051                self.zrange = kargs.pop("zrange", 0)
4052
4053        elif "cone" in s:
4054            self.strategy = vtki.new("ConeLayoutStrategy")
4055            self.rotX = 180
4056            opt = kargs.pop("compactness", None)
4057            if opt is not None:
4058                self.strategy.SetCompactness(opt)
4059            opt = kargs.pop("compression", None)
4060            if opt is not None:
4061                self.strategy.SetCompression(opt)
4062            opt = kargs.pop("spacing", None)
4063            if opt is not None:
4064                self.strategy.SetSpacing(opt)
4065
4066        elif "force" in s:
4067            self.strategy = vtki.new("ForceDirectedLayoutStrategy")
4068            opt = kargs.pop("seed", None)
4069            if opt is not None:
4070                self.strategy.SetRandomSeed(opt)
4071            opt = kargs.pop("bounds", None)
4072            if opt is not None:
4073                self.strategy.SetAutomaticBoundsComputation(False)
4074                self.strategy.SetGraphBounds(opt)  # list
4075            opt = kargs.pop("nmax", None)
4076            if opt is not None:
4077                self.strategy.SetMaxNumberOfIterations(opt)  # int
4078            opt = kargs.pop("three_dimensional", True)
4079            if opt is not None:
4080                self.strategy.SetThreeDimensionalLayout(opt)  # bool
4081            opt = kargs.pop("random_initial_points", None)
4082            if opt is not None:
4083                self.strategy.SetRandomInitialPoints(opt)  # bool
4084
4085        elif "tree" in s:
4086            self.strategy = vtki.new("SpanTreeLayoutStrategy")
4087            self.rotX = 180
4088
4089        else:
4090            vedo.logger.error(f"Cannot understand layout {s}. Available layouts:")
4091            vedo.logger.error("[2d,fast2d,clustering2d,circular,circular3d,cone,force,tree]")
4092            raise RuntimeError()
4093
4094        self.gl.SetLayoutStrategy(self.strategy)
4095
4096        if len(kargs) > 0:
4097            vedo.logger.error(f"Cannot understand options: {kargs}")
4098
4099    def add_node(self, label="id") -> int:
4100        """Add a new node to the `Graph`."""
4101        v = self.mdg.AddVertex()  # vtk calls it vertex..
4102        self.nodes.append(v)
4103        if label == "id":
4104            label = int(v)
4105        self._node_labels.append(str(label))
4106        return v
4107
4108    def add_edge(self, v1, v2, label="") -> int:
4109        """Add a new edge between to nodes.
4110        An extra node is created automatically if needed."""
4111        nv = len(self.nodes)
4112        if v1 >= nv:
4113            for _ in range(nv, v1 + 1):
4114                self.add_node()
4115        nv = len(self.nodes)
4116        if v2 >= nv:
4117            for _ in range(nv, v2 + 1):
4118                self.add_node()
4119        e = self.mdg.AddEdge(v1, v2)
4120        self.edges.append(e)
4121        self._edge_labels.append(str(label))
4122        return e
4123
4124    def add_child(self, v, node_label="id", edge_label="") -> int:
4125        """Add a new edge to a new node as its child.
4126        The extra node is created automatically if needed."""
4127        nv = len(self.nodes)
4128        if v >= nv:
4129            for _ in range(nv, v + 1):
4130                self.add_node()
4131        child = self.mdg.AddChild(v)
4132        self.edges.append((v, child))
4133        self.nodes.append(child)
4134        if node_label == "id":
4135            node_label = int(child)
4136        self._node_labels.append(str(node_label))
4137        self._edge_labels.append(str(edge_label))
4138        return child
4139
4140    def build(self):
4141        """
4142        Build the `DirectedGraph(Assembly)`.
4143        Accessory objects are also created for labels and arrows.
4144        """
4145        self.gl.SetZRange(self.zrange)
4146        self.gl.SetInputData(self.mdg)
4147        self.gl.Update()
4148
4149        gr2poly = vtki.new("GraphToPolyData")
4150        gr2poly.EdgeGlyphOutputOn()
4151        gr2poly.SetEdgeGlyphPosition(self.edge_glyph_position)
4152        gr2poly.SetInputData(self.gl.GetOutput())
4153        gr2poly.Update()
4154
4155        dgraph = Mesh(gr2poly.GetOutput(0))
4156        # dgraph.clean() # WRONG!!! dont uncomment
4157        dgraph.flat().color(self._c).lw(2)
4158        dgraph.name = "DirectedGraph"
4159
4160        diagsz = self.diagonal_size() / 1.42
4161        if not diagsz:
4162            return None
4163
4164        dgraph.scale(1 / diagsz)
4165        if self.rotX:
4166            dgraph.rotate_x(self.rotX)
4167        if self.rotY:
4168            dgraph.rotate_y(self.rotY)
4169        if self.rotZ:
4170            dgraph.rotate_z(self.rotZ)
4171
4172        vecs = gr2poly.GetOutput(1).GetPointData().GetVectors()
4173        self.edge_orientations = utils.vtk2numpy(vecs)
4174
4175        # Use Glyph3D to repeat the glyph on all edges.
4176        arrows = None
4177        if self.arrow_scale:
4178            arrow_source = vtki.new("GlyphSource2D")
4179            arrow_source.SetGlyphTypeToEdgeArrow()
4180            arrow_source.SetScale(self.arrow_scale)
4181            arrow_source.Update()
4182            arrow_glyph = vtki.vtkGlyph3D()
4183            arrow_glyph.SetInputData(0, gr2poly.GetOutput(1))
4184            arrow_glyph.SetInputData(1, arrow_source.GetOutput())
4185            arrow_glyph.Update()
4186            arrows = Mesh(arrow_glyph.GetOutput())
4187            arrows.scale(1 / diagsz)
4188            arrows.lighting("off").color(self._c)
4189            if self.rotX:
4190                arrows.rotate_x(self.rotX)
4191            if self.rotY:
4192                arrows.rotate_y(self.rotY)
4193            if self.rotZ:
4194                arrows.rotate_z(self.rotZ)
4195            arrows.name = "DirectedGraphArrows"
4196
4197        node_labels = None
4198        if self._node_labels:
4199            node_labels = dgraph.labels(
4200                self._node_labels,
4201                scale=self.node_label_scale,
4202                precision=0,
4203                font=self.font,
4204                justify=self.node_label_justify,
4205            )
4206            node_labels.color(self._c).pickable(True)
4207            node_labels.name = "DirectedGraphNodeLabels"
4208
4209        edge_labels = None
4210        if self._edge_labels:
4211            edge_labels = dgraph.labels(
4212                self._edge_labels, on="cells", scale=self.edge_label_scale, precision=0, font=self.font
4213            )
4214            edge_labels.color(self._c).pickable(True)
4215            edge_labels.name = "DirectedGraphEdgeLabels"
4216
4217        super().__init__([dgraph, node_labels, edge_labels, arrows])
4218        self.name = "DirectedGraphAssembly"
4219        return self
class Figure(vedo.assembly.Assembly):
 59class Figure(Assembly):
 60    """Format class for figures."""
 61
 62    def __init__(self, xlim, ylim, aspect=4 / 3, padding=(0.05, 0.05, 0.05, 0.05), **kwargs):
 63        """
 64        Create an empty formatted figure for plotting.
 65
 66        Arguments:
 67            xlim : (list)
 68                range of the x-axis as [x0, x1]
 69            ylim : (list)
 70                range of the y-axis as [y0, y1]
 71            aspect : (float, str)
 72                the desired aspect ratio of the histogram. Default is 4/3.
 73                Use `aspect="equal"` to force the same units in x and y.
 74            padding : (float, list)
 75                keep a padding space from the axes (as a fraction of the axis size).
 76                This can be a list of four numbers.
 77            xtitle : (str)
 78                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
 79            ytitle : (str)
 80                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
 81            grid : (bool)
 82                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
 83            axes : (dict)
 84                an extra dictionary of options for the `vedo.addons.Axes` object
 85        """
 86
 87        self.verbose = True  # printing to stdout on every mouse click
 88
 89        self.xlim = np.asarray(xlim)
 90        self.ylim = np.asarray(ylim)
 91        self.aspect = aspect
 92        self.padding = padding
 93        if not utils.is_sequence(self.padding):
 94            self.padding = [self.padding, self.padding, self.padding, self.padding]
 95
 96        self.force_scaling_types = (
 97            shapes.Glyph,
 98            shapes.Line,
 99            shapes.Rectangle,
100            shapes.DashedLine,
101            shapes.Tube,
102            shapes.Ribbon,
103            shapes.GeoCircle,
104            shapes.Arc,
105            shapes.Grid,
106            # shapes.Arrows, # todo
107            # shapes.Arrows2D, # todo
108            shapes.Brace,  # todo
109        )
110
111        options = dict(kwargs)
112
113        self.title  = options.pop("title", "")
114        self.xtitle = options.pop("xtitle", " ")
115        self.ytitle = options.pop("ytitle", " ")
116        number_of_divisions = 6
117
118        self.legend = None
119        self.labels = []
120        self.label = options.pop("label", None)
121        if self.label:
122            self.labels = [self.label]
123
124        self.axopts = options.pop("axes", {})
125        if isinstance(self.axopts, (bool, int, float)):
126            if self.axopts:
127                self.axopts = {}
128        if self.axopts or isinstance(self.axopts, dict):
129            number_of_divisions = self.axopts.pop("number_of_divisions", number_of_divisions)
130
131            self.axopts["xtitle"] = self.xtitle
132            self.axopts["ytitle"] = self.ytitle
133
134            if "xygrid" not in self.axopts:  ## modify the default
135                self.axopts["xygrid"] = options.pop("grid", False)
136
137            if "xygrid_transparent" not in self.axopts:  ## modify the default
138                self.axopts["xygrid_transparent"] = True
139
140            if "xtitle_position" not in self.axopts:  ## modify the default
141                self.axopts["xtitle_position"] = 0.5
142                self.axopts["xtitle_justify"] = "top-center"
143
144            if "ytitle_position" not in self.axopts:  ## modify the default
145                self.axopts["ytitle_position"] = 0.5
146                self.axopts["ytitle_justify"] = "bottom-center"
147
148            if self.label:
149                if "c" in self.axopts:
150                    self.label.tcolor = self.axopts["c"]
151
152        x0, x1 = self.xlim
153        y0, y1 = self.ylim
154        dx = x1 - x0
155        dy = y1 - y0
156        x0lim, x1lim = (x0 - self.padding[0] * dx, x1 + self.padding[1] * dx)
157        y0lim, y1lim = (y0 - self.padding[2] * dy, y1 + self.padding[3] * dy)
158        dy = y1lim - y0lim
159
160        self.axes = None
161        if xlim[0] >= xlim[1] or ylim[0] >= ylim[1]:
162            vedo.logger.warning(f"Null range for Figure {self.title}... returning an empty Assembly.")
163            super().__init__()
164            self.yscale = 0
165            return
166
167        if aspect == "equal":
168            self.aspect = dx / dy  # so that yscale becomes 1
169
170        self.yscale = dx / dy / self.aspect
171
172        y0lim *= self.yscale
173        y1lim *= self.yscale
174
175        self.x0lim = x0lim
176        self.x1lim = x1lim
177        self.y0lim = y0lim
178        self.y1lim = y1lim
179
180        self.ztolerance = options.pop("ztolerance", None)
181        if self.ztolerance is None:
182            self.ztolerance = dx / 5000
183
184        ############## create axes
185        if self.axopts:
186            axes_opts = self.axopts
187            if self.axopts is True or self.axopts == 1:
188                axes_opts = {}
189
190            tp, ts = utils.make_ticks(y0lim / self.yscale, 
191                                      y1lim / self.yscale, number_of_divisions)
192            labs = []
193            for i in range(1, len(tp) - 1):
194                ynew = utils.lin_interpolate(tp[i], [0, 1], [y0lim, y1lim])
195                labs.append([ynew, ts[i]])
196
197            if self.title:
198                axes_opts["htitle"] = self.title
199            axes_opts["y_values_and_labels"] = labs
200            axes_opts["xrange"] = (x0lim, x1lim)
201            axes_opts["yrange"] = (y0lim, y1lim)
202            axes_opts["zrange"] = (0, 0)
203            axes_opts["y_use_bounds"] = True
204
205            if "c" not in axes_opts and "ac" in options:
206                axes_opts["c"] = options["ac"]
207
208            self.axes = addons.Axes(**axes_opts)
209
210        super().__init__([self.axes])
211        self.name = "Figure"
212
213        vedo.last_figure = self if settings.remember_last_figure_format else None
214
215
216    ##################################################################
217    def _repr_html_(self):
218        """
219        HTML representation of the Figure object for Jupyter Notebooks.
220
221        Returns:
222            HTML text with the image and some properties.
223        """
224        import io
225        import base64
226        from PIL import Image
227
228        library_name = "vedo.pyplot.Figure"
229        help_url = "https://vedo.embl.es/docs/vedo/pyplot.html#Figure"
230
231        arr = self.thumbnail(zoom=1.1)
232
233        im = Image.fromarray(arr)
234        buffered = io.BytesIO()
235        im.save(buffered, format="PNG", quality=100)
236        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
237        url = "data:image/png;base64," + encoded
238        image = f"<img src='{url}'></img>"
239
240        bounds = "<br/>".join(
241            [
242                vedo.utils.precision(min_x, 4) + " ... " + vedo.utils.precision(max_x, 4)
243                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
244            ]
245        )
246
247        help_text = ""
248        if self.name:
249            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
250        help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>"
251        if self.filename:
252            dots = ""
253            if len(self.filename) > 30:
254                dots = "..."
255            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"
256
257        all = [
258            "<table>",
259            "<tr>",
260            "<td>",
261            image,
262            "</td>",
263            "<td style='text-align: center; vertical-align: center;'><br/>",
264            help_text,
265            "<table>",
266            "<tr><td><b> nr. of parts </b></td><td>" + str(self.GetNumberOfPaths()) + "</td></tr>",
267            "<tr><td><b> position </b></td><td>" + str(self.GetPosition()) + "</td></tr>",
268            "<tr><td><b> x-limits </b></td><td>" + utils.precision(self.xlim, 4) + "</td></tr>",
269            "<tr><td><b> y-limits </b></td><td>" + utils.precision(self.ylim, 4) + "</td></tr>",
270            "<tr><td><b> world bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>",
271            "</table>",
272            "</table>",
273        ]
274        return "\n".join(all)
275
276    def __add__(self, *obj):
277        # just to avoid confusion, supersede Assembly.__add__
278        return self.__iadd__(*obj)
279
280    def __iadd__(self, *obj):
281        if len(obj) == 1 and isinstance(obj[0], Figure):
282            return self._check_unpack_and_insert(obj[0])
283
284        obj = utils.flatten(obj)
285        return self.insert(*obj)
286
287    def _check_unpack_and_insert(self, fig: "Figure") -> Self:
288
289        if fig.label:
290            self.labels.append(fig.label)
291
292        if abs(self.yscale - fig.yscale) > 0.0001:
293
294            colors.printc(":bomb:ERROR: adding incompatible Figure. Y-scales are different:", c='r', invert=True)
295            colors.printc("  first  figure:", self.yscale, c='r')
296            colors.printc("  second figure:", fig.yscale, c='r')
297
298            colors.printc("One or more of these parameters can be the cause:", c="r")
299            if list(self.xlim) != list(fig.xlim):
300                colors.printc("xlim --------------------------------------------\n",
301                              " first  figure:", self.xlim, "\n",
302                              " second figure:", fig.xlim, c='r')
303            if list(self.ylim) != list(fig.ylim):
304                colors.printc("ylim --------------------------------------------\n",
305                              " first  figure:", self.ylim, "\n",
306                              " second figure:", fig.ylim, c='r')
307            if list(self.padding) != list(fig.padding):
308                colors.printc("padding -----------------------------------------\n",
309                              " first  figure:", self.padding,
310                              " second figure:", fig.padding, c='r')
311            if self.aspect != fig.aspect:
312                colors.printc("aspect ------------------------------------------\n",
313                              " first  figure:", self.aspect, "\n",
314                              " second figure:", fig.aspect, c='r')
315
316            colors.printc("\n:idea: Consider using fig2 = histogram(..., like=fig1)", c="r")
317            colors.printc(" Or fig += histogram(..., like=fig)\n", c="r")
318            return self
319
320        offset = self.zbounds()[1] + self.ztolerance
321
322        for ele in fig.unpack():
323            if "Axes" in ele.name:
324                continue
325            ele.z(offset)
326            self.insert(ele, rescale=False)
327
328        return self
329
330    def insert(self, *objs, rescale=True, as3d=True, adjusted=False, cut=True) -> Self:
331        """
332        Insert objects into a Figure.
333
334        The recommended syntax is to use "+=", which calls `insert()` under the hood.
335        If a whole Figure is added with "+=", it is unpacked and its objects are added
336        one by one.
337
338        Arguments:
339            rescale : (bool)
340                rescale the y axis position while inserting the object.
341            as3d : (bool)
342                if True keep the aspect ratio of the 3d object, otherwise stretch it in y.
343            adjusted : (bool)
344                adjust the scaling according to the shortest axis
345            cut : (bool)
346                cut off the parts of the object which go beyond the axes frame.
347        """
348        for a in objs:
349
350            if a in self.objects:
351                # should not add twice the same object in plot
352                continue
353
354            if isinstance(a, vedo.Points):  # hacky way to identify Points
355                if a.ncells == a.npoints:
356                    poly = a.dataset
357                    if poly.GetNumberOfPolys() == 0 and poly.GetNumberOfLines() == 0:
358                        as3d = False
359                        rescale = True
360
361            if isinstance(a, (shapes.Arrow, shapes.Arrow2D)):
362                # discard input Arrow and substitute it with a brand new one
363                # (because scaling would fatally distort the shape)
364
365                py = a.base[1]
366                a.top[1] = (a.top[1] - py) * self.yscale + py
367                b = shapes.Arrow2D(a.base, a.top, s=a.s, fill=a.fill).z(a.z())
368
369                prop = a.properties
370                prop.LightingOff()
371                b.actor.SetProperty(prop)
372                b.properties = prop
373                b.y(py * self.yscale)
374                a = b
375
376            # elif isinstance(a, shapes.Rectangle) and a.radius is not None:
377            #     # discard input Rectangle and substitute it with a brand new one
378            #     # (because scaling would fatally distort the shape of the corners)
379            #     py = a.corner1[1]
380            #     rx1,ry1,rz1 = a.corner1
381            #     rx2,ry2,rz2 = a.corner2
382            #     ry2 = (ry2-py) * self.yscale + py
383            #     b = shapes.Rectangle([rx1,0,rz1], [rx2,ry2,rz2], radius=a.radius).z(a.z())
384            #     b.SetProperty(a.properties)
385            #     b.y(py / self.yscale)
386            #     a = b
387
388            else:
389
390                if rescale:
391
392                    if not isinstance(a, Figure):
393
394                        if as3d and not isinstance(a, self.force_scaling_types):
395                            if adjusted:
396                                scl = np.min([1, self.yscale])
397                            else:
398                                scl = self.yscale
399
400                            a.scale(scl)
401
402                        else:
403                            a.scale([1, self.yscale, 1])
404
405                    # shift it in y
406                    a.y(a.y() * self.yscale)
407
408            if cut:
409                try:
410                    bx0, bx1, by0, by1, _, _ = a.bounds()
411                    if self.y0lim > by0:
412                        a.cut_with_plane([0, self.y0lim, 0], [0, 1, 0])
413                    if self.y1lim < by1:
414                        a.cut_with_plane([0, self.y1lim, 0], [0, -1, 0])
415                    if self.x0lim > bx0:
416                        a.cut_with_plane([self.x0lim, 0, 0], [1, 0, 0])
417                    if self.x1lim < bx1:
418                        a.cut_with_plane([self.x1lim, 0, 0], [-1, 0, 0])
419                except:
420                    # print("insert(): cannot cut", [a])
421                    pass
422
423            self.AddPart(a.actor)
424            self.objects.append(a)
425
426        return self
427
428    def add_label(self, text: str, c=None, marker="", mc="black") -> Self:
429        """
430        Manually add en entry label to the legend.
431
432        Arguments:
433            text : (str)
434                text string for the label.
435            c : (str)
436                color of the text
437            marker : (str), Mesh
438                a marker char or a Mesh object to be used as marker
439            mc : (str)
440                color for the marker
441        """
442        newlabel = LabelData()
443        newlabel.text = text.replace("\n", " ")
444        newlabel.tcolor = c
445        newlabel.marker = marker
446        newlabel.mcolor = mc
447        self.labels.append(newlabel)
448        return self
449
450    def add_legend(
451        self,
452        pos="top-right",
453        relative=True,
454        font=None,
455        s=1,
456        c=None,
457        vspace=1.75,
458        padding=0.1,
459        radius=0,
460        alpha=1,
461        bc="k7",
462        lw=1,
463        lc="k4",
464        z=0,
465    ) -> Self:
466        """
467        Add existing labels to form a legend box.
468        Labels have been previously filled with eg: `plot(..., label="text")`
469
470        Arguments:
471            pos : (str, list)
472                A string or 2D coordinates. The default is "top-right".
473            relative : (bool)
474                control whether `pos` is absolute or relative, e.i. normalized
475                to the x and y ranges so that x and y in `pos=[x,y]` should be
476                both in the range [0,1].
477                This flag is ignored if a string despcriptor is passed.
478                Default is True.
479            font : (str, int)
480                font name or number.
481                Check [available fonts here](https://vedo.embl.es/fonts).
482            s : (float)
483                global size of the legend
484            c : (str)
485                color of the text
486            vspace : (float)
487                vertical spacing of lines
488            padding : (float)
489                padding of the box as a fraction of the text size
490            radius : (float)
491                border radius of the box
492            alpha : (float)
493                opacity of the box. Values below 1 may cause poor rendering
494                because of antialiasing.
495                Use alpha = 0 to remove the box.
496            bc : (str)
497                box color
498            lw : (int)
499                border line width of the box in pixel units
500            lc : (int)
501                border line color of the box
502            z : (float)
503                set the zorder as z position (useful to avoid overlap)
504        """
505        sx = self.x1lim - self.x0lim
506        s = s * sx / 55  # so that input can be about 1
507
508        ds = 0
509        texts = []
510        mks = []
511        for i, t in enumerate(self.labels):
512            label = self.labels[i]
513            t = label.text
514
515            if label.tcolor is not None:
516                c = label.tcolor
517
518            tx = vedo.shapes.Text3D(t, s=s, c=c, justify="center-left", font=font)
519            y0, y1 = tx.ybounds()
520            ds = max(y1 - y0, ds)
521            texts.append(tx)
522
523            mk = label.marker
524            if isinstance(mk, vedo.Points):
525                mk = mk.clone(deep=False).lighting("off")
526                cm = mk.center_of_mass()
527                ty0, ty1 = tx.ybounds()
528                oby0, oby1 = mk.ybounds()
529                mk.shift(-cm)
530                mk.SetOrigin(cm)
531                mk.scale((ty1 - ty0) / (oby1 - oby0))
532                mk.scale([1.1, 1.1, 0.01])
533            elif mk == "-":
534                mk = vedo.shapes.Marker(mk, s=s * 2)
535                mk.color(label.mcolor)
536            else:
537                mk = vedo.shapes.Marker(mk, s=s)
538                mk.color(label.mcolor)
539            mks.append(mk)
540
541        for i, tx in enumerate(texts):
542            tx.shift(0, -(i + 0) * ds * vspace)
543
544        for i, mk in enumerate(mks):
545            mk.shift(-ds * 1.75, -(i + 0) * ds * vspace, 0)
546
547        acts = texts + mks
548
549        aleg = Assembly(acts)  # .show(axes=1).close()
550        x0, x1, y0, y1, _, _ = aleg.GetBounds()
551
552        if alpha:
553            dx = x1 - x0
554            dy = y1 - y0
555
556            if not utils.is_sequence(padding):
557                padding = [padding] * 4
558            padding = min(padding)
559            padding = min(padding * dx, padding * dy)
560            if len(self.labels) == 1:
561                padding *= 4
562            x0 -= padding
563            x1 += padding
564            y0 -= padding
565            y1 += padding
566
567            box = shapes.Rectangle([x0, y0], [x1, y1], radius=radius, c=bc, alpha=alpha)
568            box.shift(0, 0, -dy / 100).pickable(False)
569            if lc:
570                box.lc(lc).lw(lw)
571            aleg.AddPart(box.actor)
572            aleg.objects.append(box)
573
574        xlim = self.xlim
575        ylim = self.ylim
576        if isinstance(pos, str):
577            px, py = 0.0, 0.0
578            rx, ry = (xlim[1] + xlim[0]) / 2, (ylim[1] + ylim[0]) / 2
579            shx, shy = 0.0, 0.0
580            if "top" in pos:
581                if "cent" in pos:
582                    px, py = rx, ylim[1]
583                    shx, shy = (x0 + x1) / 2, y1
584                elif "left" in pos:
585                    px, py = xlim[0], ylim[1]
586                    shx, shy = x0, y1
587                else:  # "right"
588                    px, py = xlim[1], ylim[1]
589                    shx, shy = x1, y1
590            elif "bot" in pos:
591                if "left" in pos:
592                    px, py = xlim[0], ylim[0]
593                    shx, shy = x0, y0
594                elif "right" in pos:
595                    px, py = xlim[1], ylim[0]
596                    shx, shy = x1, y0
597                else:  # "cent"
598                    px, py = rx, ylim[0]
599                    shx, shy = (x0 + x1) / 2, y0
600            elif "cent" in pos:
601                if "left" in pos:
602                    px, py = xlim[0], ry
603                    shx, shy = x0, (y0 + y1) / 2
604                elif "right" in pos:
605                    px, py = xlim[1], ry
606                    shx, shy = x1, (y0 + y1) / 2
607            else:
608                vedo.logger.error(f"in add_legend(), cannot understand {pos}")
609                raise RuntimeError
610
611        else:
612
613            if relative:
614                rx, ry = pos[0], pos[1]
615                px = (xlim[1] - xlim[0]) * rx + xlim[0]
616                py = (ylim[1] - ylim[0]) * ry + ylim[0]
617                z *= xlim[1] - xlim[0]
618            else:
619                px, py = pos[0], pos[1]
620            shx, shy = x0, y1
621
622        zpos = aleg.pos()[2]
623        aleg.pos(px - shx, py * self.yscale - shy, zpos + sx / 50 + z)
624
625        self.insert(aleg, rescale=False, cut=False)
626        self.legend = aleg
627        aleg.name = "Legend"
628        return self

Format class for figures.

Figure( xlim, ylim, aspect=1.3333333333333333, padding=(0.05, 0.05, 0.05, 0.05), **kwargs)
 62    def __init__(self, xlim, ylim, aspect=4 / 3, padding=(0.05, 0.05, 0.05, 0.05), **kwargs):
 63        """
 64        Create an empty formatted figure for plotting.
 65
 66        Arguments:
 67            xlim : (list)
 68                range of the x-axis as [x0, x1]
 69            ylim : (list)
 70                range of the y-axis as [y0, y1]
 71            aspect : (float, str)
 72                the desired aspect ratio of the histogram. Default is 4/3.
 73                Use `aspect="equal"` to force the same units in x and y.
 74            padding : (float, list)
 75                keep a padding space from the axes (as a fraction of the axis size).
 76                This can be a list of four numbers.
 77            xtitle : (str)
 78                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
 79            ytitle : (str)
 80                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
 81            grid : (bool)
 82                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
 83            axes : (dict)
 84                an extra dictionary of options for the `vedo.addons.Axes` object
 85        """
 86
 87        self.verbose = True  # printing to stdout on every mouse click
 88
 89        self.xlim = np.asarray(xlim)
 90        self.ylim = np.asarray(ylim)
 91        self.aspect = aspect
 92        self.padding = padding
 93        if not utils.is_sequence(self.padding):
 94            self.padding = [self.padding, self.padding, self.padding, self.padding]
 95
 96        self.force_scaling_types = (
 97            shapes.Glyph,
 98            shapes.Line,
 99            shapes.Rectangle,
100            shapes.DashedLine,
101            shapes.Tube,
102            shapes.Ribbon,
103            shapes.GeoCircle,
104            shapes.Arc,
105            shapes.Grid,
106            # shapes.Arrows, # todo
107            # shapes.Arrows2D, # todo
108            shapes.Brace,  # todo
109        )
110
111        options = dict(kwargs)
112
113        self.title  = options.pop("title", "")
114        self.xtitle = options.pop("xtitle", " ")
115        self.ytitle = options.pop("ytitle", " ")
116        number_of_divisions = 6
117
118        self.legend = None
119        self.labels = []
120        self.label = options.pop("label", None)
121        if self.label:
122            self.labels = [self.label]
123
124        self.axopts = options.pop("axes", {})
125        if isinstance(self.axopts, (bool, int, float)):
126            if self.axopts:
127                self.axopts = {}
128        if self.axopts or isinstance(self.axopts, dict):
129            number_of_divisions = self.axopts.pop("number_of_divisions", number_of_divisions)
130
131            self.axopts["xtitle"] = self.xtitle
132            self.axopts["ytitle"] = self.ytitle
133
134            if "xygrid" not in self.axopts:  ## modify the default
135                self.axopts["xygrid"] = options.pop("grid", False)
136
137            if "xygrid_transparent" not in self.axopts:  ## modify the default
138                self.axopts["xygrid_transparent"] = True
139
140            if "xtitle_position" not in self.axopts:  ## modify the default
141                self.axopts["xtitle_position"] = 0.5
142                self.axopts["xtitle_justify"] = "top-center"
143
144            if "ytitle_position" not in self.axopts:  ## modify the default
145                self.axopts["ytitle_position"] = 0.5
146                self.axopts["ytitle_justify"] = "bottom-center"
147
148            if self.label:
149                if "c" in self.axopts:
150                    self.label.tcolor = self.axopts["c"]
151
152        x0, x1 = self.xlim
153        y0, y1 = self.ylim
154        dx = x1 - x0
155        dy = y1 - y0
156        x0lim, x1lim = (x0 - self.padding[0] * dx, x1 + self.padding[1] * dx)
157        y0lim, y1lim = (y0 - self.padding[2] * dy, y1 + self.padding[3] * dy)
158        dy = y1lim - y0lim
159
160        self.axes = None
161        if xlim[0] >= xlim[1] or ylim[0] >= ylim[1]:
162            vedo.logger.warning(f"Null range for Figure {self.title}... returning an empty Assembly.")
163            super().__init__()
164            self.yscale = 0
165            return
166
167        if aspect == "equal":
168            self.aspect = dx / dy  # so that yscale becomes 1
169
170        self.yscale = dx / dy / self.aspect
171
172        y0lim *= self.yscale
173        y1lim *= self.yscale
174
175        self.x0lim = x0lim
176        self.x1lim = x1lim
177        self.y0lim = y0lim
178        self.y1lim = y1lim
179
180        self.ztolerance = options.pop("ztolerance", None)
181        if self.ztolerance is None:
182            self.ztolerance = dx / 5000
183
184        ############## create axes
185        if self.axopts:
186            axes_opts = self.axopts
187            if self.axopts is True or self.axopts == 1:
188                axes_opts = {}
189
190            tp, ts = utils.make_ticks(y0lim / self.yscale, 
191                                      y1lim / self.yscale, number_of_divisions)
192            labs = []
193            for i in range(1, len(tp) - 1):
194                ynew = utils.lin_interpolate(tp[i], [0, 1], [y0lim, y1lim])
195                labs.append([ynew, ts[i]])
196
197            if self.title:
198                axes_opts["htitle"] = self.title
199            axes_opts["y_values_and_labels"] = labs
200            axes_opts["xrange"] = (x0lim, x1lim)
201            axes_opts["yrange"] = (y0lim, y1lim)
202            axes_opts["zrange"] = (0, 0)
203            axes_opts["y_use_bounds"] = True
204
205            if "c" not in axes_opts and "ac" in options:
206                axes_opts["c"] = options["ac"]
207
208            self.axes = addons.Axes(**axes_opts)
209
210        super().__init__([self.axes])
211        self.name = "Figure"
212
213        vedo.last_figure = self if settings.remember_last_figure_format else None

Create an empty formatted figure for plotting.

Arguments:
  • xlim : (list) range of the x-axis as [x0, x1]
  • ylim : (list) range of the y-axis as [y0, y1]
  • aspect : (float, str) the desired aspect ratio of the histogram. Default is 4/3. Use aspect="equal" to force the same units in x and y.
  • padding : (float, list) keep a padding space from the axes (as a fraction of the axis size). This can be a list of four numbers.
  • xtitle : (str) title for the x-axis, can also be set using axes=dict(xtitle="my x axis")
  • ytitle : (str) title for the y-axis, can also be set using axes=dict(ytitle="my y axis")
  • grid : (bool) show the background grid for the axes, can also be set using axes=dict(xygrid=True)
  • axes : (dict) an extra dictionary of options for the vedo.addons.Axes object
def insert(self, *objs, rescale=True, as3d=True, adjusted=False, cut=True) -> Self:
330    def insert(self, *objs, rescale=True, as3d=True, adjusted=False, cut=True) -> Self:
331        """
332        Insert objects into a Figure.
333
334        The recommended syntax is to use "+=", which calls `insert()` under the hood.
335        If a whole Figure is added with "+=", it is unpacked and its objects are added
336        one by one.
337
338        Arguments:
339            rescale : (bool)
340                rescale the y axis position while inserting the object.
341            as3d : (bool)
342                if True keep the aspect ratio of the 3d object, otherwise stretch it in y.
343            adjusted : (bool)
344                adjust the scaling according to the shortest axis
345            cut : (bool)
346                cut off the parts of the object which go beyond the axes frame.
347        """
348        for a in objs:
349
350            if a in self.objects:
351                # should not add twice the same object in plot
352                continue
353
354            if isinstance(a, vedo.Points):  # hacky way to identify Points
355                if a.ncells == a.npoints:
356                    poly = a.dataset
357                    if poly.GetNumberOfPolys() == 0 and poly.GetNumberOfLines() == 0:
358                        as3d = False
359                        rescale = True
360
361            if isinstance(a, (shapes.Arrow, shapes.Arrow2D)):
362                # discard input Arrow and substitute it with a brand new one
363                # (because scaling would fatally distort the shape)
364
365                py = a.base[1]
366                a.top[1] = (a.top[1] - py) * self.yscale + py
367                b = shapes.Arrow2D(a.base, a.top, s=a.s, fill=a.fill).z(a.z())
368
369                prop = a.properties
370                prop.LightingOff()
371                b.actor.SetProperty(prop)
372                b.properties = prop
373                b.y(py * self.yscale)
374                a = b
375
376            # elif isinstance(a, shapes.Rectangle) and a.radius is not None:
377            #     # discard input Rectangle and substitute it with a brand new one
378            #     # (because scaling would fatally distort the shape of the corners)
379            #     py = a.corner1[1]
380            #     rx1,ry1,rz1 = a.corner1
381            #     rx2,ry2,rz2 = a.corner2
382            #     ry2 = (ry2-py) * self.yscale + py
383            #     b = shapes.Rectangle([rx1,0,rz1], [rx2,ry2,rz2], radius=a.radius).z(a.z())
384            #     b.SetProperty(a.properties)
385            #     b.y(py / self.yscale)
386            #     a = b
387
388            else:
389
390                if rescale:
391
392                    if not isinstance(a, Figure):
393
394                        if as3d and not isinstance(a, self.force_scaling_types):
395                            if adjusted:
396                                scl = np.min([1, self.yscale])
397                            else:
398                                scl = self.yscale
399
400                            a.scale(scl)
401
402                        else:
403                            a.scale([1, self.yscale, 1])
404
405                    # shift it in y
406                    a.y(a.y() * self.yscale)
407
408            if cut:
409                try:
410                    bx0, bx1, by0, by1, _, _ = a.bounds()
411                    if self.y0lim > by0:
412                        a.cut_with_plane([0, self.y0lim, 0], [0, 1, 0])
413                    if self.y1lim < by1:
414                        a.cut_with_plane([0, self.y1lim, 0], [0, -1, 0])
415                    if self.x0lim > bx0:
416                        a.cut_with_plane([self.x0lim, 0, 0], [1, 0, 0])
417                    if self.x1lim < bx1:
418                        a.cut_with_plane([self.x1lim, 0, 0], [-1, 0, 0])
419                except:
420                    # print("insert(): cannot cut", [a])
421                    pass
422
423            self.AddPart(a.actor)
424            self.objects.append(a)
425
426        return self

Insert objects into a Figure.

The recommended syntax is to use "+=", which calls insert() under the hood. If a whole Figure is added with "+=", it is unpacked and its objects are added one by one.

Arguments:
  • rescale : (bool) rescale the y axis position while inserting the object.
  • as3d : (bool) if True keep the aspect ratio of the 3d object, otherwise stretch it in y.
  • adjusted : (bool) adjust the scaling according to the shortest axis
  • cut : (bool) cut off the parts of the object which go beyond the axes frame.
def add_label(self, text: str, c=None, marker='', mc='black') -> Self:
428    def add_label(self, text: str, c=None, marker="", mc="black") -> Self:
429        """
430        Manually add en entry label to the legend.
431
432        Arguments:
433            text : (str)
434                text string for the label.
435            c : (str)
436                color of the text
437            marker : (str), Mesh
438                a marker char or a Mesh object to be used as marker
439            mc : (str)
440                color for the marker
441        """
442        newlabel = LabelData()
443        newlabel.text = text.replace("\n", " ")
444        newlabel.tcolor = c
445        newlabel.marker = marker
446        newlabel.mcolor = mc
447        self.labels.append(newlabel)
448        return self

Manually add en entry label to the legend.

Arguments:
  • text : (str) text string for the label.
  • c : (str) color of the text
  • marker : (str), Mesh a marker char or a Mesh object to be used as marker
  • mc : (str) color for the marker
def add_legend( self, pos='top-right', relative=True, font=None, s=1, c=None, vspace=1.75, padding=0.1, radius=0, alpha=1, bc='k7', lw=1, lc='k4', z=0) -> Self:
450    def add_legend(
451        self,
452        pos="top-right",
453        relative=True,
454        font=None,
455        s=1,
456        c=None,
457        vspace=1.75,
458        padding=0.1,
459        radius=0,
460        alpha=1,
461        bc="k7",
462        lw=1,
463        lc="k4",
464        z=0,
465    ) -> Self:
466        """
467        Add existing labels to form a legend box.
468        Labels have been previously filled with eg: `plot(..., label="text")`
469
470        Arguments:
471            pos : (str, list)
472                A string or 2D coordinates. The default is "top-right".
473            relative : (bool)
474                control whether `pos` is absolute or relative, e.i. normalized
475                to the x and y ranges so that x and y in `pos=[x,y]` should be
476                both in the range [0,1].
477                This flag is ignored if a string despcriptor is passed.
478                Default is True.
479            font : (str, int)
480                font name or number.
481                Check [available fonts here](https://vedo.embl.es/fonts).
482            s : (float)
483                global size of the legend
484            c : (str)
485                color of the text
486            vspace : (float)
487                vertical spacing of lines
488            padding : (float)
489                padding of the box as a fraction of the text size
490            radius : (float)
491                border radius of the box
492            alpha : (float)
493                opacity of the box. Values below 1 may cause poor rendering
494                because of antialiasing.
495                Use alpha = 0 to remove the box.
496            bc : (str)
497                box color
498            lw : (int)
499                border line width of the box in pixel units
500            lc : (int)
501                border line color of the box
502            z : (float)
503                set the zorder as z position (useful to avoid overlap)
504        """
505        sx = self.x1lim - self.x0lim
506        s = s * sx / 55  # so that input can be about 1
507
508        ds = 0
509        texts = []
510        mks = []
511        for i, t in enumerate(self.labels):
512            label = self.labels[i]
513            t = label.text
514
515            if label.tcolor is not None:
516                c = label.tcolor
517
518            tx = vedo.shapes.Text3D(t, s=s, c=c, justify="center-left", font=font)
519            y0, y1 = tx.ybounds()
520            ds = max(y1 - y0, ds)
521            texts.append(tx)
522
523            mk = label.marker
524            if isinstance(mk, vedo.Points):
525                mk = mk.clone(deep=False).lighting("off")
526                cm = mk.center_of_mass()
527                ty0, ty1 = tx.ybounds()
528                oby0, oby1 = mk.ybounds()
529                mk.shift(-cm)
530                mk.SetOrigin(cm)
531                mk.scale((ty1 - ty0) / (oby1 - oby0))
532                mk.scale([1.1, 1.1, 0.01])
533            elif mk == "-":
534                mk = vedo.shapes.Marker(mk, s=s * 2)
535                mk.color(label.mcolor)
536            else:
537                mk = vedo.shapes.Marker(mk, s=s)
538                mk.color(label.mcolor)
539            mks.append(mk)
540
541        for i, tx in enumerate(texts):
542            tx.shift(0, -(i + 0) * ds * vspace)
543
544        for i, mk in enumerate(mks):
545            mk.shift(-ds * 1.75, -(i + 0) * ds * vspace, 0)
546
547        acts = texts + mks
548
549        aleg = Assembly(acts)  # .show(axes=1).close()
550        x0, x1, y0, y1, _, _ = aleg.GetBounds()
551
552        if alpha:
553            dx = x1 - x0
554            dy = y1 - y0
555
556            if not utils.is_sequence(padding):
557                padding = [padding] * 4
558            padding = min(padding)
559            padding = min(padding * dx, padding * dy)
560            if len(self.labels) == 1:
561                padding *= 4
562            x0 -= padding
563            x1 += padding
564            y0 -= padding
565            y1 += padding
566
567            box = shapes.Rectangle([x0, y0], [x1, y1], radius=radius, c=bc, alpha=alpha)
568            box.shift(0, 0, -dy / 100).pickable(False)
569            if lc:
570                box.lc(lc).lw(lw)
571            aleg.AddPart(box.actor)
572            aleg.objects.append(box)
573
574        xlim = self.xlim
575        ylim = self.ylim
576        if isinstance(pos, str):
577            px, py = 0.0, 0.0
578            rx, ry = (xlim[1] + xlim[0]) / 2, (ylim[1] + ylim[0]) / 2
579            shx, shy = 0.0, 0.0
580            if "top" in pos:
581                if "cent" in pos:
582                    px, py = rx, ylim[1]
583                    shx, shy = (x0 + x1) / 2, y1
584                elif "left" in pos:
585                    px, py = xlim[0], ylim[1]
586                    shx, shy = x0, y1
587                else:  # "right"
588                    px, py = xlim[1], ylim[1]
589                    shx, shy = x1, y1
590            elif "bot" in pos:
591                if "left" in pos:
592                    px, py = xlim[0], ylim[0]
593                    shx, shy = x0, y0
594                elif "right" in pos:
595                    px, py = xlim[1], ylim[0]
596                    shx, shy = x1, y0
597                else:  # "cent"
598                    px, py = rx, ylim[0]
599                    shx, shy = (x0 + x1) / 2, y0
600            elif "cent" in pos:
601                if "left" in pos:
602                    px, py = xlim[0], ry
603                    shx, shy = x0, (y0 + y1) / 2
604                elif "right" in pos:
605                    px, py = xlim[1], ry
606                    shx, shy = x1, (y0 + y1) / 2
607            else:
608                vedo.logger.error(f"in add_legend(), cannot understand {pos}")
609                raise RuntimeError
610
611        else:
612
613            if relative:
614                rx, ry = pos[0], pos[1]
615                px = (xlim[1] - xlim[0]) * rx + xlim[0]
616                py = (ylim[1] - ylim[0]) * ry + ylim[0]
617                z *= xlim[1] - xlim[0]
618            else:
619                px, py = pos[0], pos[1]
620            shx, shy = x0, y1
621
622        zpos = aleg.pos()[2]
623        aleg.pos(px - shx, py * self.yscale - shy, zpos + sx / 50 + z)
624
625        self.insert(aleg, rescale=False, cut=False)
626        self.legend = aleg
627        aleg.name = "Legend"
628        return self

Add existing labels to form a legend box. Labels have been previously filled with eg: plot(..., label="text")

Arguments:
  • pos : (str, list) A string or 2D coordinates. The default is "top-right".
  • relative : (bool) control whether pos is absolute or relative, e.i. normalized to the x and y ranges so that x and y in pos=[x,y] should be both in the range [0,1]. This flag is ignored if a string despcriptor is passed. Default is True.
  • font : (str, int) font name or number. Check available fonts here.
  • s : (float) global size of the legend
  • c : (str) color of the text
  • vspace : (float) vertical spacing of lines
  • padding : (float) padding of the box as a fraction of the text size
  • radius : (float) border radius of the box
  • alpha : (float) opacity of the box. Values below 1 may cause poor rendering because of antialiasing. Use alpha = 0 to remove the box.
  • bc : (str) box color
  • lw : (int) border line width of the box in pixel units
  • lc : (int) border line color of the box
  • z : (float) set the zorder as z position (useful to avoid overlap)
class Histogram1D(Figure):
 632class Histogram1D(Figure):
 633    "1D histogramming."
 634
 635    def __init__(
 636        self,
 637        data,
 638        weights=None,
 639        bins=None,
 640        errors=False,
 641        density=False,
 642        logscale=False,
 643        max_entries=None,
 644        fill=True,
 645        radius=0.075,
 646        c="olivedrab",
 647        gap=0.0,
 648        alpha=1,
 649        outline=False,
 650        lw=2,
 651        lc="k",
 652        texture="",
 653        marker="",
 654        ms=None,
 655        mc=None,
 656        ma=None,
 657        # Figure and axes options:
 658        like=None,
 659        xlim=None,
 660        ylim=(0, None),
 661        aspect=4 / 3,
 662        padding=(0.0, 0.0, 0.0, 0.05),
 663        title="",
 664        xtitle=" ",
 665        ytitle=" ",
 666        ac="k",
 667        grid=False,
 668        ztolerance=None,
 669        label="",
 670        **fig_kwargs,
 671    ):
 672        """
 673        Creates a `Histogram1D(Figure)` object.
 674
 675        Arguments:
 676            weights : (list)
 677                An array of weights, of the same shape as `data`. Each value in `data`
 678                only contributes its associated weight towards the bin count (instead of 1).
 679            bins : (int)
 680                number of bins
 681            density : (bool)
 682                normalize the area to 1 by dividing by the nr of entries and bin size
 683            logscale : (bool)
 684                use logscale on y-axis
 685            max_entries : (int)
 686                if `data` is larger than `max_entries`, a random sample of `max_entries` is used
 687            fill : (bool)
 688                fill bars with solid color `c`
 689            gap : (float)
 690                leave a small space btw bars
 691            radius : (float)
 692                border radius of the top of the histogram bar. Default value is 0.1.
 693            texture : (str)
 694                url or path to an image to be used as texture for the bin
 695            outline : (bool)
 696                show outline of the bins
 697            errors : (bool)
 698                show error bars
 699            xtitle : (str)
 700                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
 701            ytitle : (str)
 702                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
 703            padding : (float), list
 704                keep a padding space from the axes (as a fraction of the axis size).
 705                This can be a list of four numbers.
 706            aspect : (float)
 707                the desired aspect ratio of the histogram. Default is 4/3.
 708            grid : (bool)
 709                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
 710            ztolerance : (float)
 711                a tolerance factor to superimpose objects (along the z-axis).
 712
 713        Examples:
 714            - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
 715            - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
 716            - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
 717            - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)
 718
 719            ![](https://vedo.embl.es/images/pyplot/histo_1D.png)
 720        """
 721
 722        if max_entries and data.shape[0] > max_entries:
 723            data = np.random.choice(data, int(max_entries))
 724
 725        # purge NaN from data
 726        valid_ids = np.all(np.logical_not(np.isnan(data)))
 727        data = np.asarray(data[valid_ids]).ravel()
 728
 729        # if data.dtype is integer try to center bins by default
 730        if like is None and bins is None and np.issubdtype(data.dtype, np.integer):
 731            if xlim is None and ylim == (0, None):
 732                x1, x0 = data.max(), data.min()
 733                if 0 < x1 - x0 <= 100:
 734                    bins = x1 - x0 + 1
 735                    xlim = (x0 - 0.5, x1 + 0.5)
 736
 737        if like is None and vedo.last_figure is not None:
 738            if xlim is None and ylim == (0, None):
 739                like = vedo.last_figure
 740
 741        if like is not None:
 742            xlim = like.xlim
 743            ylim = like.ylim
 744            aspect = like.aspect
 745            padding = like.padding
 746            if bins is None:
 747                bins = like.bins
 748        if bins is None:
 749            bins = 20
 750
 751        if utils.is_sequence(xlim):
 752            # deal with user passing eg [x0, None]
 753            _x0, _x1 = xlim
 754            if _x0 is None:
 755                _x0 = data.min()
 756            if _x1 is None:
 757                _x1 = data.max()
 758            xlim = [_x0, _x1]
 759
 760        fs, edges = np.histogram(data, bins=bins, weights=weights, range=xlim)
 761        binsize = edges[1] - edges[0]
 762        ntot = data.shape[0]
 763
 764        fig_kwargs["title"] = title
 765        fig_kwargs["xtitle"] = xtitle
 766        fig_kwargs["ytitle"] = ytitle
 767        fig_kwargs["ac"] = ac
 768        fig_kwargs["ztolerance"] = ztolerance
 769        fig_kwargs["grid"] = grid
 770
 771        unscaled_errors = np.sqrt(fs)
 772        if density:
 773            scaled_errors = unscaled_errors / (ntot * binsize)
 774            fs = fs / (ntot * binsize)
 775            if ytitle == " ":
 776                ytitle = f"counts / ({ntot} x {utils.precision(binsize,3)})"
 777                fig_kwargs["ytitle"] = ytitle
 778        elif logscale:
 779            se_up = np.log10(fs + unscaled_errors / 2 + 1)
 780            se_dw = np.log10(fs - unscaled_errors / 2 + 1)
 781            scaled_errors = np.c_[se_up, se_dw]
 782            fs = np.log10(fs + 1)
 783            if ytitle == " ":
 784                ytitle = "log_10 (counts+1)"
 785                fig_kwargs["ytitle"] = ytitle
 786
 787        x0, x1 = np.min(edges), np.max(edges)
 788        y0, y1 = ylim[0], np.max(fs)
 789
 790        _errors = []
 791        if errors:
 792            if density:
 793                y1 += max(scaled_errors) / 2
 794                _errors = scaled_errors
 795            elif logscale:
 796                y1 = max(scaled_errors[:, 0])
 797                _errors = scaled_errors
 798            else:
 799                y1 += max(unscaled_errors) / 2
 800                _errors = unscaled_errors
 801
 802        if like is None:
 803            ylim = list(ylim)
 804            if xlim is None:
 805                xlim = [x0, x1]
 806            if ylim[1] is None:
 807                ylim[1] = y1
 808            if ylim[0] != 0:
 809                ylim[0] = y0
 810
 811        self.title = title
 812        self.xtitle = xtitle
 813        self.ytitle = ytitle
 814        self.entries = ntot
 815        self.frequencies = fs
 816        self.errors = _errors
 817        self.edges = edges
 818        self.centers = (edges[0:-1] + edges[1:]) / 2
 819        self.mean = data.mean()
 820        self.mode = self.centers[np.argmax(fs)]
 821        self.std = data.std()
 822        self.bins = edges  # internally used by "like"
 823
 824        ############################### stats legend as htitle
 825        addstats = False
 826        if not title:
 827            if "axes" not in fig_kwargs:
 828                addstats = True
 829                axes_opts = {}
 830                fig_kwargs["axes"] = axes_opts
 831            elif fig_kwargs["axes"] is False:
 832                pass
 833            else:
 834                axes_opts = fig_kwargs["axes"]
 835                if "htitle" not in axes_opts:
 836                    addstats = True
 837
 838        if addstats:
 839            htitle = f"Entries:~~{int(self.entries)}  "
 840            htitle += f"Mean:~~{utils.precision(self.mean, 4)}  "
 841            htitle += f"STD:~~{utils.precision(self.std, 4)}  "
 842
 843            axes_opts["htitle"] = htitle
 844            axes_opts["htitle_justify"] = "bottom-left"
 845            axes_opts["htitle_size"] = 0.016
 846            # axes_opts["htitle_offset"] = [-0.49, 0.01, 0]
 847
 848        if mc is None:
 849            mc = lc
 850        if ma is None:
 851            ma = alpha
 852
 853        if label:
 854            nlab = LabelData()
 855            nlab.text = label
 856            nlab.tcolor = ac
 857            nlab.marker = marker
 858            nlab.mcolor = mc
 859            if not marker:
 860                nlab.marker = "s"
 861                nlab.mcolor = c
 862            fig_kwargs["label"] = nlab
 863
 864        ############################################### Figure init
 865        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
 866
 867        if not self.yscale:
 868            return
 869
 870        if utils.is_sequence(bins):
 871            myedges = np.array(bins)
 872            bins = len(bins) - 1
 873        else:
 874            myedges = edges
 875
 876        bin_centers = []
 877        for i in range(bins):
 878            x = (myedges[i] + myedges[i + 1]) / 2
 879            bin_centers.append([x, fs[i], 0])
 880
 881        rs = []
 882        maxheigth = 0
 883        if not fill and not outline and not errors and not marker:
 884            outline = True  # otherwise it's empty..
 885
 886        if fill:  #####################
 887            if outline:
 888                gap = 0
 889
 890            for i in range(bins):
 891                F = fs[i]
 892                if not F:
 893                    continue
 894                p0 = (myedges[i] + gap * binsize, 0, 0)
 895                p1 = (myedges[i + 1] - gap * binsize, F, 0)
 896
 897                if radius:
 898                    if gap:
 899                        rds = np.array([0, 0, radius, radius])
 900                    else:
 901                        rd1 = 0 if i < bins - 1 and fs[i + 1] >= F else radius / 2
 902                        rd2 = 0 if i > 0 and fs[i - 1] >= F else radius / 2
 903                        rds = np.array([0, 0, rd1, rd2])
 904                    p1_yscaled = [p1[0], p1[1] * self.yscale, 0]
 905                    r = shapes.Rectangle(p0, p1_yscaled, radius=rds * binsize, res=6)
 906                    r.scale([1, 1 / self.yscale, 1])
 907                    r.radius = None  # so it doesnt get recreated and rescaled by insert()
 908                else:
 909                    r = shapes.Rectangle(p0, p1)
 910
 911                if texture:
 912                    r.texture(texture)
 913                    c = "w"
 914
 915                r.actor.PickableOff()
 916                maxheigth = max(maxheigth, p1[1])
 917                if c in colors.cmaps_names:
 918                    col = colors.color_map((p0[0] + p1[0]) / 2, c, myedges[0], myedges[-1])
 919                else:
 920                    col = c
 921                r.color(col).alpha(alpha).lighting("off")
 922                r.z(self.ztolerance)
 923                rs.append(r)
 924
 925        if outline:  #####################
 926            lns = [[myedges[0], 0, 0]]
 927            for i in range(bins):
 928                lns.append([myedges[i], fs[i], 0])
 929                lns.append([myedges[i + 1], fs[i], 0])
 930                maxheigth = max(maxheigth, fs[i])
 931            lns.append([myedges[-1], 0, 0])
 932            outl = shapes.Line(lns, c=lc, alpha=alpha, lw=lw)
 933            outl.z(self.ztolerance * 2)
 934            rs.append(outl)
 935
 936        if errors:  #####################
 937            for i in range(bins):
 938                x = self.centers[i]
 939                f = fs[i]
 940                if not f:
 941                    continue
 942                err = _errors[i]
 943                if utils.is_sequence(err):
 944                    el = shapes.Line([x, err[0], 0], [x, err[1], 0], c=lc, alpha=alpha, lw=lw)
 945                else:
 946                    el = shapes.Line(
 947                        [x, f - err / 2, 0], [x, f + err / 2, 0], c=lc, alpha=alpha, lw=lw
 948                    )
 949                el.z(self.ztolerance * 3)
 950                rs.append(el)
 951
 952        if marker:  #####################
 953
 954            # remove empty bins (we dont want a marker there)
 955            bin_centers = np.array(bin_centers)
 956            bin_centers = bin_centers[bin_centers[:, 1] > 0]
 957
 958            if utils.is_sequence(ms):  ### variable point size
 959                mk = shapes.Marker(marker, s=1)
 960                mk.scale([1, 1 / self.yscale, 1])
 961                msv = np.zeros_like(bin_centers)
 962                msv[:, 0] = ms
 963                marked = shapes.Glyph(
 964                    bin_centers, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
 965                )
 966            else:  ### fixed point size
 967
 968                if ms is None:
 969                    ms = (xlim[1] - xlim[0]) / 100.0
 970                else:
 971                    ms = (xlim[1] - xlim[0]) / 100.0 * ms
 972
 973                if utils.is_sequence(mc):
 974                    mk = shapes.Marker(marker, s=ms)
 975                    mk.scale([1, 1 / self.yscale, 1])
 976                    msv = np.zeros_like(bin_centers)
 977                    msv[:, 0] = 1
 978                    marked = shapes.Glyph(
 979                        bin_centers, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
 980                    )
 981                else:
 982                    mk = shapes.Marker(marker, s=ms)
 983                    mk.scale([1, 1 / self.yscale, 1])
 984                    marked = shapes.Glyph(bin_centers, mk, c=mc)
 985
 986            marked.alpha(ma)
 987            marked.z(self.ztolerance * 4)
 988            rs.append(marked)
 989
 990        self.insert(*rs, as3d=False)
 991        self.name = "Histogram1D"
 992
 993    def print(self, **kwargs) -> None:
 994        """Print infos about this histogram"""
 995        txt = (
 996            f"{self.name}  {self.title}\n"
 997            f"    xtitle  = '{self.xtitle}'\n"
 998            f"    ytitle  = '{self.ytitle}'\n"
 999            f"    entries = {self.entries}\n"
1000            f"    mean    = {self.mean}\n"
1001            f"    std     = {self.std}"
1002        )
1003        colors.printc(txt, **kwargs)

1D histogramming.

Histogram1D( data, weights=None, bins=None, errors=False, density=False, logscale=False, max_entries=None, fill=True, radius=0.075, c='olivedrab', gap=0.0, alpha=1, outline=False, lw=2, lc='k', texture='', marker='', ms=None, mc=None, ma=None, like=None, xlim=None, ylim=(0, None), aspect=1.3333333333333333, padding=(0.0, 0.0, 0.0, 0.05), title='', xtitle=' ', ytitle=' ', ac='k', grid=False, ztolerance=None, label='', **fig_kwargs)
635    def __init__(
636        self,
637        data,
638        weights=None,
639        bins=None,
640        errors=False,
641        density=False,
642        logscale=False,
643        max_entries=None,
644        fill=True,
645        radius=0.075,
646        c="olivedrab",
647        gap=0.0,
648        alpha=1,
649        outline=False,
650        lw=2,
651        lc="k",
652        texture="",
653        marker="",
654        ms=None,
655        mc=None,
656        ma=None,
657        # Figure and axes options:
658        like=None,
659        xlim=None,
660        ylim=(0, None),
661        aspect=4 / 3,
662        padding=(0.0, 0.0, 0.0, 0.05),
663        title="",
664        xtitle=" ",
665        ytitle=" ",
666        ac="k",
667        grid=False,
668        ztolerance=None,
669        label="",
670        **fig_kwargs,
671    ):
672        """
673        Creates a `Histogram1D(Figure)` object.
674
675        Arguments:
676            weights : (list)
677                An array of weights, of the same shape as `data`. Each value in `data`
678                only contributes its associated weight towards the bin count (instead of 1).
679            bins : (int)
680                number of bins
681            density : (bool)
682                normalize the area to 1 by dividing by the nr of entries and bin size
683            logscale : (bool)
684                use logscale on y-axis
685            max_entries : (int)
686                if `data` is larger than `max_entries`, a random sample of `max_entries` is used
687            fill : (bool)
688                fill bars with solid color `c`
689            gap : (float)
690                leave a small space btw bars
691            radius : (float)
692                border radius of the top of the histogram bar. Default value is 0.1.
693            texture : (str)
694                url or path to an image to be used as texture for the bin
695            outline : (bool)
696                show outline of the bins
697            errors : (bool)
698                show error bars
699            xtitle : (str)
700                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
701            ytitle : (str)
702                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
703            padding : (float), list
704                keep a padding space from the axes (as a fraction of the axis size).
705                This can be a list of four numbers.
706            aspect : (float)
707                the desired aspect ratio of the histogram. Default is 4/3.
708            grid : (bool)
709                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
710            ztolerance : (float)
711                a tolerance factor to superimpose objects (along the z-axis).
712
713        Examples:
714            - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
715            - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
716            - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
717            - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)
718
719            ![](https://vedo.embl.es/images/pyplot/histo_1D.png)
720        """
721
722        if max_entries and data.shape[0] > max_entries:
723            data = np.random.choice(data, int(max_entries))
724
725        # purge NaN from data
726        valid_ids = np.all(np.logical_not(np.isnan(data)))
727        data = np.asarray(data[valid_ids]).ravel()
728
729        # if data.dtype is integer try to center bins by default
730        if like is None and bins is None and np.issubdtype(data.dtype, np.integer):
731            if xlim is None and ylim == (0, None):
732                x1, x0 = data.max(), data.min()
733                if 0 < x1 - x0 <= 100:
734                    bins = x1 - x0 + 1
735                    xlim = (x0 - 0.5, x1 + 0.5)
736
737        if like is None and vedo.last_figure is not None:
738            if xlim is None and ylim == (0, None):
739                like = vedo.last_figure
740
741        if like is not None:
742            xlim = like.xlim
743            ylim = like.ylim
744            aspect = like.aspect
745            padding = like.padding
746            if bins is None:
747                bins = like.bins
748        if bins is None:
749            bins = 20
750
751        if utils.is_sequence(xlim):
752            # deal with user passing eg [x0, None]
753            _x0, _x1 = xlim
754            if _x0 is None:
755                _x0 = data.min()
756            if _x1 is None:
757                _x1 = data.max()
758            xlim = [_x0, _x1]
759
760        fs, edges = np.histogram(data, bins=bins, weights=weights, range=xlim)
761        binsize = edges[1] - edges[0]
762        ntot = data.shape[0]
763
764        fig_kwargs["title"] = title
765        fig_kwargs["xtitle"] = xtitle
766        fig_kwargs["ytitle"] = ytitle
767        fig_kwargs["ac"] = ac
768        fig_kwargs["ztolerance"] = ztolerance
769        fig_kwargs["grid"] = grid
770
771        unscaled_errors = np.sqrt(fs)
772        if density:
773            scaled_errors = unscaled_errors / (ntot * binsize)
774            fs = fs / (ntot * binsize)
775            if ytitle == " ":
776                ytitle = f"counts / ({ntot} x {utils.precision(binsize,3)})"
777                fig_kwargs["ytitle"] = ytitle
778        elif logscale:
779            se_up = np.log10(fs + unscaled_errors / 2 + 1)
780            se_dw = np.log10(fs - unscaled_errors / 2 + 1)
781            scaled_errors = np.c_[se_up, se_dw]
782            fs = np.log10(fs + 1)
783            if ytitle == " ":
784                ytitle = "log_10 (counts+1)"
785                fig_kwargs["ytitle"] = ytitle
786
787        x0, x1 = np.min(edges), np.max(edges)
788        y0, y1 = ylim[0], np.max(fs)
789
790        _errors = []
791        if errors:
792            if density:
793                y1 += max(scaled_errors) / 2
794                _errors = scaled_errors
795            elif logscale:
796                y1 = max(scaled_errors[:, 0])
797                _errors = scaled_errors
798            else:
799                y1 += max(unscaled_errors) / 2
800                _errors = unscaled_errors
801
802        if like is None:
803            ylim = list(ylim)
804            if xlim is None:
805                xlim = [x0, x1]
806            if ylim[1] is None:
807                ylim[1] = y1
808            if ylim[0] != 0:
809                ylim[0] = y0
810
811        self.title = title
812        self.xtitle = xtitle
813        self.ytitle = ytitle
814        self.entries = ntot
815        self.frequencies = fs
816        self.errors = _errors
817        self.edges = edges
818        self.centers = (edges[0:-1] + edges[1:]) / 2
819        self.mean = data.mean()
820        self.mode = self.centers[np.argmax(fs)]
821        self.std = data.std()
822        self.bins = edges  # internally used by "like"
823
824        ############################### stats legend as htitle
825        addstats = False
826        if not title:
827            if "axes" not in fig_kwargs:
828                addstats = True
829                axes_opts = {}
830                fig_kwargs["axes"] = axes_opts
831            elif fig_kwargs["axes"] is False:
832                pass
833            else:
834                axes_opts = fig_kwargs["axes"]
835                if "htitle" not in axes_opts:
836                    addstats = True
837
838        if addstats:
839            htitle = f"Entries:~~{int(self.entries)}  "
840            htitle += f"Mean:~~{utils.precision(self.mean, 4)}  "
841            htitle += f"STD:~~{utils.precision(self.std, 4)}  "
842
843            axes_opts["htitle"] = htitle
844            axes_opts["htitle_justify"] = "bottom-left"
845            axes_opts["htitle_size"] = 0.016
846            # axes_opts["htitle_offset"] = [-0.49, 0.01, 0]
847
848        if mc is None:
849            mc = lc
850        if ma is None:
851            ma = alpha
852
853        if label:
854            nlab = LabelData()
855            nlab.text = label
856            nlab.tcolor = ac
857            nlab.marker = marker
858            nlab.mcolor = mc
859            if not marker:
860                nlab.marker = "s"
861                nlab.mcolor = c
862            fig_kwargs["label"] = nlab
863
864        ############################################### Figure init
865        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
866
867        if not self.yscale:
868            return
869
870        if utils.is_sequence(bins):
871            myedges = np.array(bins)
872            bins = len(bins) - 1
873        else:
874            myedges = edges
875
876        bin_centers = []
877        for i in range(bins):
878            x = (myedges[i] + myedges[i + 1]) / 2
879            bin_centers.append([x, fs[i], 0])
880
881        rs = []
882        maxheigth = 0
883        if not fill and not outline and not errors and not marker:
884            outline = True  # otherwise it's empty..
885
886        if fill:  #####################
887            if outline:
888                gap = 0
889
890            for i in range(bins):
891                F = fs[i]
892                if not F:
893                    continue
894                p0 = (myedges[i] + gap * binsize, 0, 0)
895                p1 = (myedges[i + 1] - gap * binsize, F, 0)
896
897                if radius:
898                    if gap:
899                        rds = np.array([0, 0, radius, radius])
900                    else:
901                        rd1 = 0 if i < bins - 1 and fs[i + 1] >= F else radius / 2
902                        rd2 = 0 if i > 0 and fs[i - 1] >= F else radius / 2
903                        rds = np.array([0, 0, rd1, rd2])
904                    p1_yscaled = [p1[0], p1[1] * self.yscale, 0]
905                    r = shapes.Rectangle(p0, p1_yscaled, radius=rds * binsize, res=6)
906                    r.scale([1, 1 / self.yscale, 1])
907                    r.radius = None  # so it doesnt get recreated and rescaled by insert()
908                else:
909                    r = shapes.Rectangle(p0, p1)
910
911                if texture:
912                    r.texture(texture)
913                    c = "w"
914
915                r.actor.PickableOff()
916                maxheigth = max(maxheigth, p1[1])
917                if c in colors.cmaps_names:
918                    col = colors.color_map((p0[0] + p1[0]) / 2, c, myedges[0], myedges[-1])
919                else:
920                    col = c
921                r.color(col).alpha(alpha).lighting("off")
922                r.z(self.ztolerance)
923                rs.append(r)
924
925        if outline:  #####################
926            lns = [[myedges[0], 0, 0]]
927            for i in range(bins):
928                lns.append([myedges[i], fs[i], 0])
929                lns.append([myedges[i + 1], fs[i], 0])
930                maxheigth = max(maxheigth, fs[i])
931            lns.append([myedges[-1], 0, 0])
932            outl = shapes.Line(lns, c=lc, alpha=alpha, lw=lw)
933            outl.z(self.ztolerance * 2)
934            rs.append(outl)
935
936        if errors:  #####################
937            for i in range(bins):
938                x = self.centers[i]
939                f = fs[i]
940                if not f:
941                    continue
942                err = _errors[i]
943                if utils.is_sequence(err):
944                    el = shapes.Line([x, err[0], 0], [x, err[1], 0], c=lc, alpha=alpha, lw=lw)
945                else:
946                    el = shapes.Line(
947                        [x, f - err / 2, 0], [x, f + err / 2, 0], c=lc, alpha=alpha, lw=lw
948                    )
949                el.z(self.ztolerance * 3)
950                rs.append(el)
951
952        if marker:  #####################
953
954            # remove empty bins (we dont want a marker there)
955            bin_centers = np.array(bin_centers)
956            bin_centers = bin_centers[bin_centers[:, 1] > 0]
957
958            if utils.is_sequence(ms):  ### variable point size
959                mk = shapes.Marker(marker, s=1)
960                mk.scale([1, 1 / self.yscale, 1])
961                msv = np.zeros_like(bin_centers)
962                msv[:, 0] = ms
963                marked = shapes.Glyph(
964                    bin_centers, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
965                )
966            else:  ### fixed point size
967
968                if ms is None:
969                    ms = (xlim[1] - xlim[0]) / 100.0
970                else:
971                    ms = (xlim[1] - xlim[0]) / 100.0 * ms
972
973                if utils.is_sequence(mc):
974                    mk = shapes.Marker(marker, s=ms)
975                    mk.scale([1, 1 / self.yscale, 1])
976                    msv = np.zeros_like(bin_centers)
977                    msv[:, 0] = 1
978                    marked = shapes.Glyph(
979                        bin_centers, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
980                    )
981                else:
982                    mk = shapes.Marker(marker, s=ms)
983                    mk.scale([1, 1 / self.yscale, 1])
984                    marked = shapes.Glyph(bin_centers, mk, c=mc)
985
986            marked.alpha(ma)
987            marked.z(self.ztolerance * 4)
988            rs.append(marked)
989
990        self.insert(*rs, as3d=False)
991        self.name = "Histogram1D"

Creates a Histogram1D(Figure) object.

Arguments:
  • weights : (list) An array of weights, of the same shape as data. Each value in data only contributes its associated weight towards the bin count (instead of 1).
  • bins : (int) number of bins
  • density : (bool) normalize the area to 1 by dividing by the nr of entries and bin size
  • logscale : (bool) use logscale on y-axis
  • max_entries : (int) if data is larger than max_entries, a random sample of max_entries is used
  • fill : (bool) fill bars with solid color c
  • gap : (float) leave a small space btw bars
  • radius : (float) border radius of the top of the histogram bar. Default value is 0.1.
  • texture : (str) url or path to an image to be used as texture for the bin
  • outline : (bool) show outline of the bins
  • errors : (bool) show error bars
  • xtitle : (str) title for the x-axis, can also be set using axes=dict(xtitle="my x axis")
  • ytitle : (str) title for the y-axis, can also be set using axes=dict(ytitle="my y axis")
  • padding : (float), list keep a padding space from the axes (as a fraction of the axis size). This can be a list of four numbers.
  • aspect : (float) the desired aspect ratio of the histogram. Default is 4/3.
  • grid : (bool) show the background grid for the axes, can also be set using axes=dict(xygrid=True)
  • ztolerance : (float) a tolerance factor to superimpose objects (along the z-axis).
Examples:

def print(self, **kwargs) -> None:
 993    def print(self, **kwargs) -> None:
 994        """Print infos about this histogram"""
 995        txt = (
 996            f"{self.name}  {self.title}\n"
 997            f"    xtitle  = '{self.xtitle}'\n"
 998            f"    ytitle  = '{self.ytitle}'\n"
 999            f"    entries = {self.entries}\n"
1000            f"    mean    = {self.mean}\n"
1001            f"    std     = {self.std}"
1002        )
1003        colors.printc(txt, **kwargs)

Print infos about this histogram

class Histogram2D(Figure):
1007class Histogram2D(Figure):
1008    """2D histogramming."""
1009
1010    def __init__(
1011        self,
1012        xvalues,
1013        yvalues=None,
1014        bins=25,
1015        weights=None,
1016        cmap="cividis",
1017        alpha=1,
1018        gap=0,
1019        scalarbar=True,
1020        # Figure and axes options:
1021        like=None,
1022        xlim=None,
1023        ylim=(None, None),
1024        zlim=(None, None),
1025        aspect=1,
1026        title="",
1027        xtitle=" ",
1028        ytitle=" ",
1029        ztitle="",
1030        ac="k",
1031        **fig_kwargs,
1032    ):
1033        """
1034        Input data formats `[(x1,x2,..), (y1,y2,..)] or [(x1,y1), (x2,y2),..]`
1035        are both valid.
1036
1037        Use keyword `like=...` if you want to use the same format of a previously
1038        created Figure (useful when superimposing Figures) to make sure
1039        they are compatible and comparable. If they are not compatible
1040        you will receive an error message.
1041
1042        Arguments:
1043            bins : (list)
1044                binning as (nx, ny)
1045            weights : (list)
1046                array of weights to assign to each entry
1047            cmap : (str, lookuptable)
1048                color map name or look up table
1049            alpha : (float)
1050                opacity of the histogram
1051            gap : (float)
1052                separation between adjacent bins as a fraction for their size
1053            scalarbar : (bool)
1054                add a scalarbar to right of the histogram
1055            like : (Figure)
1056                grab and use the same format of the given Figure (for superimposing)
1057            xlim : (list)
1058                [x0, x1] range of interest. If left to None will automatically
1059                choose the minimum or the maximum of the data range.
1060                Data outside the range are completely ignored.
1061            ylim : (list)
1062                [y0, y1] range of interest. If left to None will automatically
1063                choose the minimum or the maximum of the data range.
1064                Data outside the range are completely ignored.
1065            aspect : (float)
1066                the desired aspect ratio of the figure.
1067            title : (str)
1068                title of the plot to appear on top.
1069                If left blank some statistics will be shown.
1070            xtitle : (str)
1071                x axis title
1072            ytitle : (str)
1073                y axis title
1074            ztitle : (str)
1075                title for the scalar bar
1076            ac : (str)
1077                axes color, additional keyword for Axes can also be added
1078                using e.g. `axes=dict(xygrid=True)`
1079
1080        Examples:
1081            - [histo_2d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_a.py)
1082            - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py)
1083
1084            ![](https://vedo.embl.es/images/pyplot/histo_2D.png)
1085        """
1086        xvalues = np.asarray(xvalues)
1087        if yvalues is None:
1088            # assume [(x1,y1), (x2,y2) ...] format
1089            yvalues = xvalues[:, 1]
1090            xvalues = xvalues[:, 0]
1091        else:
1092            yvalues = np.asarray(yvalues)
1093
1094        padding = [0, 0, 0, 0]
1095
1096        if like is None and vedo.last_figure is not None:
1097            if xlim is None and ylim == (None, None) and zlim == (None, None):
1098                like = vedo.last_figure
1099
1100        if like is not None:
1101            xlim = like.xlim
1102            ylim = like.ylim
1103            aspect = like.aspect
1104            padding = like.padding
1105            if bins is None:
1106                bins = like.bins
1107        if bins is None:
1108            bins = 20
1109
1110        if isinstance(bins, int):
1111            bins = (bins, bins)
1112
1113        if utils.is_sequence(xlim):
1114            # deal with user passing eg [x0, None]
1115            _x0, _x1 = xlim
1116            if _x0 is None:
1117                _x0 = xvalues.min()
1118            if _x1 is None:
1119                _x1 = xvalues.max()
1120            xlim = [_x0, _x1]
1121
1122        if utils.is_sequence(ylim):
1123            # deal with user passing eg [x0, None]
1124            _y0, _y1 = ylim
1125            if _y0 is None:
1126                _y0 = yvalues.min()
1127            if _y1 is None:
1128                _y1 = yvalues.max()
1129            ylim = [_y0, _y1]
1130
1131        H, xedges, yedges = np.histogram2d(
1132            xvalues, yvalues, weights=weights, bins=bins, range=(xlim, ylim)
1133        )
1134
1135        xlim = np.min(xedges), np.max(xedges)
1136        ylim = np.min(yedges), np.max(yedges)
1137        dx, dy = xlim[1] - xlim[0], ylim[1] - ylim[0]
1138
1139        fig_kwargs["title"] = title
1140        fig_kwargs["xtitle"] = xtitle
1141        fig_kwargs["ytitle"] = ytitle
1142        fig_kwargs["ac"] = ac
1143
1144        self.entries = len(xvalues)
1145        self.frequencies = H
1146        self.edges = (xedges, yedges)
1147        self.mean = (xvalues.mean(), yvalues.mean())
1148        self.std = (xvalues.std(), yvalues.std())
1149        self.bins = bins  # internally used by "like"
1150
1151        ############################### stats legend as htitle
1152        addstats = False
1153        if not title:
1154            if "axes" not in fig_kwargs:
1155                addstats = True
1156                axes_opts = {}
1157                fig_kwargs["axes"] = axes_opts
1158            elif fig_kwargs["axes"] is False:
1159                pass
1160            else:
1161                axes_opts = fig_kwargs["axes"]
1162                if "htitle" not in fig_kwargs["axes"]:
1163                    addstats = True
1164
1165        if addstats:
1166            htitle = f"Entries:~~{int(self.entries)}  "
1167            htitle += f"Mean:~~{utils.precision(self.mean, 3)}  "
1168            htitle += f"STD:~~{utils.precision(self.std, 3)}  "
1169            axes_opts["htitle"] = htitle
1170            axes_opts["htitle_justify"] = "bottom-left"
1171            axes_opts["htitle_size"] = 0.0175
1172
1173        ############################################### Figure init
1174        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1175
1176        if self.yscale:
1177            ##################### the grid
1178            acts = []
1179            g = shapes.Grid(
1180                pos=[(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, 0], s=(dx, dy), res=bins[:2]
1181            )
1182            g.alpha(alpha).lw(0).wireframe(False).flat().lighting("off")
1183            g.cmap(cmap, np.ravel(H.T), on="cells", vmin=zlim[0], vmax=zlim[1])
1184            if gap:
1185                g.shrink(abs(1 - gap))
1186
1187            if scalarbar:
1188                sc = g.add_scalarbar3d(ztitle, c=ac).scalarbar
1189
1190                # print(" g.GetBounds()[0]", g.bounds()[:2])
1191                # print("sc.GetBounds()[0]",sc.GetBounds()[:2])
1192                delta = sc.GetBounds()[0] - g.bounds()[1]
1193
1194                sc_size = sc.GetBounds()[1] - sc.GetBounds()[0]
1195
1196                sc.SetOrigin(sc.GetBounds()[0], 0, 0)
1197                sc.scale([self.yscale, 1, 1])  ## prescale trick
1198                sc.shift(-delta + 0.25*sc_size*self.yscale)
1199
1200                acts.append(sc)
1201            acts.append(g)
1202
1203            self.insert(*acts, as3d=False)
1204            self.name = "Histogram2D"

2D histogramming.

Histogram2D( xvalues, yvalues=None, bins=25, weights=None, cmap='cividis', alpha=1, gap=0, scalarbar=True, like=None, xlim=None, ylim=(None, None), zlim=(None, None), aspect=1, title='', xtitle=' ', ytitle=' ', ztitle='', ac='k', **fig_kwargs)
1010    def __init__(
1011        self,
1012        xvalues,
1013        yvalues=None,
1014        bins=25,
1015        weights=None,
1016        cmap="cividis",
1017        alpha=1,
1018        gap=0,
1019        scalarbar=True,
1020        # Figure and axes options:
1021        like=None,
1022        xlim=None,
1023        ylim=(None, None),
1024        zlim=(None, None),
1025        aspect=1,
1026        title="",
1027        xtitle=" ",
1028        ytitle=" ",
1029        ztitle="",
1030        ac="k",
1031        **fig_kwargs,
1032    ):
1033        """
1034        Input data formats `[(x1,x2,..), (y1,y2,..)] or [(x1,y1), (x2,y2),..]`
1035        are both valid.
1036
1037        Use keyword `like=...` if you want to use the same format of a previously
1038        created Figure (useful when superimposing Figures) to make sure
1039        they are compatible and comparable. If they are not compatible
1040        you will receive an error message.
1041
1042        Arguments:
1043            bins : (list)
1044                binning as (nx, ny)
1045            weights : (list)
1046                array of weights to assign to each entry
1047            cmap : (str, lookuptable)
1048                color map name or look up table
1049            alpha : (float)
1050                opacity of the histogram
1051            gap : (float)
1052                separation between adjacent bins as a fraction for their size
1053            scalarbar : (bool)
1054                add a scalarbar to right of the histogram
1055            like : (Figure)
1056                grab and use the same format of the given Figure (for superimposing)
1057            xlim : (list)
1058                [x0, x1] range of interest. If left to None will automatically
1059                choose the minimum or the maximum of the data range.
1060                Data outside the range are completely ignored.
1061            ylim : (list)
1062                [y0, y1] range of interest. If left to None will automatically
1063                choose the minimum or the maximum of the data range.
1064                Data outside the range are completely ignored.
1065            aspect : (float)
1066                the desired aspect ratio of the figure.
1067            title : (str)
1068                title of the plot to appear on top.
1069                If left blank some statistics will be shown.
1070            xtitle : (str)
1071                x axis title
1072            ytitle : (str)
1073                y axis title
1074            ztitle : (str)
1075                title for the scalar bar
1076            ac : (str)
1077                axes color, additional keyword for Axes can also be added
1078                using e.g. `axes=dict(xygrid=True)`
1079
1080        Examples:
1081            - [histo_2d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_a.py)
1082            - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py)
1083
1084            ![](https://vedo.embl.es/images/pyplot/histo_2D.png)
1085        """
1086        xvalues = np.asarray(xvalues)
1087        if yvalues is None:
1088            # assume [(x1,y1), (x2,y2) ...] format
1089            yvalues = xvalues[:, 1]
1090            xvalues = xvalues[:, 0]
1091        else:
1092            yvalues = np.asarray(yvalues)
1093
1094        padding = [0, 0, 0, 0]
1095
1096        if like is None and vedo.last_figure is not None:
1097            if xlim is None and ylim == (None, None) and zlim == (None, None):
1098                like = vedo.last_figure
1099
1100        if like is not None:
1101            xlim = like.xlim
1102            ylim = like.ylim
1103            aspect = like.aspect
1104            padding = like.padding
1105            if bins is None:
1106                bins = like.bins
1107        if bins is None:
1108            bins = 20
1109
1110        if isinstance(bins, int):
1111            bins = (bins, bins)
1112
1113        if utils.is_sequence(xlim):
1114            # deal with user passing eg [x0, None]
1115            _x0, _x1 = xlim
1116            if _x0 is None:
1117                _x0 = xvalues.min()
1118            if _x1 is None:
1119                _x1 = xvalues.max()
1120            xlim = [_x0, _x1]
1121
1122        if utils.is_sequence(ylim):
1123            # deal with user passing eg [x0, None]
1124            _y0, _y1 = ylim
1125            if _y0 is None:
1126                _y0 = yvalues.min()
1127            if _y1 is None:
1128                _y1 = yvalues.max()
1129            ylim = [_y0, _y1]
1130
1131        H, xedges, yedges = np.histogram2d(
1132            xvalues, yvalues, weights=weights, bins=bins, range=(xlim, ylim)
1133        )
1134
1135        xlim = np.min(xedges), np.max(xedges)
1136        ylim = np.min(yedges), np.max(yedges)
1137        dx, dy = xlim[1] - xlim[0], ylim[1] - ylim[0]
1138
1139        fig_kwargs["title"] = title
1140        fig_kwargs["xtitle"] = xtitle
1141        fig_kwargs["ytitle"] = ytitle
1142        fig_kwargs["ac"] = ac
1143
1144        self.entries = len(xvalues)
1145        self.frequencies = H
1146        self.edges = (xedges, yedges)
1147        self.mean = (xvalues.mean(), yvalues.mean())
1148        self.std = (xvalues.std(), yvalues.std())
1149        self.bins = bins  # internally used by "like"
1150
1151        ############################### stats legend as htitle
1152        addstats = False
1153        if not title:
1154            if "axes" not in fig_kwargs:
1155                addstats = True
1156                axes_opts = {}
1157                fig_kwargs["axes"] = axes_opts
1158            elif fig_kwargs["axes"] is False:
1159                pass
1160            else:
1161                axes_opts = fig_kwargs["axes"]
1162                if "htitle" not in fig_kwargs["axes"]:
1163                    addstats = True
1164
1165        if addstats:
1166            htitle = f"Entries:~~{int(self.entries)}  "
1167            htitle += f"Mean:~~{utils.precision(self.mean, 3)}  "
1168            htitle += f"STD:~~{utils.precision(self.std, 3)}  "
1169            axes_opts["htitle"] = htitle
1170            axes_opts["htitle_justify"] = "bottom-left"
1171            axes_opts["htitle_size"] = 0.0175
1172
1173        ############################################### Figure init
1174        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1175
1176        if self.yscale:
1177            ##################### the grid
1178            acts = []
1179            g = shapes.Grid(
1180                pos=[(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, 0], s=(dx, dy), res=bins[:2]
1181            )
1182            g.alpha(alpha).lw(0).wireframe(False).flat().lighting("off")
1183            g.cmap(cmap, np.ravel(H.T), on="cells", vmin=zlim[0], vmax=zlim[1])
1184            if gap:
1185                g.shrink(abs(1 - gap))
1186
1187            if scalarbar:
1188                sc = g.add_scalarbar3d(ztitle, c=ac).scalarbar
1189
1190                # print(" g.GetBounds()[0]", g.bounds()[:2])
1191                # print("sc.GetBounds()[0]",sc.GetBounds()[:2])
1192                delta = sc.GetBounds()[0] - g.bounds()[1]
1193
1194                sc_size = sc.GetBounds()[1] - sc.GetBounds()[0]
1195
1196                sc.SetOrigin(sc.GetBounds()[0], 0, 0)
1197                sc.scale([self.yscale, 1, 1])  ## prescale trick
1198                sc.shift(-delta + 0.25*sc_size*self.yscale)
1199
1200                acts.append(sc)
1201            acts.append(g)
1202
1203            self.insert(*acts, as3d=False)
1204            self.name = "Histogram2D"

Input data formats [(x1,x2,..), (y1,y2,..)] or [(x1,y1), (x2,y2),..] are both valid.

Use keyword like=... if you want to use the same format of a previously created Figure (useful when superimposing Figures) to make sure they are compatible and comparable. If they are not compatible you will receive an error message.

Arguments:
  • bins : (list) binning as (nx, ny)
  • weights : (list) array of weights to assign to each entry
  • cmap : (str, lookuptable) color map name or look up table
  • alpha : (float) opacity of the histogram
  • gap : (float) separation between adjacent bins as a fraction for their size
  • scalarbar : (bool) add a scalarbar to right of the histogram
  • like : (Figure) grab and use the same format of the given Figure (for superimposing)
  • xlim : (list) [x0, x1] range of interest. If left to None will automatically choose the minimum or the maximum of the data range. Data outside the range are completely ignored.
  • ylim : (list) [y0, y1] range of interest. If left to None will automatically choose the minimum or the maximum of the data range. Data outside the range are completely ignored.
  • aspect : (float) the desired aspect ratio of the figure.
  • title : (str) title of the plot to appear on top. If left blank some statistics will be shown.
  • xtitle : (str) x axis title
  • ytitle : (str) y axis title
  • ztitle : (str) title for the scalar bar
  • ac : (str) axes color, additional keyword for Axes can also be added using e.g. axes=dict(xygrid=True)
Examples:

class PlotXY(Figure):
1427class PlotXY(Figure):
1428    """Creates a `PlotXY(Figure)` object."""
1429
1430    def __init__(
1431        self,
1432        #
1433        data,
1434        xerrors=None,
1435        yerrors=None,
1436        #
1437        lw=2,
1438        lc=None,
1439        la=1,
1440        dashed=False,
1441        splined=False,
1442        #
1443        elw=2,  # error line width
1444        ec=None,  # error line or band color
1445        error_band=False,  # errors in x are ignored
1446        #
1447        marker="",
1448        ms=None,
1449        mc=None,
1450        ma=None,
1451        # Figure and axes options:
1452        like=None,
1453        xlim=None,
1454        ylim=(None, None),
1455        aspect=4 / 3,
1456        padding=0.05,
1457        #
1458        title="",
1459        xtitle=" ",
1460        ytitle=" ",
1461        ac="k",
1462        grid=True,
1463        ztolerance=None,
1464        label="",
1465        **fig_kwargs,
1466    ):
1467        """
1468        Arguments:
1469            xerrors : (bool)
1470                show error bars associated to each point in x
1471            yerrors : (bool)
1472                show error bars associated to each point in y
1473            lw : (int)
1474                width of the line connecting points in pixel units.
1475                Set it to 0 to remove the line.
1476            lc : (str)
1477                line color
1478            la : (float)
1479                line "alpha", opacity of the line
1480            dashed : (bool)
1481                draw a dashed line instead of a continuous line
1482            splined : (bool)
1483                spline the line joining the point as a countinous curve
1484            elw : (int)
1485                width of error bar lines in units of pixels
1486            ec : (color)
1487                color of error bar, by default the same as marker color
1488            error_band : (bool)
1489                represent errors on y as a filled error band.
1490                Use `ec` keyword to modify its color.
1491            marker : (str, int)
1492                use a marker for the data points
1493            ms : (float)
1494                marker size
1495            mc : (color)
1496                color of the marker
1497            ma : (float)
1498                opacity of the marker
1499            xlim : (list)
1500                set limits to the range for the x variable
1501            ylim : (list)
1502                set limits to the range for the y variable
1503            aspect : (float, str)
1504                Desired aspect ratio.
1505                Use `aspect="equal"` to force the same units in x and y.
1506                Scaling factor is saved in Figure.yscale.
1507            padding : (float, list)
1508                keep a padding space from the axes (as a fraction of the axis size).
1509                This can be a list of four numbers.
1510            title : (str)
1511                title to appear on the top of the frame, like a header.
1512            xtitle : (str)
1513                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1514            ytitle : (str)
1515                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1516            ac : (str)
1517                axes color
1518            grid : (bool)
1519                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1520            ztolerance : (float)
1521                a tolerance factor to superimpose objects (along the z-axis).
1522
1523        Example:
1524            ```python
1525            import numpy as np
1526            from vedo.pyplot import plot
1527            x = np.arange(0, np.pi, 0.1)
1528            fig = plot(x, np.sin(2*x), 'r0-', aspect='equal')
1529            fig+= plot(x, np.cos(2*x), 'blue4 o-', like=fig)
1530            fig.show().close()
1531            ```
1532            ![](https://vedo.embl.es/images/feats/plotxy.png)
1533
1534        Examples:
1535            - [plot_errbars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errbars.py)
1536            - [plot_errband.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errband.py)
1537            - [plot_pip.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_pip.py)
1538
1539                ![](https://vedo.embl.es/images/pyplot/plot_pip.png)
1540
1541            - [scatter1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter1.py)
1542            - [scatter2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter2.py)
1543
1544                ![](https://vedo.embl.es/images/pyplot/scatter2.png)
1545        """
1546        line = False
1547        if lw > 0:
1548            line = True
1549        if marker == "" and not line and not splined:
1550            marker = "o"
1551
1552        if like is None and vedo.last_figure is not None:
1553            if xlim is None and ylim == (None, None):
1554                like = vedo.last_figure
1555
1556        if like is not None:
1557            xlim = like.xlim
1558            ylim = like.ylim
1559            aspect = like.aspect
1560            padding = like.padding
1561
1562        if utils.is_sequence(xlim):
1563            # deal with user passing eg [x0, None]
1564            _x0, _x1 = xlim
1565            if _x0 is None:
1566                _x0 = data.min()
1567            if _x1 is None:
1568                _x1 = data.max()
1569            xlim = [_x0, _x1]
1570
1571        # purge NaN from data
1572        validIds = np.all(np.logical_not(np.isnan(data)))
1573        data = np.array(data[validIds])[0]
1574
1575        fig_kwargs["title"] = title
1576        fig_kwargs["xtitle"] = xtitle
1577        fig_kwargs["ytitle"] = ytitle
1578        fig_kwargs["ac"] = ac
1579        fig_kwargs["ztolerance"] = ztolerance
1580        fig_kwargs["grid"] = grid
1581
1582        x0, y0 = np.min(data, axis=0)
1583        x1, y1 = np.max(data, axis=0)
1584        if xerrors is not None and not error_band:
1585            x0 = min(data[:, 0] - xerrors)
1586            x1 = max(data[:, 0] + xerrors)
1587        if yerrors is not None:
1588            y0 = min(data[:, 1] - yerrors)
1589            y1 = max(data[:, 1] + yerrors)
1590
1591        if like is None:
1592            if xlim is None:
1593                xlim = (None, None)
1594            xlim = list(xlim)
1595            if xlim[0] is None:
1596                xlim[0] = x0
1597            if xlim[1] is None:
1598                xlim[1] = x1
1599            ylim = list(ylim)
1600            if ylim[0] is None:
1601                ylim[0] = y0
1602            if ylim[1] is None:
1603                ylim[1] = y1
1604
1605        self.entries = len(data)
1606        self.mean = data.mean()
1607        self.std = data.std()
1608        
1609        self.ztolerance = 0
1610        
1611        ######### the PlotXY marker
1612        # fall back solutions logic for colors
1613        if "c" in fig_kwargs:
1614            if mc is None:
1615                mc = fig_kwargs["c"]
1616            if lc is None:
1617                lc = fig_kwargs["c"]
1618            if ec is None:
1619                ec = fig_kwargs["c"]
1620        if lc is None:
1621            lc = "k"
1622        if mc is None:
1623            mc = lc
1624        if ma is None:
1625            ma = la
1626        if ec is None:
1627            if mc is None:
1628                ec = lc
1629            else:
1630                ec = mc
1631
1632        if label:
1633            nlab = LabelData()
1634            nlab.text = label
1635            nlab.tcolor = ac
1636            nlab.marker = marker
1637            if line and marker == "":
1638                nlab.marker = "-"
1639            nlab.mcolor = mc
1640            fig_kwargs["label"] = nlab
1641
1642        ############################################### Figure init
1643        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1644
1645        if not self.yscale:
1646            return
1647
1648        acts = []
1649
1650        ######### the PlotXY Line or Spline
1651        if dashed:
1652            l = shapes.DashedLine(data, c=lc, alpha=la, lw=lw)
1653            acts.append(l)
1654        elif splined:
1655            l = shapes.KSpline(data).lw(lw).c(lc).alpha(la)
1656            acts.append(l)
1657        elif line:
1658            l = shapes.Line(data, c=lc, alpha=la).lw(lw)
1659            acts.append(l)
1660
1661        if marker:
1662
1663            pts = np.c_[data, np.zeros(len(data))]
1664
1665            if utils.is_sequence(ms):
1666                ### variable point size
1667                mk = shapes.Marker(marker, s=1)
1668                mk.scale([1, 1 / self.yscale, 1])
1669                msv = np.zeros_like(pts)
1670                msv[:, 0] = ms
1671                marked = shapes.Glyph(
1672                    pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
1673                )
1674            else:
1675                ### fixed point size
1676                if ms is None:
1677                    ms = (xlim[1] - xlim[0]) / 100.0
1678
1679                if utils.is_sequence(mc):
1680                    fig_kwargs["marker_color"] = None  # for labels
1681                    mk = shapes.Marker(marker, s=ms)
1682                    mk.scale([1, 1 / self.yscale, 1])
1683                    msv = np.zeros_like(pts)
1684                    msv[:, 0] = 1
1685                    marked = shapes.Glyph(
1686                        pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
1687                    )
1688                else:
1689                    mk = shapes.Marker(marker, s=ms)
1690                    mk.scale([1, 1 / self.yscale, 1])
1691                    marked = shapes.Glyph(pts, mk, c=mc)
1692
1693            marked.name = "Marker"
1694            marked.alpha(ma)
1695            marked.z(3 * self.ztolerance)
1696            acts.append(marked)
1697
1698        ######### the PlotXY marker errors
1699        ztol = self.ztolerance
1700
1701        if error_band:
1702            yerrors = np.abs(yerrors)
1703            du = np.array(data)
1704            dd = np.array(data)
1705            du[:, 1] += yerrors
1706            dd[:, 1] -= yerrors
1707            if splined:
1708                res = len(data) * 20
1709                band1 = shapes.KSpline(du, res=res)
1710                band2 = shapes.KSpline(dd, res=res)
1711                band = shapes.Ribbon(band1, band2, res=(res, 2))
1712            else:
1713                dd = list(reversed(dd.tolist()))
1714                band = shapes.Line(du.tolist() + dd, closed=True)
1715                band.triangulate().lw(0)
1716            if ec is None:
1717                band.c(lc)
1718            else:
1719                band.c(ec)
1720            band.lighting("off").alpha(la).z(ztol / 20)
1721            acts.append(band)
1722
1723        else:
1724
1725            ## xerrors
1726            if xerrors is not None:
1727                if len(xerrors) == len(data):
1728                    errs = []
1729                    for i, val in enumerate(data):
1730                        xval, yval = val
1731                        xerr = xerrors[i] / 2
1732                        el = shapes.Line((xval - xerr, yval, ztol), (xval + xerr, yval, ztol))
1733                        el.lw(elw)
1734                        errs.append(el)
1735                    mxerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol)
1736                    acts.append(mxerrs)
1737                else:
1738                    vedo.logger.error("in PlotXY(xerrors=...): mismatch in array length")
1739
1740            ## yerrors
1741            if yerrors is not None:
1742                if len(yerrors) == len(data):
1743                    errs = []
1744                    for i, val in enumerate(data):
1745                        xval, yval = val
1746                        yerr = yerrors[i]
1747                        el = shapes.Line((xval, yval - yerr, ztol), (xval, yval + yerr, ztol))
1748                        el.lw(elw)
1749                        errs.append(el)
1750                    myerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol)
1751                    acts.append(myerrs)
1752                else:
1753                    vedo.logger.error("in PlotXY(yerrors=...): mismatch in array length")
1754
1755        self.insert(*acts, as3d=False)
1756        self.name = "PlotXY"

Creates a PlotXY(Figure) object.

PlotXY( data, xerrors=None, yerrors=None, lw=2, lc=None, la=1, dashed=False, splined=False, elw=2, ec=None, error_band=False, marker='', ms=None, mc=None, ma=None, like=None, xlim=None, ylim=(None, None), aspect=1.3333333333333333, padding=0.05, title='', xtitle=' ', ytitle=' ', ac='k', grid=True, ztolerance=None, label='', **fig_kwargs)
1430    def __init__(
1431        self,
1432        #
1433        data,
1434        xerrors=None,
1435        yerrors=None,
1436        #
1437        lw=2,
1438        lc=None,
1439        la=1,
1440        dashed=False,
1441        splined=False,
1442        #
1443        elw=2,  # error line width
1444        ec=None,  # error line or band color
1445        error_band=False,  # errors in x are ignored
1446        #
1447        marker="",
1448        ms=None,
1449        mc=None,
1450        ma=None,
1451        # Figure and axes options:
1452        like=None,
1453        xlim=None,
1454        ylim=(None, None),
1455        aspect=4 / 3,
1456        padding=0.05,
1457        #
1458        title="",
1459        xtitle=" ",
1460        ytitle=" ",
1461        ac="k",
1462        grid=True,
1463        ztolerance=None,
1464        label="",
1465        **fig_kwargs,
1466    ):
1467        """
1468        Arguments:
1469            xerrors : (bool)
1470                show error bars associated to each point in x
1471            yerrors : (bool)
1472                show error bars associated to each point in y
1473            lw : (int)
1474                width of the line connecting points in pixel units.
1475                Set it to 0 to remove the line.
1476            lc : (str)
1477                line color
1478            la : (float)
1479                line "alpha", opacity of the line
1480            dashed : (bool)
1481                draw a dashed line instead of a continuous line
1482            splined : (bool)
1483                spline the line joining the point as a countinous curve
1484            elw : (int)
1485                width of error bar lines in units of pixels
1486            ec : (color)
1487                color of error bar, by default the same as marker color
1488            error_band : (bool)
1489                represent errors on y as a filled error band.
1490                Use `ec` keyword to modify its color.
1491            marker : (str, int)
1492                use a marker for the data points
1493            ms : (float)
1494                marker size
1495            mc : (color)
1496                color of the marker
1497            ma : (float)
1498                opacity of the marker
1499            xlim : (list)
1500                set limits to the range for the x variable
1501            ylim : (list)
1502                set limits to the range for the y variable
1503            aspect : (float, str)
1504                Desired aspect ratio.
1505                Use `aspect="equal"` to force the same units in x and y.
1506                Scaling factor is saved in Figure.yscale.
1507            padding : (float, list)
1508                keep a padding space from the axes (as a fraction of the axis size).
1509                This can be a list of four numbers.
1510            title : (str)
1511                title to appear on the top of the frame, like a header.
1512            xtitle : (str)
1513                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1514            ytitle : (str)
1515                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1516            ac : (str)
1517                axes color
1518            grid : (bool)
1519                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1520            ztolerance : (float)
1521                a tolerance factor to superimpose objects (along the z-axis).
1522
1523        Example:
1524            ```python
1525            import numpy as np
1526            from vedo.pyplot import plot
1527            x = np.arange(0, np.pi, 0.1)
1528            fig = plot(x, np.sin(2*x), 'r0-', aspect='equal')
1529            fig+= plot(x, np.cos(2*x), 'blue4 o-', like=fig)
1530            fig.show().close()
1531            ```
1532            ![](https://vedo.embl.es/images/feats/plotxy.png)
1533
1534        Examples:
1535            - [plot_errbars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errbars.py)
1536            - [plot_errband.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errband.py)
1537            - [plot_pip.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_pip.py)
1538
1539                ![](https://vedo.embl.es/images/pyplot/plot_pip.png)
1540
1541            - [scatter1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter1.py)
1542            - [scatter2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter2.py)
1543
1544                ![](https://vedo.embl.es/images/pyplot/scatter2.png)
1545        """
1546        line = False
1547        if lw > 0:
1548            line = True
1549        if marker == "" and not line and not splined:
1550            marker = "o"
1551
1552        if like is None and vedo.last_figure is not None:
1553            if xlim is None and ylim == (None, None):
1554                like = vedo.last_figure
1555
1556        if like is not None:
1557            xlim = like.xlim
1558            ylim = like.ylim
1559            aspect = like.aspect
1560            padding = like.padding
1561
1562        if utils.is_sequence(xlim):
1563            # deal with user passing eg [x0, None]
1564            _x0, _x1 = xlim
1565            if _x0 is None:
1566                _x0 = data.min()
1567            if _x1 is None:
1568                _x1 = data.max()
1569            xlim = [_x0, _x1]
1570
1571        # purge NaN from data
1572        validIds = np.all(np.logical_not(np.isnan(data)))
1573        data = np.array(data[validIds])[0]
1574
1575        fig_kwargs["title"] = title
1576        fig_kwargs["xtitle"] = xtitle
1577        fig_kwargs["ytitle"] = ytitle
1578        fig_kwargs["ac"] = ac
1579        fig_kwargs["ztolerance"] = ztolerance
1580        fig_kwargs["grid"] = grid
1581
1582        x0, y0 = np.min(data, axis=0)
1583        x1, y1 = np.max(data, axis=0)
1584        if xerrors is not None and not error_band:
1585            x0 = min(data[:, 0] - xerrors)
1586            x1 = max(data[:, 0] + xerrors)
1587        if yerrors is not None:
1588            y0 = min(data[:, 1] - yerrors)
1589            y1 = max(data[:, 1] + yerrors)
1590
1591        if like is None:
1592            if xlim is None:
1593                xlim = (None, None)
1594            xlim = list(xlim)
1595            if xlim[0] is None:
1596                xlim[0] = x0
1597            if xlim[1] is None:
1598                xlim[1] = x1
1599            ylim = list(ylim)
1600            if ylim[0] is None:
1601                ylim[0] = y0
1602            if ylim[1] is None:
1603                ylim[1] = y1
1604
1605        self.entries = len(data)
1606        self.mean = data.mean()
1607        self.std = data.std()
1608        
1609        self.ztolerance = 0
1610        
1611        ######### the PlotXY marker
1612        # fall back solutions logic for colors
1613        if "c" in fig_kwargs:
1614            if mc is None:
1615                mc = fig_kwargs["c"]
1616            if lc is None:
1617                lc = fig_kwargs["c"]
1618            if ec is None:
1619                ec = fig_kwargs["c"]
1620        if lc is None:
1621            lc = "k"
1622        if mc is None:
1623            mc = lc
1624        if ma is None:
1625            ma = la
1626        if ec is None:
1627            if mc is None:
1628                ec = lc
1629            else:
1630                ec = mc
1631
1632        if label:
1633            nlab = LabelData()
1634            nlab.text = label
1635            nlab.tcolor = ac
1636            nlab.marker = marker
1637            if line and marker == "":
1638                nlab.marker = "-"
1639            nlab.mcolor = mc
1640            fig_kwargs["label"] = nlab
1641
1642        ############################################### Figure init
1643        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1644
1645        if not self.yscale:
1646            return
1647
1648        acts = []
1649
1650        ######### the PlotXY Line or Spline
1651        if dashed:
1652            l = shapes.DashedLine(data, c=lc, alpha=la, lw=lw)
1653            acts.append(l)
1654        elif splined:
1655            l = shapes.KSpline(data).lw(lw).c(lc).alpha(la)
1656            acts.append(l)
1657        elif line:
1658            l = shapes.Line(data, c=lc, alpha=la).lw(lw)
1659            acts.append(l)
1660
1661        if marker:
1662
1663            pts = np.c_[data, np.zeros(len(data))]
1664
1665            if utils.is_sequence(ms):
1666                ### variable point size
1667                mk = shapes.Marker(marker, s=1)
1668                mk.scale([1, 1 / self.yscale, 1])
1669                msv = np.zeros_like(pts)
1670                msv[:, 0] = ms
1671                marked = shapes.Glyph(
1672                    pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
1673                )
1674            else:
1675                ### fixed point size
1676                if ms is None:
1677                    ms = (xlim[1] - xlim[0]) / 100.0
1678
1679                if utils.is_sequence(mc):
1680                    fig_kwargs["marker_color"] = None  # for labels
1681                    mk = shapes.Marker(marker, s=ms)
1682                    mk.scale([1, 1 / self.yscale, 1])
1683                    msv = np.zeros_like(pts)
1684                    msv[:, 0] = 1
1685                    marked = shapes.Glyph(
1686                        pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
1687                    )
1688                else:
1689                    mk = shapes.Marker(marker, s=ms)
1690                    mk.scale([1, 1 / self.yscale, 1])
1691                    marked = shapes.Glyph(pts, mk, c=mc)
1692
1693            marked.name = "Marker"
1694            marked.alpha(ma)
1695            marked.z(3 * self.ztolerance)
1696            acts.append(marked)
1697
1698        ######### the PlotXY marker errors
1699        ztol = self.ztolerance
1700
1701        if error_band:
1702            yerrors = np.abs(yerrors)
1703            du = np.array(data)
1704            dd = np.array(data)
1705            du[:, 1] += yerrors
1706            dd[:, 1] -= yerrors
1707            if splined:
1708                res = len(data) * 20
1709                band1 = shapes.KSpline(du, res=res)
1710                band2 = shapes.KSpline(dd, res=res)
1711                band = shapes.Ribbon(band1, band2, res=(res, 2))
1712            else:
1713                dd = list(reversed(dd.tolist()))
1714                band = shapes.Line(du.tolist() + dd, closed=True)
1715                band.triangulate().lw(0)
1716            if ec is None:
1717                band.c(lc)
1718            else:
1719                band.c(ec)
1720            band.lighting("off").alpha(la).z(ztol / 20)
1721            acts.append(band)
1722
1723        else:
1724
1725            ## xerrors
1726            if xerrors is not None:
1727                if len(xerrors) == len(data):
1728                    errs = []
1729                    for i, val in enumerate(data):
1730                        xval, yval = val
1731                        xerr = xerrors[i] / 2
1732                        el = shapes.Line((xval - xerr, yval, ztol), (xval + xerr, yval, ztol))
1733                        el.lw(elw)
1734                        errs.append(el)
1735                    mxerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol)
1736                    acts.append(mxerrs)
1737                else:
1738                    vedo.logger.error("in PlotXY(xerrors=...): mismatch in array length")
1739
1740            ## yerrors
1741            if yerrors is not None:
1742                if len(yerrors) == len(data):
1743                    errs = []
1744                    for i, val in enumerate(data):
1745                        xval, yval = val
1746                        yerr = yerrors[i]
1747                        el = shapes.Line((xval, yval - yerr, ztol), (xval, yval + yerr, ztol))
1748                        el.lw(elw)
1749                        errs.append(el)
1750                    myerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol)
1751                    acts.append(myerrs)
1752                else:
1753                    vedo.logger.error("in PlotXY(yerrors=...): mismatch in array length")
1754
1755        self.insert(*acts, as3d=False)
1756        self.name = "PlotXY"
Arguments:
  • xerrors : (bool) show error bars associated to each point in x
  • yerrors : (bool) show error bars associated to each point in y
  • lw : (int) width of the line connecting points in pixel units. Set it to 0 to remove the line.
  • lc : (str) line color
  • la : (float) line "alpha", opacity of the line
  • dashed : (bool) draw a dashed line instead of a continuous line
  • splined : (bool) spline the line joining the point as a countinous curve
  • elw : (int) width of error bar lines in units of pixels
  • ec : (color) color of error bar, by default the same as marker color
  • error_band : (bool) represent errors on y as a filled error band. Use ec keyword to modify its color.
  • marker : (str, int) use a marker for the data points
  • ms : (float) marker size
  • mc : (color) color of the marker
  • ma : (float) opacity of the marker
  • xlim : (list) set limits to the range for the x variable
  • ylim : (list) set limits to the range for the y variable
  • aspect : (float, str) Desired aspect ratio. Use aspect="equal" to force the same units in x and y. Scaling factor is saved in Figure.yscale.
  • padding : (float, list) keep a padding space from the axes (as a fraction of the axis size). This can be a list of four numbers.
  • title : (str) title to appear on the top of the frame, like a header.
  • xtitle : (str) title for the x-axis, can also be set using axes=dict(xtitle="my x axis")
  • ytitle : (str) title for the y-axis, can also be set using axes=dict(ytitle="my y axis")
  • ac : (str) axes color
  • grid : (bool) show the background grid for the axes, can also be set using axes=dict(xygrid=True)
  • ztolerance : (float) a tolerance factor to superimpose objects (along the z-axis).
Example:
import numpy as np
from vedo.pyplot import plot
x = np.arange(0, np.pi, 0.1)
fig = plot(x, np.sin(2*x), 'r0-', aspect='equal')
fig+= plot(x, np.cos(2*x), 'blue4 o-', like=fig)
fig.show().close()

Examples:
class PlotBars(Figure):
1208class PlotBars(Figure):
1209    """Creates a `PlotBars(Figure)` object."""
1210
1211    def __init__(
1212        self,
1213        data,
1214        errors=False,
1215        logscale=False,
1216        fill=True,
1217        gap=0.02,
1218        radius=0.05,
1219        c="olivedrab",
1220        alpha=1,
1221        texture="",
1222        outline=False,
1223        lw=2,
1224        lc="k",
1225        # Figure and axes options:
1226        like=None,
1227        xlim=(None, None),
1228        ylim=(0, None),
1229        aspect=4 / 3,
1230        padding=(0.025, 0.025, 0, 0.05),
1231        #
1232        title="",
1233        xtitle=" ",
1234        ytitle=" ",
1235        ac="k",
1236        grid=False,
1237        ztolerance=None,
1238        **fig_kwargs,
1239    ):
1240        """
1241        Input must be in format `[counts, labels, colors, edges]`.
1242        Either or both `edges` and `colors` are optional and can be omitted.
1243
1244        Use keyword `like=...` if you want to use the same format of a previously
1245        created Figure (useful when superimposing Figures) to make sure
1246        they are compatible and comparable. If they are not compatible
1247        you will receive an error message.
1248
1249        Arguments:
1250            errors : (bool)
1251                show error bars
1252            logscale : (bool)
1253                use logscale on y-axis
1254            fill : (bool)
1255                fill bars with solid color `c`
1256            gap : (float)
1257                leave a small space btw bars
1258            radius : (float)
1259                border radius of the top of the histogram bar. Default value is 0.1.
1260            texture : (str)
1261                url or path to an image to be used as texture for the bin
1262            outline : (bool)
1263                show outline of the bins
1264            xtitle : (str)
1265                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1266            ytitle : (str)
1267                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1268            ac : (str)
1269                axes color
1270            padding : (float, list)
1271                keep a padding space from the axes (as a fraction of the axis size).
1272                This can be a list of four numbers.
1273            aspect : (float)
1274                the desired aspect ratio of the figure. Default is 4/3.
1275            grid : (bool)
1276                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1277
1278        Examples:
1279            - [plot_bars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_bars.py)
1280
1281               ![](https://vedo.embl.es/images/pyplot/plot_bars.png)
1282        """
1283        ndata = len(data)
1284        if ndata == 4:
1285            counts, xlabs, cols, edges = data
1286        elif ndata == 3:
1287            counts, xlabs, cols = data
1288            edges = np.array(range(len(counts) + 1)) + 0.5
1289        elif ndata == 2:
1290            counts, xlabs = data
1291            edges = np.array(range(len(counts) + 1)) + 0.5
1292            cols = [c] * len(counts)
1293        else:
1294            m = "barplot error: data must be given as [counts, labels, colors, edges] not\n"
1295            vedo.logger.error(m + f" {data}\n     bin edges and colors are optional.")
1296            raise RuntimeError()
1297
1298        # sanity checks
1299        assert len(counts) == len(xlabs)
1300        assert len(counts) == len(cols)
1301        assert len(counts) == len(edges) - 1
1302
1303        counts = np.asarray(counts)
1304        edges = np.asarray(edges)
1305
1306        if logscale:
1307            counts = np.log10(counts + 1)
1308            if ytitle == " ":
1309                ytitle = "log_10 (counts+1)"
1310
1311        if like is None and vedo.last_figure is not None:
1312            if xlim == (None, None) and ylim == (0, None):
1313                like = vedo.last_figure
1314
1315        if like is not None:
1316            xlim = like.xlim
1317            ylim = like.ylim
1318            aspect = like.aspect
1319            padding = like.padding
1320
1321        if utils.is_sequence(xlim):
1322            # deal with user passing eg [x0, None]
1323            _x0, _x1 = xlim
1324            if _x0 is None:
1325                _x0 = np.min(edges)
1326            if _x1 is None:
1327                _x1 = np.max(edges)
1328            xlim = [_x0, _x1]
1329
1330        x0, x1 = np.min(edges), np.max(edges)
1331        y0, y1 = ylim[0], np.max(counts)
1332
1333        if like is None:
1334            ylim = list(ylim)
1335            if xlim is None:
1336                xlim = [x0, x1]
1337            if ylim[1] is None:
1338                ylim[1] = y1
1339            if ylim[0] != 0:
1340                ylim[0] = y0
1341
1342        fig_kwargs["title"] = title
1343        fig_kwargs["xtitle"] = xtitle
1344        fig_kwargs["ytitle"] = ytitle
1345        fig_kwargs["ac"] = ac
1346        fig_kwargs["ztolerance"] = ztolerance
1347        fig_kwargs["grid"] = grid
1348
1349        centers = (edges[0:-1] + edges[1:]) / 2
1350        binsizes = (centers - edges[0:-1]) * 2
1351
1352        if "axes" not in fig_kwargs:
1353            fig_kwargs["axes"] = {}
1354
1355        _xlabs = []
1356        for center, xlb in zip(centers, xlabs):
1357            _xlabs.append([center, str(xlb)])
1358        fig_kwargs["axes"]["x_values_and_labels"] = _xlabs
1359
1360        ############################################### Figure
1361        self.statslegend = ""
1362        self.edges = edges
1363        self.centers = centers
1364        self.bins = edges  # internal used by "like"
1365        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1366        if not self.yscale:
1367            return
1368
1369        rs = []
1370        maxheigth = 0
1371        if fill:  #####################
1372            if outline:
1373                gap = 0
1374
1375            for i in range(len(centers)):
1376                binsize = binsizes[i]
1377                p0 = (edges[i] + gap * binsize, 0, 0)
1378                p1 = (edges[i + 1] - gap * binsize, counts[i], 0)
1379
1380                if radius:
1381                    rds = np.array([0, 0, radius, radius])
1382                    p1_yscaled = [p1[0], p1[1] * self.yscale, 0]
1383                    r = shapes.Rectangle(p0, p1_yscaled, radius=rds * binsize, res=6)
1384                    r.scale([1, 1 / self.yscale, 1])
1385                    r.radius = None  # so it doesnt get recreated and rescaled by insert()
1386                else:
1387                    r = shapes.Rectangle(p0, p1)
1388
1389                if texture:
1390                    r.texture(texture)
1391                    c = "w"
1392
1393                r.actor.PickableOff()
1394                maxheigth = max(maxheigth, p1[1])
1395                if c in colors.cmaps_names:
1396                    col = colors.color_map((p0[0] + p1[0]) / 2, c, edges[0], edges[-1])
1397                else:
1398                    col = cols[i]
1399                r.color(col).alpha(alpha).lighting("off")
1400                r.name = f"bar_{i}"
1401                r.z(self.ztolerance)
1402                rs.append(r)
1403
1404        elif outline:  #####################
1405            lns = [[edges[0], 0, 0]]
1406            for i in range(len(centers)):
1407                lns.append([edges[i], counts[i], 0])
1408                lns.append([edges[i + 1], counts[i], 0])
1409                maxheigth = max(maxheigth, counts[i])
1410            lns.append([edges[-1], 0, 0])
1411            outl = shapes.Line(lns, c=lc, alpha=alpha, lw=lw).z(self.ztolerance)
1412            outl.name = f"bar_outline_{i}"
1413            rs.append(outl)
1414
1415        if errors:  #####################
1416            for x, f in centers:
1417                err = np.sqrt(f)
1418                el = shapes.Line([x, f - err / 2, 0], [x, f + err / 2, 0], c=lc, alpha=alpha, lw=lw)
1419                el.z(self.ztolerance * 2)
1420                rs.append(el)
1421
1422        self.insert(*rs, as3d=False)
1423        self.name = "PlotBars"

Creates a PlotBars(Figure) object.

PlotBars( data, errors=False, logscale=False, fill=True, gap=0.02, radius=0.05, c='olivedrab', alpha=1, texture='', outline=False, lw=2, lc='k', like=None, xlim=(None, None), ylim=(0, None), aspect=1.3333333333333333, padding=(0.025, 0.025, 0, 0.05), title='', xtitle=' ', ytitle=' ', ac='k', grid=False, ztolerance=None, **fig_kwargs)
1211    def __init__(
1212        self,
1213        data,
1214        errors=False,
1215        logscale=False,
1216        fill=True,
1217        gap=0.02,
1218        radius=0.05,
1219        c="olivedrab",
1220        alpha=1,
1221        texture="",
1222        outline=False,
1223        lw=2,
1224        lc="k",
1225        # Figure and axes options:
1226        like=None,
1227        xlim=(None, None),
1228        ylim=(0, None),
1229        aspect=4 / 3,
1230        padding=(0.025, 0.025, 0, 0.05),
1231        #
1232        title="",
1233        xtitle=" ",
1234        ytitle=" ",
1235        ac="k",
1236        grid=False,
1237        ztolerance=None,
1238        **fig_kwargs,
1239    ):
1240        """
1241        Input must be in format `[counts, labels, colors, edges]`.
1242        Either or both `edges` and `colors` are optional and can be omitted.
1243
1244        Use keyword `like=...` if you want to use the same format of a previously
1245        created Figure (useful when superimposing Figures) to make sure
1246        they are compatible and comparable. If they are not compatible
1247        you will receive an error message.
1248
1249        Arguments:
1250            errors : (bool)
1251                show error bars
1252            logscale : (bool)
1253                use logscale on y-axis
1254            fill : (bool)
1255                fill bars with solid color `c`
1256            gap : (float)
1257                leave a small space btw bars
1258            radius : (float)
1259                border radius of the top of the histogram bar. Default value is 0.1.
1260            texture : (str)
1261                url or path to an image to be used as texture for the bin
1262            outline : (bool)
1263                show outline of the bins
1264            xtitle : (str)
1265                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1266            ytitle : (str)
1267                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1268            ac : (str)
1269                axes color
1270            padding : (float, list)
1271                keep a padding space from the axes (as a fraction of the axis size).
1272                This can be a list of four numbers.
1273            aspect : (float)
1274                the desired aspect ratio of the figure. Default is 4/3.
1275            grid : (bool)
1276                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1277
1278        Examples:
1279            - [plot_bars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_bars.py)
1280
1281               ![](https://vedo.embl.es/images/pyplot/plot_bars.png)
1282        """
1283        ndata = len(data)
1284        if ndata == 4:
1285            counts, xlabs, cols, edges = data
1286        elif ndata == 3:
1287            counts, xlabs, cols = data
1288            edges = np.array(range(len(counts) + 1)) + 0.5
1289        elif ndata == 2:
1290            counts, xlabs = data
1291            edges = np.array(range(len(counts) + 1)) + 0.5
1292            cols = [c] * len(counts)
1293        else:
1294            m = "barplot error: data must be given as [counts, labels, colors, edges] not\n"
1295            vedo.logger.error(m + f" {data}\n     bin edges and colors are optional.")
1296            raise RuntimeError()
1297
1298        # sanity checks
1299        assert len(counts) == len(xlabs)
1300        assert len(counts) == len(cols)
1301        assert len(counts) == len(edges) - 1
1302
1303        counts = np.asarray(counts)
1304        edges = np.asarray(edges)
1305
1306        if logscale:
1307            counts = np.log10(counts + 1)
1308            if ytitle == " ":
1309                ytitle = "log_10 (counts+1)"
1310
1311        if like is None and vedo.last_figure is not None:
1312            if xlim == (None, None) and ylim == (0, None):
1313                like = vedo.last_figure
1314
1315        if like is not None:
1316            xlim = like.xlim
1317            ylim = like.ylim
1318            aspect = like.aspect
1319            padding = like.padding
1320
1321        if utils.is_sequence(xlim):
1322            # deal with user passing eg [x0, None]
1323            _x0, _x1 = xlim
1324            if _x0 is None:
1325                _x0 = np.min(edges)
1326            if _x1 is None:
1327                _x1 = np.max(edges)
1328            xlim = [_x0, _x1]
1329
1330        x0, x1 = np.min(edges), np.max(edges)
1331        y0, y1 = ylim[0], np.max(counts)
1332
1333        if like is None:
1334            ylim = list(ylim)
1335            if xlim is None:
1336                xlim = [x0, x1]
1337            if ylim[1] is None:
1338                ylim[1] = y1
1339            if ylim[0] != 0:
1340                ylim[0] = y0
1341
1342        fig_kwargs["title"] = title
1343        fig_kwargs["xtitle"] = xtitle
1344        fig_kwargs["ytitle"] = ytitle
1345        fig_kwargs["ac"] = ac
1346        fig_kwargs["ztolerance"] = ztolerance
1347        fig_kwargs["grid"] = grid
1348
1349        centers = (edges[0:-1] + edges[1:]) / 2
1350        binsizes = (centers - edges[0:-1]) * 2
1351
1352        if "axes" not in fig_kwargs:
1353            fig_kwargs["axes"] = {}
1354
1355        _xlabs = []
1356        for center, xlb in zip(centers, xlabs):
1357            _xlabs.append([center, str(xlb)])
1358        fig_kwargs["axes"]["x_values_and_labels"] = _xlabs
1359
1360        ############################################### Figure
1361        self.statslegend = ""
1362        self.edges = edges
1363        self.centers = centers
1364        self.bins = edges  # internal used by "like"
1365        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1366        if not self.yscale:
1367            return
1368
1369        rs = []
1370        maxheigth = 0
1371        if fill:  #####################
1372            if outline:
1373                gap = 0
1374
1375            for i in range(len(centers)):
1376                binsize = binsizes[i]
1377                p0 = (edges[i] + gap * binsize, 0, 0)
1378                p1 = (edges[i + 1] - gap * binsize, counts[i], 0)
1379
1380                if radius:
1381                    rds = np.array([0, 0, radius, radius])
1382                    p1_yscaled = [p1[0], p1[1] * self.yscale, 0]
1383                    r = shapes.Rectangle(p0, p1_yscaled, radius=rds * binsize, res=6)
1384                    r.scale([1, 1 / self.yscale, 1])
1385                    r.radius = None  # so it doesnt get recreated and rescaled by insert()
1386                else:
1387                    r = shapes.Rectangle(p0, p1)
1388
1389                if texture:
1390                    r.texture(texture)
1391                    c = "w"
1392
1393                r.actor.PickableOff()
1394                maxheigth = max(maxheigth, p1[1])
1395                if c in colors.cmaps_names:
1396                    col = colors.color_map((p0[0] + p1[0]) / 2, c, edges[0], edges[-1])
1397                else:
1398                    col = cols[i]
1399                r.color(col).alpha(alpha).lighting("off")
1400                r.name = f"bar_{i}"
1401                r.z(self.ztolerance)
1402                rs.append(r)
1403
1404        elif outline:  #####################
1405            lns = [[edges[0], 0, 0]]
1406            for i in range(len(centers)):
1407                lns.append([edges[i], counts[i], 0])
1408                lns.append([edges[i + 1], counts[i], 0])
1409                maxheigth = max(maxheigth, counts[i])
1410            lns.append([edges[-1], 0, 0])
1411            outl = shapes.Line(lns, c=lc, alpha=alpha, lw=lw).z(self.ztolerance)
1412            outl.name = f"bar_outline_{i}"
1413            rs.append(outl)
1414
1415        if errors:  #####################
1416            for x, f in centers:
1417                err = np.sqrt(f)
1418                el = shapes.Line([x, f - err / 2, 0], [x, f + err / 2, 0], c=lc, alpha=alpha, lw=lw)
1419                el.z(self.ztolerance * 2)
1420                rs.append(el)
1421
1422        self.insert(*rs, as3d=False)
1423        self.name = "PlotBars"

Input must be in format [counts, labels, colors, edges]. Either or both edges and colors are optional and can be omitted.

Use keyword like=... if you want to use the same format of a previously created Figure (useful when superimposing Figures) to make sure they are compatible and comparable. If they are not compatible you will receive an error message.

Arguments:
  • errors : (bool) show error bars
  • logscale : (bool) use logscale on y-axis
  • fill : (bool) fill bars with solid color c
  • gap : (float) leave a small space btw bars
  • radius : (float) border radius of the top of the histogram bar. Default value is 0.1.
  • texture : (str) url or path to an image to be used as texture for the bin
  • outline : (bool) show outline of the bins
  • xtitle : (str) title for the x-axis, can also be set using axes=dict(xtitle="my x axis")
  • ytitle : (str) title for the y-axis, can also be set using axes=dict(ytitle="my y axis")
  • ac : (str) axes color
  • padding : (float, list) keep a padding space from the axes (as a fraction of the axis size). This can be a list of four numbers.
  • aspect : (float) the desired aspect ratio of the figure. Default is 4/3.
  • grid : (bool) show the background grid for the axes, can also be set using axes=dict(xygrid=True)
Examples:
def plot(*args, **kwargs):
1759def plot(*args, **kwargs):
1760    """
1761    Draw a 2D line plot, or scatter plot, of variable x vs variable y.
1762    Input format can be either `[allx], [allx, ally] or [(x1,y1), (x2,y2), ...]`
1763
1764    Use `like=...` if you want to use the same format of a previously
1765    created Figure (useful when superimposing Figures) to make sure
1766    they are compatible and comparable. If they are not compatible
1767    you will receive an error message.
1768
1769    Arguments:
1770        xerrors : (bool)
1771            show error bars associated to each point in x
1772        yerrors : (bool)
1773            show error bars associated to each point in y
1774        lw : (int)
1775            width of the line connecting points in pixel units.
1776            Set it to 0 to remove the line.
1777        lc : (str)
1778            line color
1779        la : (float)
1780            line "alpha", opacity of the line
1781        dashed : (bool)
1782            draw a dashed line instead of a continuous line
1783        splined : (bool)
1784            spline the line joining the point as a countinous curve
1785        elw : (int)
1786            width of error bar lines in units of pixels
1787        ec : (color)
1788            color of error bar, by default the same as marker color
1789        error_band : (bool)
1790            represent errors on y as a filled error band.
1791            Use `ec` keyword to modify its color.
1792        marker : (str, int)
1793            use a marker for the data points
1794        ms : (float)
1795            marker size
1796        mc : (color)
1797            color of the marker
1798        ma : (float)
1799            opacity of the marker
1800        xlim : (list)
1801            set limits to the range for the x variable
1802        ylim : (list)
1803            set limits to the range for the y variable
1804        aspect : (float)
1805            Desired aspect ratio.
1806            If None, it is automatically calculated to get a reasonable aspect ratio.
1807            Scaling factor is saved in Figure.yscale
1808        padding : (float, list)
1809            keep a padding space from the axes (as a fraction of the axis size).
1810            This can be a list of four numbers.
1811        title : (str)
1812            title to appear on the top of the frame, like a header.
1813        xtitle : (str)
1814            title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1815        ytitle : (str)
1816            title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1817        ac : (str)
1818            axes color
1819        grid : (bool)
1820            show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1821        ztolerance : (float)
1822            a tolerance factor to superimpose objects (along the z-axis).
1823
1824    Example:
1825        ```python
1826        import numpy as np
1827        from vedo.pyplot import plot
1828        from vedo import settings
1829        settings.remember_last_figure_format = True #############
1830        x = np.linspace(0, 6.28, num=50)
1831        fig = plot(np.sin(x), 'r-')
1832        fig+= plot(np.cos(x), 'bo-') # no need to specify like=...
1833        fig.show().close()
1834        ```
1835        <img src="https://user-images.githubusercontent.com/32848391/74363882-c3638300-4dcb-11ea-8a78-eb492ad9711f.png" width="600">
1836
1837    Examples:
1838        - [plot_errbars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errbars.py)
1839        - [plot_errband.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errband.py)
1840        - [plot_pip.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_pip.py)
1841
1842            ![](https://vedo.embl.es/images/pyplot/plot_pip.png)
1843
1844        - [scatter1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter1.py)
1845        - [scatter2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter2.py)
1846
1847
1848
1849    -------------------------------------------------------------------------
1850    .. note:: mode="bar"
1851
1852    Creates a `PlotBars(Figure)` object.
1853
1854    Input must be in format `[counts, labels, colors, edges]`.
1855    Either or both `edges` and `colors` are optional and can be omitted.
1856
1857    Arguments:
1858        errors : (bool)
1859            show error bars
1860        logscale : (bool)
1861            use logscale on y-axis
1862        fill : (bool)
1863            fill bars with solid color `c`
1864        gap : (float)
1865            leave a small space btw bars
1866        radius : (float)
1867            border radius of the top of the histogram bar. Default value is 0.1.
1868        texture : (str)
1869            url or path to an image to be used as texture for the bin
1870        outline : (bool)
1871            show outline of the bins
1872        xtitle : (str)
1873            title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1874        ytitle : (str)
1875            title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1876        ac : (str)
1877            axes color
1878        padding : (float, list)
1879            keep a padding space from the axes (as a fraction of the axis size).
1880            This can be a list of four numbers.
1881        aspect : (float)
1882            the desired aspect ratio of the figure. Default is 4/3.
1883        grid : (bool)
1884            show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1885
1886    Examples:
1887        - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
1888        - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
1889        - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
1890        - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)
1891
1892        ![](https://vedo.embl.es/images/pyplot/histo_1D.png)
1893
1894
1895    ----------------------------------------------------------------------
1896    .. note:: 2D functions
1897
1898    If input is an external function or a formula, draw the surface
1899    representing the function `f(x,y)`.
1900
1901    Arguments:
1902        x : (float)
1903            x range of values
1904        y : (float)
1905            y range of values
1906        zlimits : (float)
1907            limit the z range of the independent variable
1908        zlevels : (int)
1909            will draw the specified number of z-levels contour lines
1910        show_nan : (bool)
1911            show where the function does not exist as red points
1912        bins : (list)
1913            number of bins in x and y
1914
1915    Examples:
1916        - [plot_fxy1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy1.py)
1917
1918            ![](https://vedo.embl.es/images/pyplot/plot_fxy.png)
1919        
1920        - [plot_fxy2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy2.py)
1921
1922
1923    --------------------------------------------------------------------
1924    .. note:: mode="complex"
1925
1926    If `mode='complex'` draw the real value of the function and color map the imaginary part.
1927
1928    Arguments:
1929        cmap : (str)
1930            diverging color map (white means `imag(z)=0`)
1931        lw : (float)
1932            line with of the binning
1933        bins : (list)
1934            binning in x and y
1935
1936    Examples:
1937        - [plot_fxy.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy.py)
1938
1939            ![](https://user-images.githubusercontent.com/32848391/73392962-1709a300-42db-11ea-9278-30c9d6e5eeaa.png)
1940
1941
1942    --------------------------------------------------------------------
1943    .. note:: mode="polar"
1944
1945    If `mode='polar'` input arrays are interpreted as a list of polar angles and radii.
1946    Build a polar (radar) plot by joining the set of points in polar coordinates.
1947
1948    Arguments:
1949        title : (str)
1950            plot title
1951        tsize : (float)
1952            title size
1953        bins : (int)
1954            number of bins in phi
1955        r1 : (float)
1956            inner radius
1957        r2 : (float)
1958            outer radius
1959        lsize : (float)
1960            label size
1961        c : (color)
1962            color of the line
1963        ac : (color)
1964            color of the frame and labels
1965        alpha : (float)
1966            opacity of the frame
1967        ps : (int)
1968            point size in pixels, if ps=0 no point is drawn
1969        lw : (int)
1970            line width in pixels, if lw=0 no line is drawn
1971        deg : (bool)
1972            input array is in degrees
1973        vmax : (float)
1974            normalize radius to this maximum value
1975        fill : (bool)
1976            fill convex area with solid color
1977        splined : (bool)
1978            interpolate the set of input points
1979        show_disc : (bool)
1980            draw the outer ring axis
1981        nrays : (int)
1982            draw this number of axis rays (continuous and dashed)
1983        show_lines : (bool)
1984            draw lines to the origin
1985        show_angles : (bool)
1986            draw angle values
1987
1988    Examples:
1989        - [histo_polar.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_polar.py)
1990
1991            ![](https://user-images.githubusercontent.com/32848391/64992590-7fc82400-d8d4-11e9-9c10-795f4756a73f.png)
1992
1993
1994    --------------------------------------------------------------------
1995    .. note:: mode="spheric"
1996
1997    If `mode='spheric'` input must be an external function rho(theta, phi).
1998    A surface is created in spherical coordinates.
1999
2000    Return an `Figure(Assembly)` of 2 objects: the unit
2001    sphere (in wireframe representation) and the surface `rho(theta, phi)`.
2002
2003    Arguments:
2004        rfunc : function
2005            handle to a user defined function `rho(theta, phi)`.
2006        normalize : (bool)
2007            scale surface to fit inside the unit sphere
2008        res : (int)
2009            grid resolution of the unit sphere
2010        scalarbar : (bool)
2011            add a 3D scalarbar to the plot for radius
2012        c : (color)
2013            color of the unit sphere
2014        alpha : (float)
2015            opacity of the unit sphere
2016        cmap : (str)
2017            color map for the surface
2018
2019    Examples:
2020        - [plot_spheric.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_spheric.py)
2021
2022            ![](https://vedo.embl.es/images/pyplot/plot_spheric.png)
2023    """
2024    mode = kwargs.pop("mode", "")
2025    if "spher" in mode:
2026        return _plot_spheric(args[0], **kwargs)
2027
2028    if "bar" in mode:
2029        return PlotBars(args[0], **kwargs)
2030
2031    if isinstance(args[0], str) or "function" in str(type(args[0])):
2032        if "complex" in mode:
2033            return _plot_fz(args[0], **kwargs)
2034        return _plot_fxy(args[0], **kwargs)
2035
2036    # grab the matplotlib-like options
2037    optidx = None
2038    for i, a in enumerate(args):
2039        if i > 0 and isinstance(a, str):
2040            optidx = i
2041            break
2042    if optidx:
2043        opts = args[optidx].replace(" ", "")
2044        if "--" in opts:
2045            opts = opts.replace("--", "")
2046            kwargs["dashed"] = True
2047        elif "-" in opts:
2048            opts = opts.replace("-", "")
2049        else:
2050            kwargs["lw"] = 0
2051
2052        symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"]
2053
2054        allcols = list(colors.colors.keys()) + list(colors.color_nicks.keys())
2055        for cc in allcols:
2056            if cc == "o":
2057                continue
2058            if cc in opts:
2059                opts = opts.replace(cc, "")
2060                kwargs["lc"] = cc
2061                kwargs["mc"] = cc
2062                break
2063
2064        for ss in symbs:
2065            if ss in opts:
2066                opts = opts.replace(ss, "", 1)
2067                kwargs["marker"] = ss
2068                break
2069
2070        opts.replace(" ", "")
2071        if opts:
2072            vedo.logger.error(f"in plot(), could not understand option(s): {opts}")
2073
2074    if optidx == 1 or optidx is None:
2075        if utils.is_sequence(args[0][0]) and len(args[0][0]) > 1:
2076            # print('------------- case 1', 'plot([(x,y),..])')
2077            data = np.asarray(args[0])  # (x,y)
2078            x = np.asarray(data[:, 0])
2079            y = np.asarray(data[:, 1])
2080
2081        elif len(args) == 1 or optidx == 1:
2082            # print('------------- case 2', 'plot(x)')
2083            if "pandas" in str(type(args[0])):
2084                if "ytitle" not in kwargs:
2085                    kwargs.update({"ytitle": args[0].name.replace("_", "_ ")})
2086            x = np.linspace(0, len(args[0]), num=len(args[0]))
2087            y = np.asarray(args[0]).ravel()
2088
2089        elif utils.is_sequence(args[1]):
2090            # print('------------- case 3', 'plot(allx,ally)',str(type(args[0])))
2091            if "pandas" in str(type(args[0])):
2092                if "xtitle" not in kwargs:
2093                    kwargs.update({"xtitle": args[0].name.replace("_", "_ ")})
2094            if "pandas" in str(type(args[1])):
2095                if "ytitle" not in kwargs:
2096                    kwargs.update({"ytitle": args[1].name.replace("_", "_ ")})
2097            x = np.asarray(args[0]).ravel()
2098            y = np.asarray(args[1]).ravel()
2099
2100        elif utils.is_sequence(args[0]) and utils.is_sequence(args[0][0]):
2101            # print('------------- case 4', 'plot([allx,ally])')
2102            x = np.asarray(args[0][0]).ravel()
2103            y = np.asarray(args[0][1]).ravel()
2104
2105    elif optidx == 2:
2106        # print('------------- case 5', 'plot(x,y)')
2107        x = np.asarray(args[0]).ravel()
2108        y = np.asarray(args[1]).ravel()
2109
2110    else:
2111        vedo.logger.error(f"plot(): Could not understand input arguments {args}")
2112        return None
2113
2114    if "polar" in mode:
2115        return _plot_polar(np.c_[x, y], **kwargs)
2116
2117    return PlotXY(np.c_[x, y], **kwargs)

Draw a 2D line plot, or scatter plot, of variable x vs variable y. Input format can be either [allx], [allx, ally] or [(x1,y1), (x2,y2), ...]

Use like=... if you want to use the same format of a previously created Figure (useful when superimposing Figures) to make sure they are compatible and comparable. If they are not compatible you will receive an error message.

Arguments:
  • xerrors : (bool) show error bars associated to each point in x
  • yerrors : (bool) show error bars associated to each point in y
  • lw : (int) width of the line connecting points in pixel units. Set it to 0 to remove the line.
  • lc : (str) line color
  • la : (float) line "alpha", opacity of the line
  • dashed : (bool) draw a dashed line instead of a continuous line
  • splined : (bool) spline the line joining the point as a countinous curve
  • elw : (int) width of error bar lines in units of pixels
  • ec : (color) color of error bar, by default the same as marker color
  • error_band : (bool) represent errors on y as a filled error band. Use ec keyword to modify its color.
  • marker : (str, int) use a marker for the data points
  • ms : (float) marker size
  • mc : (color) color of the marker
  • ma : (float) opacity of the marker
  • xlim : (list) set limits to the range for the x variable
  • ylim : (list) set limits to the range for the y variable
  • aspect : (float) Desired aspect ratio. If None, it is automatically calculated to get a reasonable aspect ratio. Scaling factor is saved in Figure.yscale
  • padding : (float, list) keep a padding space from the axes (as a fraction of the axis size). This can be a list of four numbers.
  • title : (str) title to appear on the top of the frame, like a header.
  • xtitle : (str) title for the x-axis, can also be set using axes=dict(xtitle="my x axis")
  • ytitle : (str) title for the y-axis, can also be set using axes=dict(ytitle="my y axis")
  • ac : (str) axes color
  • grid : (bool) show the background grid for the axes, can also be set using axes=dict(xygrid=True)
  • ztolerance : (float) a tolerance factor to superimpose objects (along the z-axis).
Example:
import numpy as np
from vedo.pyplot import plot
from vedo import settings
settings.remember_last_figure_format = True #############
x = np.linspace(0, 6.28, num=50)
fig = plot(np.sin(x), 'r-')
fig+= plot(np.cos(x), 'bo-') # no need to specify like=...
fig.show().close()

Examples:

mode="bar"

Creates a PlotBars(Figure) object.

Input must be in format [counts, labels, colors, edges]. Either or both edges and colors are optional and can be omitted.

Arguments:
  • errors : (bool) show error bars
  • logscale : (bool) use logscale on y-axis
  • fill : (bool) fill bars with solid color c
  • gap : (float) leave a small space btw bars
  • radius : (float) border radius of the top of the histogram bar. Default value is 0.1.
  • texture : (str) url or path to an image to be used as texture for the bin
  • outline : (bool) show outline of the bins
  • xtitle : (str) title for the x-axis, can also be set using axes=dict(xtitle="my x axis")
  • ytitle : (str) title for the y-axis, can also be set using axes=dict(ytitle="my y axis")
  • ac : (str) axes color
  • padding : (float, list) keep a padding space from the axes (as a fraction of the axis size). This can be a list of four numbers.
  • aspect : (float) the desired aspect ratio of the figure. Default is 4/3.
  • grid : (bool) show the background grid for the axes, can also be set using axes=dict(xygrid=True)
Examples:


2D functions

If input is an external function or a formula, draw the surface representing the function f(x,y).

Arguments:
  • x : (float) x range of values
  • y : (float) y range of values
  • zlimits : (float) limit the z range of the independent variable
  • zlevels : (int) will draw the specified number of z-levels contour lines
  • show_nan : (bool) show where the function does not exist as red points
  • bins : (list) number of bins in x and y
Examples:

mode="complex"

If mode='complex' draw the real value of the function and color map the imaginary part.

Arguments:
  • cmap : (str) diverging color map (white means imag(z)=0)
  • lw : (float) line with of the binning
  • bins : (list) binning in x and y
Examples:

mode="polar"

If mode='polar' input arrays are interpreted as a list of polar angles and radii. Build a polar (radar) plot by joining the set of points in polar coordinates.

Arguments:
  • title : (str) plot title
  • tsize : (float) title size
  • bins : (int) number of bins in phi
  • r1 : (float) inner radius
  • r2 : (float) outer radius
  • lsize : (float) label size
  • c : (color) color of the line
  • ac : (color) color of the frame and labels
  • alpha : (float) opacity of the frame
  • ps : (int) point size in pixels, if ps=0 no point is drawn
  • lw : (int) line width in pixels, if lw=0 no line is drawn
  • deg : (bool) input array is in degrees
  • vmax : (float) normalize radius to this maximum value
  • fill : (bool) fill convex area with solid color
  • splined : (bool) interpolate the set of input points
  • show_disc : (bool) draw the outer ring axis
  • nrays : (int) draw this number of axis rays (continuous and dashed)
  • show_lines : (bool) draw lines to the origin
  • show_angles : (bool) draw angle values
Examples:

mode="spheric"

If mode='spheric' input must be an external function rho(theta, phi). A surface is created in spherical coordinates.

Return an Figure(Assembly) of 2 objects: the unit sphere (in wireframe representation) and the surface rho(theta, phi).

Arguments:
  • rfunc : function handle to a user defined function rho(theta, phi).
  • normalize : (bool) scale surface to fit inside the unit sphere
  • res : (int) grid resolution of the unit sphere
  • scalarbar : (bool) add a 3D scalarbar to the plot for radius
  • c : (color) color of the unit sphere
  • alpha : (float) opacity of the unit sphere
  • cmap : (str) color map for the surface
Examples:
def histogram(*args, **kwargs):
2120def histogram(*args, **kwargs):
2121    """
2122    Histogramming for 1D and 2D data arrays.
2123
2124    This is meant as a convenience function that creates the appropriate object
2125    based on the shape of the provided input data.
2126
2127    Use keyword `like=...` if you want to use the same format of a previously
2128    created Figure (useful when superimposing Figures) to make sure
2129    they are compatible and comparable. If they are not compatible
2130    you will receive an error message.
2131
2132    -------------------------------------------------------------------------
2133    .. note:: default mode, for 1D arrays
2134
2135    Creates a `Histogram1D(Figure)` object.
2136
2137    Arguments:
2138        weights : (list)
2139            An array of weights, of the same shape as `data`. Each value in `data`
2140            only contributes its associated weight towards the bin count (instead of 1).
2141        bins : (int)
2142            number of bins
2143        vrange : (list)
2144            restrict the range of the histogram
2145        density : (bool)
2146            normalize the area to 1 by dividing by the nr of entries and bin size
2147        logscale : (bool)
2148            use logscale on y-axis
2149        fill : (bool)
2150            fill bars with solid color `c`
2151        gap : (float)
2152            leave a small space btw bars
2153        radius : (float)
2154            border radius of the top of the histogram bar. Default value is 0.1.
2155        texture : (str)
2156            url or path to an image to be used as texture for the bin
2157        outline : (bool)
2158            show outline of the bins
2159        errors : (bool)
2160            show error bars
2161        xtitle : (str)
2162            title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
2163        ytitle : (str)
2164            title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
2165        padding : (float, list)
2166            keep a padding space from the axes (as a fraction of the axis size).
2167            This can be a list of four numbers.
2168        aspect : (float)
2169            the desired aspect ratio of the histogram. Default is 4/3.
2170        grid : (bool)
2171            show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
2172        ztolerance : (float)
2173            a tolerance factor to superimpose objects (along the z-axis).
2174
2175    Examples:
2176        - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
2177        - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
2178        - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
2179        - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)
2180
2181        ![](https://vedo.embl.es/images/pyplot/histo_1D.png)
2182
2183
2184    -------------------------------------------------------------------------
2185    .. note:: default mode, for 2D arrays
2186
2187    Input data formats `[(x1,x2,..), (y1,y2,..)] or [(x1,y1), (x2,y2),..]`
2188    are both valid.
2189
2190    Arguments:
2191        bins : (list)
2192            binning as (nx, ny)
2193        weights : (list)
2194            array of weights to assign to each entry
2195        cmap : (str, lookuptable)
2196            color map name or look up table
2197        alpha : (float)
2198            opacity of the histogram
2199        gap : (float)
2200            separation between adjacent bins as a fraction for their size.
2201            Set gap=-1 to generate a quad surface.
2202        scalarbar : (bool)
2203            add a scalarbar to right of the histogram
2204        like : (Figure)
2205            grab and use the same format of the given Figure (for superimposing)
2206        xlim : (list)
2207            [x0, x1] range of interest. If left to None will automatically
2208            choose the minimum or the maximum of the data range.
2209            Data outside the range are completely ignored.
2210        ylim : (list)
2211            [y0, y1] range of interest. If left to None will automatically
2212            choose the minimum or the maximum of the data range.
2213            Data outside the range are completely ignored.
2214        aspect : (float)
2215            the desired aspect ratio of the figure.
2216        title : (str)
2217            title of the plot to appear on top.
2218            If left blank some statistics will be shown.
2219        xtitle : (str)
2220            x axis title
2221        ytitle : (str)
2222            y axis title
2223        ztitle : (str)
2224            title for the scalar bar
2225        ac : (str)
2226            axes color, additional keyword for Axes can also be added
2227            using e.g. `axes=dict(xygrid=True)`
2228
2229    Examples:
2230        - [histo_2d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_a.py)
2231        - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py)
2232
2233        ![](https://vedo.embl.es/images/pyplot/histo_2D.png)
2234
2235
2236    -------------------------------------------------------------------------
2237    .. note:: mode="3d"
2238
2239    If `mode='3d'`, build a 2D histogram as 3D bars from a list of x and y values.
2240
2241    Arguments:
2242        xtitle : (str)
2243            x axis title
2244        bins : (int)
2245            nr of bins for the smaller range in x or y
2246        vrange : (list)
2247            range in x and y in format `[(xmin,xmax), (ymin,ymax)]`
2248        norm : (float)
2249            sets a scaling factor for the z axis (frequency axis)
2250        fill : (bool)
2251            draw solid hexagons
2252        cmap : (str)
2253            color map name for elevation
2254        gap : (float)
2255            keep a internal empty gap between bins [0,1]
2256        zscale : (float)
2257            rescale the (already normalized) zaxis for visual convenience
2258
2259    Examples:
2260        - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/histo_2d_b.py)
2261
2262
2263    -------------------------------------------------------------------------
2264    .. note:: mode="hexbin"
2265
2266    If `mode='hexbin'`, build a hexagonal histogram from a list of x and y values.
2267
2268    Arguments:
2269        xtitle : (str)
2270            x axis title
2271        bins : (int)
2272            nr of bins for the smaller range in x or y
2273        vrange : (list)
2274            range in x and y in format `[(xmin,xmax), (ymin,ymax)]`
2275        norm : (float)
2276            sets a scaling factor for the z axis (frequency axis)
2277        fill : (bool)
2278            draw solid hexagons
2279        cmap : (str)
2280            color map name for elevation
2281
2282    Examples:
2283        - [histo_hexagonal.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_hexagonal.py)
2284
2285        ![](https://vedo.embl.es/images/pyplot/histo_hexagonal.png)
2286
2287
2288    -------------------------------------------------------------------------
2289    .. note:: mode="polar"
2290
2291    If `mode='polar'` assume input is polar coordinate system (rho, theta):
2292
2293    Arguments:
2294        weights : (list)
2295            Array of weights, of the same shape as the input.
2296            Each value only contributes its associated weight towards the bin count (instead of 1).
2297        title : (str)
2298            histogram title
2299        tsize : (float)
2300            title size
2301        bins : (int)
2302            number of bins in phi
2303        r1 : (float)
2304            inner radius
2305        r2 : (float)
2306            outer radius
2307        phigap : (float)
2308            gap angle btw 2 radial bars, in degrees
2309        rgap : (float)
2310            gap factor along radius of numeric angle labels
2311        lpos : (float)
2312            label gap factor along radius
2313        lsize : (float)
2314            label size
2315        c : (color)
2316            color of the histogram bars, can be a list of length `bins`
2317        bc : (color)
2318            color of the frame and labels
2319        alpha : (float)
2320            opacity of the frame
2321        cmap : (str)
2322            color map name
2323        deg : (bool)
2324            input array is in degrees
2325        vmin : (float)
2326            minimum value of the radial axis
2327        vmax : (float)
2328            maximum value of the radial axis
2329        labels : (list)
2330            list of labels, must be of length `bins`
2331        show_disc : (bool)
2332            show the outer ring axis
2333        nrays : (int)
2334            draw this number of axis rays (continuous and dashed)
2335        show_lines : (bool)
2336            show lines to the origin
2337        show_angles : (bool)
2338            show angular values
2339        show_errors : (bool)
2340            show error bars
2341
2342    Examples:
2343        - [histo_polar.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_polar.py)
2344
2345        ![](https://vedo.embl.es/images/pyplot/histo_polar.png)
2346
2347
2348    -------------------------------------------------------------------------
2349    .. note:: mode="spheric"
2350
2351    If `mode='spheric'`, build a histogram from list of theta and phi values.
2352
2353    Arguments:
2354        rmax : (float)
2355            maximum radial elevation of bin
2356        res : (int)
2357            sphere resolution
2358        cmap : (str)
2359            color map name
2360        lw : (int)
2361            line width of the bin edges
2362
2363    Examples:
2364        - [histo_spheric.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_spheric.py)
2365
2366        ![](https://vedo.embl.es/images/pyplot/histo_spheric.png)
2367    """
2368    mode = kwargs.pop("mode", "")
2369    if len(args) == 2:  # x, y
2370
2371        if "spher" in mode:
2372            return _histogram_spheric(args[0], args[1], **kwargs)
2373
2374        if "hex" in mode:
2375            return _histogram_hex_bin(args[0], args[1], **kwargs)
2376
2377        if "3d" in mode.lower():
2378            return _histogram_quad_bin(args[0], args[1], **kwargs)
2379
2380        return Histogram2D(args[0], args[1], **kwargs)
2381
2382    elif len(args) == 1:
2383
2384        if isinstance(args[0], vedo.Volume):
2385            data = args[0].pointdata[0]
2386        elif isinstance(args[0], vedo.Points):
2387            pd0 = args[0].pointdata[0]
2388            if pd0 is not None:
2389                data = pd0.ravel()
2390            else:
2391                data = args[0].celldata[0].ravel()
2392        else:
2393            try:
2394                if "pandas" in str(type(args[0])):
2395                    if "xtitle" not in kwargs:
2396                        kwargs.update({"xtitle": args[0].name.replace("_", "_ ")})
2397            except:
2398                pass
2399            data = np.asarray(args[0])
2400
2401        if "spher" in mode:
2402            return _histogram_spheric(args[0][:, 0], args[0][:, 1], **kwargs)
2403
2404        if data.ndim == 1:
2405            if "polar" in mode:
2406                return _histogram_polar(data, **kwargs)
2407            return Histogram1D(data, **kwargs)
2408
2409        if "hex" in mode:
2410            return _histogram_hex_bin(args[0][:, 0], args[0][:, 1], **kwargs)
2411
2412        if "3d" in mode.lower():
2413            return _histogram_quad_bin(args[0][:, 0], args[0][:, 1], **kwargs)
2414
2415        return Histogram2D(args[0], **kwargs)
2416
2417    vedo.logger.error(f"in histogram(): could not understand input {args[0]}")
2418    return None

Histogramming for 1D and 2D data arrays.

This is meant as a convenience function that creates the appropriate object based on the shape of the provided input data.

Use keyword like=... if you want to use the same format of a previously created Figure (useful when superimposing Figures) to make sure they are compatible and comparable. If they are not compatible you will receive an error message.


default mode, for 1D arrays

Creates a Histogram1D(Figure) object.

Arguments:
  • weights : (list) An array of weights, of the same shape as data. Each value in data only contributes its associated weight towards the bin count (instead of 1).
  • bins : (int) number of bins
  • vrange : (list) restrict the range of the histogram
  • density : (bool) normalize the area to 1 by dividing by the nr of entries and bin size
  • logscale : (bool) use logscale on y-axis
  • fill : (bool) fill bars with solid color c
  • gap : (float) leave a small space btw bars
  • radius : (float) border radius of the top of the histogram bar. Default value is 0.1.
  • texture : (str) url or path to an image to be used as texture for the bin
  • outline : (bool) show outline of the bins
  • errors : (bool) show error bars
  • xtitle : (str) title for the x-axis, can also be set using axes=dict(xtitle="my x axis")
  • ytitle : (str) title for the y-axis, can also be set using axes=dict(ytitle="my y axis")
  • padding : (float, list) keep a padding space from the axes (as a fraction of the axis size). This can be a list of four numbers.
  • aspect : (float) the desired aspect ratio of the histogram. Default is 4/3.
  • grid : (bool) show the background grid for the axes, can also be set using axes=dict(xygrid=True)
  • ztolerance : (float) a tolerance factor to superimpose objects (along the z-axis).
Examples:


default mode, for 2D arrays

Input data formats [(x1,x2,..), (y1,y2,..)] or [(x1,y1), (x2,y2),..] are both valid.

Arguments:
  • bins : (list) binning as (nx, ny)
  • weights : (list) array of weights to assign to each entry
  • cmap : (str, lookuptable) color map name or look up table
  • alpha : (float) opacity of the histogram
  • gap : (float) separation between adjacent bins as a fraction for their size. Set gap=-1 to generate a quad surface.
  • scalarbar : (bool) add a scalarbar to right of the histogram
  • like : (Figure) grab and use the same format of the given Figure (for superimposing)
  • xlim : (list) [x0, x1] range of interest. If left to None will automatically choose the minimum or the maximum of the data range. Data outside the range are completely ignored.
  • ylim : (list) [y0, y1] range of interest. If left to None will automatically choose the minimum or the maximum of the data range. Data outside the range are completely ignored.
  • aspect : (float) the desired aspect ratio of the figure.
  • title : (str) title of the plot to appear on top. If left blank some statistics will be shown.
  • xtitle : (str) x axis title
  • ytitle : (str) y axis title
  • ztitle : (str) title for the scalar bar
  • ac : (str) axes color, additional keyword for Axes can also be added using e.g. axes=dict(xygrid=True)
Examples:


mode="3d"

If mode='3d', build a 2D histogram as 3D bars from a list of x and y values.

Arguments:
  • xtitle : (str) x axis title
  • bins : (int) nr of bins for the smaller range in x or y
  • vrange : (list) range in x and y in format [(xmin,xmax), (ymin,ymax)]
  • norm : (float) sets a scaling factor for the z axis (frequency axis)
  • fill : (bool) draw solid hexagons
  • cmap : (str) color map name for elevation
  • gap : (float) keep a internal empty gap between bins [0,1]
  • zscale : (float) rescale the (already normalized) zaxis for visual convenience
Examples:

mode="hexbin"

If mode='hexbin', build a hexagonal histogram from a list of x and y values.

Arguments:
  • xtitle : (str) x axis title
  • bins : (int) nr of bins for the smaller range in x or y
  • vrange : (list) range in x and y in format [(xmin,xmax), (ymin,ymax)]
  • norm : (float) sets a scaling factor for the z axis (frequency axis)
  • fill : (bool) draw solid hexagons
  • cmap : (str) color map name for elevation
Examples:


mode="polar"

If mode='polar' assume input is polar coordinate system (rho, theta):

Arguments:
  • weights : (list) Array of weights, of the same shape as the input. Each value only contributes its associated weight towards the bin count (instead of 1).
  • title : (str) histogram title
  • tsize : (float) title size
  • bins : (int) number of bins in phi
  • r1 : (float) inner radius
  • r2 : (float) outer radius
  • phigap : (float) gap angle btw 2 radial bars, in degrees
  • rgap : (float) gap factor along radius of numeric angle labels
  • lpos : (float) label gap factor along radius
  • lsize : (float) label size
  • c : (color) color of the histogram bars, can be a list of length bins
  • bc : (color) color of the frame and labels
  • alpha : (float) opacity of the frame
  • cmap : (str) color map name
  • deg : (bool) input array is in degrees
  • vmin : (float) minimum value of the radial axis
  • vmax : (float) maximum value of the radial axis
  • labels : (list) list of labels, must be of length bins
  • show_disc : (bool) show the outer ring axis
  • nrays : (int) draw this number of axis rays (continuous and dashed)
  • show_lines : (bool) show lines to the origin
  • show_angles : (bool) show angular values
  • show_errors : (bool) show error bars
Examples:


mode="spheric"

If mode='spheric', build a histogram from list of theta and phi values.

Arguments:
  • rmax : (float) maximum radial elevation of bin
  • res : (int) sphere resolution
  • cmap : (str) color map name
  • lw : (int) line width of the bin edges
Examples:

def fit( points, deg=1, niter=0, nstd=3, xerrors=None, yerrors=None, vrange=None, res=250, lw=3, c='red4') -> vedo.shapes.Line:
2421def fit(
2422    points, deg=1, niter=0, nstd=3, xerrors=None, yerrors=None, vrange=None, res=250, lw=3, c="red4"
2423) -> "vedo.shapes.Line":
2424    """
2425    Polynomial fitting with parameter error and error bands calculation.
2426    Errors bars in both x and y are supported.
2427
2428    Returns a `vedo.shapes.Line` object.
2429
2430    Additional information about the fitting output can be accessed with:
2431
2432    `fitd = fit(pts)`
2433
2434    - `fitd.coefficients` will contain the coefficients of the polynomial fit
2435    - `fitd.coefficient_errors`, errors on the fitting coefficients
2436    - `fitd.monte_carlo_coefficients`, fitting coefficient set from MC generation
2437    - `fitd.covariance_matrix`, covariance matrix as a numpy array
2438    - `fitd.reduced_chi2`, reduced chi-square of the fitting
2439    - `fitd.ndof`, number of degrees of freedom
2440    - `fitd.data_sigma`, mean data dispersion from the central fit assuming `Chi2=1`
2441    - `fitd.error_lines`, a `vedo.shapes.Line` object for the upper and lower error band
2442    - `fitd.error_band`, the `vedo.mesh.Mesh` object representing the error band
2443
2444    Errors on x and y can be specified. If left to `None` an estimate is made from
2445    the statistical spread of the dataset itself. Errors are always assumed gaussian.
2446
2447    Arguments:
2448        deg : (int)
2449            degree of the polynomial to be fitted
2450        niter : (int)
2451            number of monte-carlo iterations to compute error bands.
2452            If set to 0, return the simple least-squares fit with naive error estimation
2453            on coefficients only. A reasonable non-zero value to set is about 500, in
2454            this case *error_lines*, *error_band* and the other class attributes are filled
2455        nstd : (float)
2456            nr. of standard deviation to use for error calculation
2457        xerrors : (list)
2458            array of the same length of points with the errors on x
2459        yerrors : (list)
2460            array of the same length of points with the errors on y
2461        vrange : (list)
2462            specify the domain range of the fitting line
2463            (only affects visualization, but can be used to extrapolate the fit
2464            outside the data range)
2465        res : (int)
2466            resolution of the output fitted line and error lines
2467
2468    Examples:
2469        - [fit_polynomial1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fit_polynomial1.py)
2470
2471        ![](https://vedo.embl.es/images/pyplot/fitPolynomial1.png)
2472    """
2473    if isinstance(points, vedo.pointcloud.Points):
2474        points = points.vertices
2475    points = np.asarray(points)
2476    if len(points) == 2:  # assume user is passing [x,y]
2477        points = np.c_[points[0], points[1]]
2478    x = points[:, 0]
2479    y = points[:, 1]  # ignore z
2480
2481    n = len(x)
2482    ndof = n - deg - 1
2483    if vrange is not None:
2484        x0, x1 = vrange
2485    else:
2486        x0, x1 = np.min(x), np.max(x)
2487        if xerrors is not None:
2488            x0 -= xerrors[0] / 2
2489            x1 += xerrors[-1] / 2
2490
2491    tol = (x1 - x0) / 10000
2492    xr = np.linspace(x0, x1, res)
2493
2494    # project x errs on y
2495    if xerrors is not None:
2496        xerrors = np.asarray(xerrors)
2497        if yerrors is not None:
2498            yerrors = np.asarray(yerrors)
2499            w = 1.0 / yerrors
2500            coeffs = np.polyfit(x, y, deg, w=w, rcond=None)
2501        else:
2502            coeffs = np.polyfit(x, y, deg, rcond=None)
2503        # update yerrors, 1 bootstrap iteration is enough
2504        p1d = np.poly1d(coeffs)
2505        der = (p1d(x + tol) - p1d(x)) / tol
2506        yerrors = np.sqrt(yerrors * yerrors + np.power(der * xerrors, 2))
2507
2508    if yerrors is not None:
2509        yerrors = np.asarray(yerrors)
2510        w = 1.0 / yerrors
2511        coeffs, V = np.polyfit(x, y, deg, w=w, rcond=None, cov=True)
2512    else:
2513        w = 1
2514        coeffs, V = np.polyfit(x, y, deg, rcond=None, cov=True)
2515
2516    p1d = np.poly1d(coeffs)
2517    theor = p1d(xr)
2518    fitl = shapes.Line(np.c_[xr, theor], lw=lw, c=c).z(tol * 2)
2519    fitl.coefficients = coeffs
2520    fitl.covariance_matrix = V
2521    residuals2_sum = np.sum(np.power(p1d(x) - y, 2)) / ndof
2522    sigma = np.sqrt(residuals2_sum)
2523    fitl.reduced_chi2 = np.sum(np.power((p1d(x) - y) * w, 2)) / ndof
2524    fitl.ndof = ndof
2525    fitl.data_sigma = sigma  # worked out from data using chi2=1 hypo
2526    fitl.name = "LinearPolynomialFit"
2527
2528    if not niter:
2529        fitl.coefficient_errors = np.sqrt(np.diag(V))
2530        return fitl  ################################
2531
2532    if yerrors is not None:
2533        sigma = yerrors
2534    else:
2535        w = None
2536        fitl.reduced_chi2 = 1
2537
2538    Theors, all_coeffs = [], []
2539    for i in range(niter):
2540        noise = np.random.randn(n) * sigma
2541        coeffs = np.polyfit(x, y + noise, deg, w=w, rcond=None)
2542        all_coeffs.append(coeffs)
2543        P1d = np.poly1d(coeffs)
2544        Theor = P1d(xr)
2545        Theors.append(Theor)
2546    # all_coeffs = np.array(all_coeffs)
2547    fitl.monte_carlo_coefficients = np.array(all_coeffs)
2548
2549    stds = np.std(Theors, axis=0)
2550    fitl.coefficient_errors = np.std(all_coeffs, axis=0)
2551
2552    # check distributions on the fly
2553    # for i in range(deg+1):
2554    #     histogram(all_coeffs[:,i],title='par'+str(i)).show(new=1)
2555    # histogram(all_coeffs[:,0], all_coeffs[:,1],
2556    #           xtitle='param0', ytitle='param1',scalarbar=1).show(new=1)
2557    # histogram(all_coeffs[:,1], all_coeffs[:,2],
2558    #           xtitle='param1', ytitle='param2').show(new=1)
2559    # histogram(all_coeffs[:,0], all_coeffs[:,2],
2560    #           xtitle='param0', ytitle='param2').show(new=1)
2561
2562    error_lines = []
2563    for i in [nstd, -nstd]:
2564        pp = np.c_[xr, theor + stds * i]
2565        el = shapes.Line(pp, lw=1, alpha=0.2, c="k").z(tol)
2566        error_lines.append(el)
2567        el.name = "ErrorLine for sigma=" + str(i)
2568
2569    fitl.error_lines = error_lines
2570    l1 = error_lines[0].vertices.tolist()
2571    cband = l1 + list(reversed(error_lines[1].vertices.tolist())) + [l1[0]]
2572    fitl.error_band = shapes.Line(cband).triangulate().lw(0).c("k", 0.15)
2573    fitl.error_band.name = "PolynomialFitErrorBand"
2574    return fitl

Polynomial fitting with parameter error and error bands calculation. Errors bars in both x and y are supported.

Returns a vedo.shapes.Line object.

Additional information about the fitting output can be accessed with:

fitd = fit(pts)

  • fitd.coefficients will contain the coefficients of the polynomial fit
  • fitd.coefficient_errors, errors on the fitting coefficients
  • fitd.monte_carlo_coefficients, fitting coefficient set from MC generation
  • fitd.covariance_matrix, covariance matrix as a numpy array
  • fitd.reduced_chi2, reduced chi-square of the fitting
  • fitd.ndof, number of degrees of freedom
  • fitd.data_sigma, mean data dispersion from the central fit assuming Chi2=1
  • fitd.error_lines, a vedo.shapes.Line object for the upper and lower error band
  • fitd.error_band, the vedo.mesh.Mesh object representing the error band

Errors on x and y can be specified. If left to None an estimate is made from the statistical spread of the dataset itself. Errors are always assumed gaussian.

Arguments:
  • deg : (int) degree of the polynomial to be fitted
  • niter : (int) number of monte-carlo iterations to compute error bands. If set to 0, return the simple least-squares fit with naive error estimation on coefficients only. A reasonable non-zero value to set is about 500, in this case error_lines, error_band and the other class attributes are filled
  • nstd : (float) nr. of standard deviation to use for error calculation
  • xerrors : (list) array of the same length of points with the errors on x
  • yerrors : (list) array of the same length of points with the errors on y
  • vrange : (list) specify the domain range of the fitting line (only affects visualization, but can be used to extrapolate the fit outside the data range)
  • res : (int) resolution of the output fitted line and error lines
Examples:

def donut( fractions, title='', tsize=0.3, r1=1.7, r2=1, phigap=0, lpos=0.8, lsize=0.15, c=None, bc='k', alpha=1, labels=(), show_disc=False) -> vedo.assembly.Assembly:
3285def donut(
3286    fractions,
3287    title="",
3288    tsize=0.3,
3289    r1=1.7,
3290    r2=1,
3291    phigap=0,
3292    lpos=0.8,
3293    lsize=0.15,
3294    c=None,
3295    bc="k",
3296    alpha=1,
3297    labels=(),
3298    show_disc=False,
3299) -> "Assembly":
3300    """
3301    Donut plot or pie chart.
3302
3303    Arguments:
3304        title : (str)
3305            plot title
3306        tsize : (float)
3307            title size
3308        r1 : (float) inner radius
3309        r2 : (float)
3310            outer radius, starting from r1
3311        phigap : (float)
3312            gap angle btw 2 radial bars, in degrees
3313        lpos : (float)
3314            label gap factor along radius
3315        lsize : (float)
3316            label size
3317        c : (color)
3318            color of the plot slices
3319        bc : (color)
3320            color of the disc frame
3321        alpha : (float)
3322            opacity of the disc frame
3323        labels : (list)
3324            list of labels
3325        show_disc : (bool)
3326            show the outer ring axis
3327
3328    Examples:
3329        - [donut.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/donut.py)
3330
3331            ![](https://vedo.embl.es/images/pyplot/donut.png)
3332    """
3333    fractions = np.array(fractions, dtype=float)
3334    angles = np.add.accumulate(2 * np.pi * fractions)
3335    angles[-1] = 2 * np.pi
3336    if angles[-2] > 2 * np.pi:
3337        print("Error in donut(): fractions must sum to 1.")
3338        raise RuntimeError
3339
3340    cols = []
3341    for i, th in enumerate(np.linspace(0, 2 * np.pi, 360, endpoint=False)):
3342        for ia, a in enumerate(angles):
3343            if th < a:
3344                cols.append(c[ia])
3345                break
3346    labs = []
3347    if labels:
3348        angles = np.concatenate([[0], angles])
3349        labs = [""] * 360
3350        for i in range(len(labels)):
3351            a = (angles[i + 1] + angles[i]) / 2
3352            j = int(a / np.pi * 180)
3353            labs[j] = labels[i]
3354
3355    data = np.linspace(0, 2 * np.pi, 360, endpoint=False) + 0.005
3356    dn = _histogram_polar(
3357        data,
3358        title=title,
3359        bins=360,
3360        r1=r1,
3361        r2=r2,
3362        phigap=phigap,
3363        lpos=lpos,
3364        lsize=lsize,
3365        tsize=tsize,
3366        c=cols,
3367        bc=bc,
3368        alpha=alpha,
3369        vmin=0,
3370        vmax=1,
3371        labels=labs,
3372        show_disc=show_disc,
3373        show_lines=0,
3374        show_angles=0,
3375        show_errors=0,
3376    )
3377    dn.name = "Donut"
3378    return dn

Donut plot or pie chart.

Arguments:
  • title : (str) plot title
  • tsize : (float) title size
  • r1 : (float) inner radius
  • r2 : (float) outer radius, starting from r1
  • phigap : (float) gap angle btw 2 radial bars, in degrees
  • lpos : (float) label gap factor along radius
  • lsize : (float) label size
  • c : (color) color of the plot slices
  • bc : (color) color of the disc frame
  • alpha : (float) opacity of the disc frame
  • labels : (list) list of labels
  • show_disc : (bool) show the outer ring axis
Examples:
def violin( values, bins=10, vlim=None, x=0, width=3, splined=True, fill=True, c='violet', alpha=1, outline=True, centerline=True, lc='darkorchid', lw=3) -> vedo.assembly.Assembly:
3381def violin(
3382    values,
3383    bins=10,
3384    vlim=None,
3385    x=0,
3386    width=3,
3387    splined=True,
3388    fill=True,
3389    c="violet",
3390    alpha=1,
3391    outline=True,
3392    centerline=True,
3393    lc="darkorchid",
3394    lw=3,
3395) -> "Assembly":
3396    """
3397    Violin style histogram.
3398
3399    Arguments:
3400        bins : (int)
3401            number of bins
3402        vlim : (list)
3403            input value limits. Crop values outside range
3404        x : (float)
3405            x-position of the violin axis
3406        width : (float)
3407            width factor of the normalized distribution
3408        splined : (bool)
3409            spline the outline
3410        fill : (bool)
3411            fill violin with solid color
3412        outline : (bool)
3413            add the distribution outline
3414        centerline : (bool)
3415            add the vertical centerline at x
3416        lc : (color)
3417            line color
3418
3419    Examples:
3420        - [histo_violin.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/histo_violin.py)
3421
3422            ![](https://vedo.embl.es/images/pyplot/histo_violin.png)
3423    """
3424    fs, edges = np.histogram(values, bins=bins, range=vlim)
3425    mine, maxe = np.min(edges), np.max(edges)
3426    fs = fs.astype(float) / len(values) * width
3427
3428    rs = []
3429
3430    if splined:
3431        lnl, lnr = [(0, edges[0], 0)], [(0, edges[0], 0)]
3432        for i in range(bins):
3433            xc = (edges[i] + edges[i + 1]) / 2
3434            yc = fs[i]
3435            lnl.append([-yc, xc, 0])
3436            lnr.append([yc, xc, 0])
3437        lnl.append((0, edges[-1], 0))
3438        lnr.append((0, edges[-1], 0))
3439        spl = shapes.KSpline(lnl).x(x)
3440        spr = shapes.KSpline(lnr).x(x)
3441        spl.color(lc).alpha(alpha).lw(lw)
3442        spr.color(lc).alpha(alpha).lw(lw)
3443        if outline:
3444            rs.append(spl)
3445            rs.append(spr)
3446        if fill:
3447            rb = shapes.Ribbon(spl, spr, c=c, alpha=alpha).lighting("off")
3448            rs.append(rb)
3449
3450    else:
3451        lns1 = [[0, mine, 0]]
3452        for i in range(bins):
3453            lns1.append([fs[i], edges[i], 0])
3454            lns1.append([fs[i], edges[i + 1], 0])
3455        lns1.append([0, maxe, 0])
3456
3457        lns2 = [[0, mine, 0]]
3458        for i in range(bins):
3459            lns2.append([-fs[i], edges[i], 0])
3460            lns2.append([-fs[i], edges[i + 1], 0])
3461        lns2.append([0, maxe, 0])
3462
3463        if outline:
3464            rs.append(shapes.Line(lns1, c=lc, alpha=alpha, lw=lw).x(x))
3465            rs.append(shapes.Line(lns2, c=lc, alpha=alpha, lw=lw).x(x))
3466
3467        if fill:
3468            for i in range(bins):
3469                p0 = (-fs[i], edges[i], 0)
3470                p1 = (fs[i], edges[i + 1], 0)
3471                r = shapes.Rectangle(p0, p1).x(p0[0] + x)
3472                r.color(c).alpha(alpha).lighting("off")
3473                rs.append(r)
3474
3475    if centerline:
3476        cl = shapes.Line([0, mine, 0.01], [0, maxe, 0.01], c=lc, alpha=alpha, lw=2).x(x)
3477        rs.append(cl)
3478
3479    asse = Assembly(rs)
3480    asse.name = "Violin"
3481    return asse

Violin style histogram.

Arguments:
  • bins : (int) number of bins
  • vlim : (list) input value limits. Crop values outside range
  • x : (float) x-position of the violin axis
  • width : (float) width factor of the normalized distribution
  • splined : (bool) spline the outline
  • fill : (bool) fill violin with solid color
  • outline : (bool) add the distribution outline
  • centerline : (bool) add the vertical centerline at x
  • lc : (color) line color
Examples:
def whisker( data, s=0.25, c='k', lw=2, bc='blue', alpha=0.25, r=5, jitter=True, horizontal=False) -> vedo.assembly.Assembly:
3484def whisker(data, s=0.25, c="k", lw=2, bc="blue", alpha=0.25, r=5, jitter=True, horizontal=False) -> "Assembly":
3485    """
3486    Generate a "whisker" bar from a 1-dimensional dataset.
3487
3488    Arguments:
3489        s : (float)
3490            size of the box
3491        c : (color)
3492            color of the lines
3493        lw : (float)
3494            line width
3495        bc : (color)
3496            color of the box
3497        alpha : (float)
3498            transparency of the box
3499        r : (float)
3500            point radius in pixels (use value 0 to disable)
3501        jitter : (bool)
3502            add some randomness to points to avoid overlap
3503        horizontal : (bool)
3504            set horizontal layout
3505
3506    Examples:
3507        - [whiskers.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/whiskers.py)
3508
3509            ![](https://vedo.embl.es/images/pyplot/whiskers.png)
3510    """
3511    xvals = np.zeros_like(np.asarray(data))
3512    if jitter:
3513        xjit = np.random.randn(len(xvals)) * s / 9
3514        xjit = np.clip(xjit, -s / 2.1, s / 2.1)
3515        xvals += xjit
3516
3517    dmean = np.mean(data)
3518    dq05 = np.quantile(data, 0.05)
3519    dq25 = np.quantile(data, 0.25)
3520    dq75 = np.quantile(data, 0.75)
3521    dq95 = np.quantile(data, 0.95)
3522
3523    pts = None
3524    if r:
3525        pts = shapes.Points(np.array([xvals, data]).T, c=c, r=r)
3526
3527    rec = shapes.Rectangle([-s / 2, dq25], [s / 2, dq75], c=bc, alpha=alpha)
3528    rec.properties.LightingOff()
3529    rl = shapes.Line([[-s / 2, dq25], [s / 2, dq25], [s / 2, dq75], [-s / 2, dq75]], closed=True)
3530    l1 = shapes.Line([0, dq05, 0], [0, dq25, 0], c=c, lw=lw)
3531    l2 = shapes.Line([0, dq75, 0], [0, dq95, 0], c=c, lw=lw)
3532    lm = shapes.Line([-s / 2, dmean], [s / 2, dmean])
3533    lns = merge(l1, l2, lm, rl)
3534    asse = Assembly([lns, rec, pts])
3535    if horizontal:
3536        asse.rotate_z(-90)
3537    asse.name = "Whisker"
3538    asse.info["mean"] = dmean
3539    asse.info["quantile_05"] = dq05
3540    asse.info["quantile_25"] = dq25
3541    asse.info["quantile_75"] = dq75
3542    asse.info["quantile_95"] = dq95
3543    return asse

Generate a "whisker" bar from a 1-dimensional dataset.

Arguments:
  • s : (float) size of the box
  • c : (color) color of the lines
  • lw : (float) line width
  • bc : (color) color of the box
  • alpha : (float) transparency of the box
  • r : (float) point radius in pixels (use value 0 to disable)
  • jitter : (bool) add some randomness to points to avoid overlap
  • horizontal : (bool) set horizontal layout
Examples:
def streamplot( X, Y, U, V, direction='both', max_propagation=None, lw=2, cmap='viridis', probes=()) -> Optional[vedo.shapes.Lines]:
3546def streamplot(
3547    X, Y, U, V, direction="both", max_propagation=None, lw=2, cmap="viridis", probes=()
3548) -> Union["vedo.shapes.Lines", None]:
3549    """
3550    Generate a streamline plot of a vectorial field (U,V) defined at positions (X,Y).
3551    Returns a `Mesh` object.
3552
3553    Arguments:
3554        direction : (str)
3555            either "forward", "backward" or "both"
3556        max_propagation : (float)
3557            maximum physical length of the streamline
3558        lw : (float)
3559            line width in absolute units
3560
3561    Examples:
3562        - [plot_stream.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/plot_stream.py)
3563
3564            ![](https://vedo.embl.es/images/pyplot/plot_stream.png)
3565    """
3566    n = len(X)
3567    m = len(Y[0])
3568    if n != m:
3569        print("Limitation in streamplot(): only square grids are allowed.", n, m)
3570        raise RuntimeError()
3571
3572    xmin, xmax = X[0][0], X[-1][-1]
3573    ymin, ymax = Y[0][0], Y[-1][-1]
3574
3575    field = np.sqrt(U * U + V * V)
3576
3577    vol = vedo.Volume(field, dims=(n, n, 1))
3578
3579    uf = np.ravel(U, order="F")
3580    vf = np.ravel(V, order="F")
3581    vects = np.c_[uf, vf, np.zeros_like(uf)]
3582    vol.pointdata["StreamPlotField"] = vects
3583
3584    if len(probes) == 0:
3585        probe = shapes.Grid(pos=((n - 1) / 2, (n - 1) / 2, 0), s=(n - 1, n - 1), res=(n - 1, n - 1))
3586    else:
3587        if isinstance(probes, vedo.Points):
3588            probes = probes.vertices
3589        else:
3590            probes = np.array(probes, dtype=float)
3591            if len(probes[0]) == 2:
3592                probes = np.c_[probes[:, 0], probes[:, 1], np.zeros(len(probes))]
3593        sv = [(n - 1) / (xmax - xmin), (n - 1) / (ymax - ymin), 1]
3594        probes = probes - [xmin, ymin, 0]
3595        probes = np.multiply(probes, sv)
3596        probe = vedo.Points(probes)
3597
3598    stream = vol.compute_streamlines(probe, direction=direction, max_propagation=max_propagation)
3599    if stream:
3600        stream.lw(lw).cmap(cmap).lighting("off")
3601        stream.scale([1 / (n - 1) * (xmax - xmin), 1 / (n - 1) * (ymax - ymin), 1])
3602        stream.shift(xmin, ymin)
3603    return stream

Generate a streamline plot of a vectorial field (U,V) defined at positions (X,Y). Returns a Mesh object.

Arguments:
  • direction : (str) either "forward", "backward" or "both"
  • max_propagation : (float) maximum physical length of the streamline
  • lw : (float) line width in absolute units
Examples:
def matrix( M, title='Matrix', xtitle='', ytitle='', xlabels=(), ylabels=(), xrotation=0, cmap='Reds', vmin=None, vmax=None, precision=2, font='Theemim', scale=0, scalarbar=True, lc='white', lw=0, c='black', alpha=1) -> vedo.assembly.Assembly:
3606def matrix(
3607    M,
3608    title="Matrix",
3609    xtitle="",
3610    ytitle="",
3611    xlabels=(),
3612    ylabels=(),
3613    xrotation=0,
3614    cmap="Reds",
3615    vmin=None,
3616    vmax=None,
3617    precision=2,
3618    font="Theemim",
3619    scale=0,
3620    scalarbar=True,
3621    lc="white",
3622    lw=0,
3623    c="black",
3624    alpha=1,
3625) -> "Assembly":
3626    """
3627    Generate a matrix, or a 2D color-coded plot with bin labels.
3628
3629    Returns an `Assembly` object.
3630
3631    Arguments:
3632        M : (list, numpy array)
3633            the input array to visualize
3634        title : (str)
3635            title of the plot
3636        xtitle : (str)
3637            title of the horizontal colmuns
3638        ytitle : (str)
3639            title of the vertical rows
3640        xlabels : (list)
3641            individual string labels for each column. Must be of length m
3642        ylabels : (list)
3643            individual string labels for each row. Must be of length n
3644        xrotation : (float)
3645            rotation of the horizontal labels
3646        cmap : (str)
3647            color map name
3648        vmin : (float)
3649            minimum value of the colormap range
3650        vmax : (float)
3651            maximum value of the colormap range
3652        precision : (int)
3653            number of digits for the matrix entries or bins
3654        font : (str)
3655            font name. Check [available fonts here](https://vedo.embl.es/fonts).
3656
3657        scale : (float)
3658            size of the numeric entries or bin values
3659        scalarbar : (bool)
3660            add a scalar bar to the right of the plot
3661        lc : (str)
3662            color of the line separating the bins
3663        lw : (float)
3664            Width of the line separating the bins
3665        c : (str)
3666            text color
3667        alpha : (float)
3668            plot transparency
3669
3670    Examples:
3671        - [np_matrix.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/np_matrix.py)
3672
3673            ![](https://vedo.embl.es/images/pyplot/np_matrix.png)
3674    """
3675    M = np.asarray(M)
3676    n, m = M.shape
3677    gr = shapes.Grid(res=(m, n), s=(m / (m + n) * 2, n / (m + n) * 2), c=c, alpha=alpha)
3678    gr.wireframe(False).lc(lc).lw(lw)
3679
3680    matr = np.flip(np.flip(M), axis=1).ravel(order="C")
3681    gr.cmap(cmap, matr, on="cells", vmin=vmin, vmax=vmax)
3682    sbar = None
3683    if scalarbar:
3684        gr.add_scalarbar3d(title_font=font, label_font=font)
3685        sbar = gr.scalarbar
3686    labs = None
3687    if scale != 0:
3688        labs = gr.labels(
3689            on="cells",
3690            scale=scale / max(m, n),
3691            precision=precision,
3692            font=font,
3693            justify="center",
3694            c=c,
3695        )
3696        labs.z(0.001)
3697    t = None
3698    if title:
3699        if title == "Matrix":
3700            title += " " + str(n) + "x" + str(m)
3701        t = shapes.Text3D(title, font=font, s=0.04, justify="bottom-center", c=c)
3702        t.shift(0, n / (m + n) * 1.05)
3703
3704    xlabs = None
3705    if len(xlabels) == m:
3706        xlabs = []
3707        jus = "top-center"
3708        if xrotation > 44:
3709            jus = "right-center"
3710        for i in range(m):
3711            xl = shapes.Text3D(xlabels[i], font=font, s=0.02, justify=jus, c=c).rotate_z(xrotation)
3712            xl.shift((2 * i - m + 1) / (m + n), -n / (m + n) * 1.05)
3713            xlabs.append(xl)
3714
3715    ylabs = None
3716    if len(ylabels) == n:
3717        ylabels = list(reversed(ylabels))
3718        ylabs = []
3719        for i in range(n):
3720            yl = shapes.Text3D(ylabels[i], font=font, s=0.02, justify="right-center", c=c)
3721            yl.shift(-m / (m + n) * 1.05, (2 * i - n + 1) / (m + n))
3722            ylabs.append(yl)
3723
3724    xt = None
3725    if xtitle:
3726        xt = shapes.Text3D(xtitle, font=font, s=0.035, justify="top-center", c=c)
3727        xt.shift(0, -n / (m + n) * 1.05)
3728        if xlabs is not None:
3729            y0, y1 = xlabs[0].ybounds()
3730            xt.shift(0, -(y1 - y0) - 0.55 / (m + n))
3731    yt = None
3732    if ytitle:
3733        yt = shapes.Text3D(ytitle, font=font, s=0.035, justify="bottom-center", c=c).rotate_z(90)
3734        yt.shift(-m / (m + n) * 1.05, 0)
3735        if ylabs is not None:
3736            x0, x1 = ylabs[0].xbounds()
3737            yt.shift(-(x1 - x0) - 0.55 / (m + n), 0)
3738    asse = Assembly(gr, sbar, labs, t, xt, yt, xlabs, ylabs)
3739    asse.name = "Matrix"
3740    return asse

Generate a matrix, or a 2D color-coded plot with bin labels.

Returns an Assembly object.

Arguments:
  • M : (list, numpy array) the input array to visualize
  • title : (str) title of the plot
  • xtitle : (str) title of the horizontal colmuns
  • ytitle : (str) title of the vertical rows
  • xlabels : (list) individual string labels for each column. Must be of length m
  • ylabels : (list) individual string labels for each row. Must be of length n
  • xrotation : (float) rotation of the horizontal labels
  • cmap : (str) color map name
  • vmin : (float) minimum value of the colormap range
  • vmax : (float) maximum value of the colormap range
  • precision : (int) number of digits for the matrix entries or bins
  • font : (str) font name. Check available fonts here.
  • scale : (float) size of the numeric entries or bin values
  • scalarbar : (bool) add a scalar bar to the right of the plot
  • lc : (str) color of the line separating the bins
  • lw : (float) Width of the line separating the bins
  • c : (str) text color
  • alpha : (float) plot transparency
Examples:
class DirectedGraph(vedo.assembly.Assembly):
3890class DirectedGraph(Assembly):
3891    """
3892    Support for Directed Graphs.
3893    """
3894
3895    def __init__(self, **kargs):
3896        """
3897        A graph consists of a collection of nodes (without postional information)
3898        and a collection of edges connecting pairs of nodes.
3899        The task is to determine the node positions only based on their connections.
3900
3901        This class is derived from class `Assembly`, and it assembles 4 Mesh objects
3902        representing the graph, the node labels, edge labels and edge arrows.
3903
3904        Arguments:
3905            c : (color)
3906                Color of the Graph
3907            n : (int)
3908                number of the initial set of nodes
3909            layout : (int, str)
3910                layout in
3911                `['2d', 'fast2d', 'clustering2d', 'circular', 'circular3d', 'cone', 'force', 'tree']`.
3912                Each of these layouts has different available options.
3913
3914        ---------------------------------------------------------------
3915        .. note:: Options for layouts '2d', 'fast2d' and 'clustering2d'
3916
3917        Arguments:
3918            seed : (int)
3919                seed of the random number generator used to jitter point positions
3920            rest_distance : (float)
3921                manually set the resting distance
3922            nmax : (int)
3923                the maximum number of iterations to be used
3924            zrange : (list)
3925                expand 2d graph along z axis.
3926
3927        ---------------------------------------------------------------
3928        .. note:: Options for layouts 'circular', and 'circular3d':
3929
3930        Arguments:
3931            radius : (float)
3932                set the radius of the circles
3933            height : (float)
3934                set the vertical (local z) distance between the circles
3935            zrange : (float)
3936                expand 2d graph along z axis
3937
3938        ---------------------------------------------------------------
3939        .. note:: Options for layout 'cone'
3940
3941        Arguments:
3942            compactness : (float)
3943                ratio between the average width of a cone in the tree,
3944                and the height of the cone.
3945            compression : (bool)
3946                put children closer together, possibly allowing sub-trees to overlap.
3947                This is useful if the tree is actually the spanning tree of a graph.
3948            spacing : (float)
3949                space between layers of the tree
3950
3951        ---------------------------------------------------------------
3952        .. note:: Options for layout 'force'
3953
3954        Arguments:
3955            seed : (int)
3956                seed the random number generator used to jitter point positions
3957            bounds : (list)
3958                set the region in space in which to place the final graph
3959            nmax : (int)
3960                the maximum number of iterations to be used
3961            three_dimensional : (bool)
3962                allow optimization in the 3rd dimension too
3963            random_initial_points : (bool)
3964                use random positions within the graph bounds as initial points
3965
3966        Examples:
3967            - [lineage_graph.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/lineage_graph.py)
3968
3969                ![](https://vedo.embl.es/images/pyplot/graph_lineage.png)
3970
3971            - [graph_network.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/graph_network.py)
3972
3973                ![](https://vedo.embl.es/images/pyplot/graph_network.png)
3974        """
3975
3976        super().__init__()
3977
3978        self.nodes = []
3979        self.edges = []
3980
3981        self._node_labels = []  # holds strings
3982        self._edge_labels = []
3983        self.edge_orientations = []
3984        self.edge_glyph_position = 0.6
3985
3986        self.zrange = 0.0
3987
3988        self.rotX = 0
3989        self.rotY = 0
3990        self.rotZ = 0
3991
3992        self.arrow_scale = 0.15
3993        self.node_label_scale = None
3994        self.node_label_justify = "bottom-left"
3995
3996        self.edge_label_scale = None
3997
3998        self.mdg = vtki.new("MutableDirectedGraph")
3999
4000        n = kargs.pop("n", 0)
4001        for _ in range(n):
4002            self.add_node()
4003
4004        self._c = kargs.pop("c", (0.3, 0.3, 0.3))
4005
4006        self.gl = vtki.new("GraphLayout")
4007
4008        self.font = kargs.pop("font", "")
4009
4010        s = kargs.pop("layout", "2d")
4011        if isinstance(s, int):
4012            ss = ["2d", "fast2d", "clustering2d", "circular", "circular3d", "cone", "force", "tree"]
4013            s = ss[s]
4014        self.layout = s
4015
4016        if "2d" in s:
4017            if "clustering" in s:
4018                self.strategy = vtki.new("Clustering2DLayoutStrategy")
4019            elif "fast" in s:
4020                self.strategy = vtki.new("Fast2DLayoutStrategy")
4021            else:
4022                self.strategy = vtki.new("Simple2DLayoutStrategy")
4023            self.rotX = 180
4024            opt = kargs.pop("rest_distance", None)
4025            if opt is not None:
4026                self.strategy.SetRestDistance(opt)
4027            opt = kargs.pop("seed", None)
4028            if opt is not None:
4029                self.strategy.SetRandomSeed(opt)
4030            opt = kargs.pop("nmax", None)
4031            if opt is not None:
4032                self.strategy.SetMaxNumberOfIterations(opt)
4033            self.zrange = kargs.pop("zrange", 0)
4034
4035        elif "circ" in s:
4036            if "3d" in s:
4037                self.strategy = vtki.new("Simple3DCirclesStrategy")
4038                self.strategy.SetDirection(0, 0, -1)
4039                self.strategy.SetAutoHeight(True)
4040                self.strategy.SetMethod(1)
4041                self.rotX = -90
4042                opt = kargs.pop("radius", None)  # float
4043                if opt is not None:
4044                    self.strategy.SetMethod(0)
4045                    self.strategy.SetRadius(opt)  # float
4046                opt = kargs.pop("height", None)
4047                if opt is not None:
4048                    self.strategy.SetAutoHeight(False)
4049                    self.strategy.SetHeight(opt)  # float
4050            else:
4051                self.strategy = vtki.new("CircularLayoutStrategy")
4052                self.zrange = kargs.pop("zrange", 0)
4053
4054        elif "cone" in s:
4055            self.strategy = vtki.new("ConeLayoutStrategy")
4056            self.rotX = 180
4057            opt = kargs.pop("compactness", None)
4058            if opt is not None:
4059                self.strategy.SetCompactness(opt)
4060            opt = kargs.pop("compression", None)
4061            if opt is not None:
4062                self.strategy.SetCompression(opt)
4063            opt = kargs.pop("spacing", None)
4064            if opt is not None:
4065                self.strategy.SetSpacing(opt)
4066
4067        elif "force" in s:
4068            self.strategy = vtki.new("ForceDirectedLayoutStrategy")
4069            opt = kargs.pop("seed", None)
4070            if opt is not None:
4071                self.strategy.SetRandomSeed(opt)
4072            opt = kargs.pop("bounds", None)
4073            if opt is not None:
4074                self.strategy.SetAutomaticBoundsComputation(False)
4075                self.strategy.SetGraphBounds(opt)  # list
4076            opt = kargs.pop("nmax", None)
4077            if opt is not None:
4078                self.strategy.SetMaxNumberOfIterations(opt)  # int
4079            opt = kargs.pop("three_dimensional", True)
4080            if opt is not None:
4081                self.strategy.SetThreeDimensionalLayout(opt)  # bool
4082            opt = kargs.pop("random_initial_points", None)
4083            if opt is not None:
4084                self.strategy.SetRandomInitialPoints(opt)  # bool
4085
4086        elif "tree" in s:
4087            self.strategy = vtki.new("SpanTreeLayoutStrategy")
4088            self.rotX = 180
4089
4090        else:
4091            vedo.logger.error(f"Cannot understand layout {s}. Available layouts:")
4092            vedo.logger.error("[2d,fast2d,clustering2d,circular,circular3d,cone,force,tree]")
4093            raise RuntimeError()
4094
4095        self.gl.SetLayoutStrategy(self.strategy)
4096
4097        if len(kargs) > 0:
4098            vedo.logger.error(f"Cannot understand options: {kargs}")
4099
4100    def add_node(self, label="id") -> int:
4101        """Add a new node to the `Graph`."""
4102        v = self.mdg.AddVertex()  # vtk calls it vertex..
4103        self.nodes.append(v)
4104        if label == "id":
4105            label = int(v)
4106        self._node_labels.append(str(label))
4107        return v
4108
4109    def add_edge(self, v1, v2, label="") -> int:
4110        """Add a new edge between to nodes.
4111        An extra node is created automatically if needed."""
4112        nv = len(self.nodes)
4113        if v1 >= nv:
4114            for _ in range(nv, v1 + 1):
4115                self.add_node()
4116        nv = len(self.nodes)
4117        if v2 >= nv:
4118            for _ in range(nv, v2 + 1):
4119                self.add_node()
4120        e = self.mdg.AddEdge(v1, v2)
4121        self.edges.append(e)
4122        self._edge_labels.append(str(label))
4123        return e
4124
4125    def add_child(self, v, node_label="id", edge_label="") -> int:
4126        """Add a new edge to a new node as its child.
4127        The extra node is created automatically if needed."""
4128        nv = len(self.nodes)
4129        if v >= nv:
4130            for _ in range(nv, v + 1):
4131                self.add_node()
4132        child = self.mdg.AddChild(v)
4133        self.edges.append((v, child))
4134        self.nodes.append(child)
4135        if node_label == "id":
4136            node_label = int(child)
4137        self._node_labels.append(str(node_label))
4138        self._edge_labels.append(str(edge_label))
4139        return child
4140
4141    def build(self):
4142        """
4143        Build the `DirectedGraph(Assembly)`.
4144        Accessory objects are also created for labels and arrows.
4145        """
4146        self.gl.SetZRange(self.zrange)
4147        self.gl.SetInputData(self.mdg)
4148        self.gl.Update()
4149
4150        gr2poly = vtki.new("GraphToPolyData")
4151        gr2poly.EdgeGlyphOutputOn()
4152        gr2poly.SetEdgeGlyphPosition(self.edge_glyph_position)
4153        gr2poly.SetInputData(self.gl.GetOutput())
4154        gr2poly.Update()
4155
4156        dgraph = Mesh(gr2poly.GetOutput(0))
4157        # dgraph.clean() # WRONG!!! dont uncomment
4158        dgraph.flat().color(self._c).lw(2)
4159        dgraph.name = "DirectedGraph"
4160
4161        diagsz = self.diagonal_size() / 1.42
4162        if not diagsz:
4163            return None
4164
4165        dgraph.scale(1 / diagsz)
4166        if self.rotX:
4167            dgraph.rotate_x(self.rotX)
4168        if self.rotY:
4169            dgraph.rotate_y(self.rotY)
4170        if self.rotZ:
4171            dgraph.rotate_z(self.rotZ)
4172
4173        vecs = gr2poly.GetOutput(1).GetPointData().GetVectors()
4174        self.edge_orientations = utils.vtk2numpy(vecs)
4175
4176        # Use Glyph3D to repeat the glyph on all edges.
4177        arrows = None
4178        if self.arrow_scale:
4179            arrow_source = vtki.new("GlyphSource2D")
4180            arrow_source.SetGlyphTypeToEdgeArrow()
4181            arrow_source.SetScale(self.arrow_scale)
4182            arrow_source.Update()
4183            arrow_glyph = vtki.vtkGlyph3D()
4184            arrow_glyph.SetInputData(0, gr2poly.GetOutput(1))
4185            arrow_glyph.SetInputData(1, arrow_source.GetOutput())
4186            arrow_glyph.Update()
4187            arrows = Mesh(arrow_glyph.GetOutput())
4188            arrows.scale(1 / diagsz)
4189            arrows.lighting("off").color(self._c)
4190            if self.rotX:
4191                arrows.rotate_x(self.rotX)
4192            if self.rotY:
4193                arrows.rotate_y(self.rotY)
4194            if self.rotZ:
4195                arrows.rotate_z(self.rotZ)
4196            arrows.name = "DirectedGraphArrows"
4197
4198        node_labels = None
4199        if self._node_labels:
4200            node_labels = dgraph.labels(
4201                self._node_labels,
4202                scale=self.node_label_scale,
4203                precision=0,
4204                font=self.font,
4205                justify=self.node_label_justify,
4206            )
4207            node_labels.color(self._c).pickable(True)
4208            node_labels.name = "DirectedGraphNodeLabels"
4209
4210        edge_labels = None
4211        if self._edge_labels:
4212            edge_labels = dgraph.labels(
4213                self._edge_labels, on="cells", scale=self.edge_label_scale, precision=0, font=self.font
4214            )
4215            edge_labels.color(self._c).pickable(True)
4216            edge_labels.name = "DirectedGraphEdgeLabels"
4217
4218        super().__init__([dgraph, node_labels, edge_labels, arrows])
4219        self.name = "DirectedGraphAssembly"
4220        return self

Support for Directed Graphs.

DirectedGraph(**kargs)
3895    def __init__(self, **kargs):
3896        """
3897        A graph consists of a collection of nodes (without postional information)
3898        and a collection of edges connecting pairs of nodes.
3899        The task is to determine the node positions only based on their connections.
3900
3901        This class is derived from class `Assembly`, and it assembles 4 Mesh objects
3902        representing the graph, the node labels, edge labels and edge arrows.
3903
3904        Arguments:
3905            c : (color)
3906                Color of the Graph
3907            n : (int)
3908                number of the initial set of nodes
3909            layout : (int, str)
3910                layout in
3911                `['2d', 'fast2d', 'clustering2d', 'circular', 'circular3d', 'cone', 'force', 'tree']`.
3912                Each of these layouts has different available options.
3913
3914        ---------------------------------------------------------------
3915        .. note:: Options for layouts '2d', 'fast2d' and 'clustering2d'
3916
3917        Arguments:
3918            seed : (int)
3919                seed of the random number generator used to jitter point positions
3920            rest_distance : (float)
3921                manually set the resting distance
3922            nmax : (int)
3923                the maximum number of iterations to be used
3924            zrange : (list)
3925                expand 2d graph along z axis.
3926
3927        ---------------------------------------------------------------
3928        .. note:: Options for layouts 'circular', and 'circular3d':
3929
3930        Arguments:
3931            radius : (float)
3932                set the radius of the circles
3933            height : (float)
3934                set the vertical (local z) distance between the circles
3935            zrange : (float)
3936                expand 2d graph along z axis
3937
3938        ---------------------------------------------------------------
3939        .. note:: Options for layout 'cone'
3940
3941        Arguments:
3942            compactness : (float)
3943                ratio between the average width of a cone in the tree,
3944                and the height of the cone.
3945            compression : (bool)
3946                put children closer together, possibly allowing sub-trees to overlap.
3947                This is useful if the tree is actually the spanning tree of a graph.
3948            spacing : (float)
3949                space between layers of the tree
3950
3951        ---------------------------------------------------------------
3952        .. note:: Options for layout 'force'
3953
3954        Arguments:
3955            seed : (int)
3956                seed the random number generator used to jitter point positions
3957            bounds : (list)
3958                set the region in space in which to place the final graph
3959            nmax : (int)
3960                the maximum number of iterations to be used
3961            three_dimensional : (bool)
3962                allow optimization in the 3rd dimension too
3963            random_initial_points : (bool)
3964                use random positions within the graph bounds as initial points
3965
3966        Examples:
3967            - [lineage_graph.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/lineage_graph.py)
3968
3969                ![](https://vedo.embl.es/images/pyplot/graph_lineage.png)
3970
3971            - [graph_network.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/graph_network.py)
3972
3973                ![](https://vedo.embl.es/images/pyplot/graph_network.png)
3974        """
3975
3976        super().__init__()
3977
3978        self.nodes = []
3979        self.edges = []
3980
3981        self._node_labels = []  # holds strings
3982        self._edge_labels = []
3983        self.edge_orientations = []
3984        self.edge_glyph_position = 0.6
3985
3986        self.zrange = 0.0
3987
3988        self.rotX = 0
3989        self.rotY = 0
3990        self.rotZ = 0
3991
3992        self.arrow_scale = 0.15
3993        self.node_label_scale = None
3994        self.node_label_justify = "bottom-left"
3995
3996        self.edge_label_scale = None
3997
3998        self.mdg = vtki.new("MutableDirectedGraph")
3999
4000        n = kargs.pop("n", 0)
4001        for _ in range(n):
4002            self.add_node()
4003
4004        self._c = kargs.pop("c", (0.3, 0.3, 0.3))
4005
4006        self.gl = vtki.new("GraphLayout")
4007
4008        self.font = kargs.pop("font", "")
4009
4010        s = kargs.pop("layout", "2d")
4011        if isinstance(s, int):
4012            ss = ["2d", "fast2d", "clustering2d", "circular", "circular3d", "cone", "force", "tree"]
4013            s = ss[s]
4014        self.layout = s
4015
4016        if "2d" in s:
4017            if "clustering" in s:
4018                self.strategy = vtki.new("Clustering2DLayoutStrategy")
4019            elif "fast" in s:
4020                self.strategy = vtki.new("Fast2DLayoutStrategy")
4021            else:
4022                self.strategy = vtki.new("Simple2DLayoutStrategy")
4023            self.rotX = 180
4024            opt = kargs.pop("rest_distance", None)
4025            if opt is not None:
4026                self.strategy.SetRestDistance(opt)
4027            opt = kargs.pop("seed", None)
4028            if opt is not None:
4029                self.strategy.SetRandomSeed(opt)
4030            opt = kargs.pop("nmax", None)
4031            if opt is not None:
4032                self.strategy.SetMaxNumberOfIterations(opt)
4033            self.zrange = kargs.pop("zrange", 0)
4034
4035        elif "circ" in s:
4036            if "3d" in s:
4037                self.strategy = vtki.new("Simple3DCirclesStrategy")
4038                self.strategy.SetDirection(0, 0, -1)
4039                self.strategy.SetAutoHeight(True)
4040                self.strategy.SetMethod(1)
4041                self.rotX = -90
4042                opt = kargs.pop("radius", None)  # float
4043                if opt is not None:
4044                    self.strategy.SetMethod(0)
4045                    self.strategy.SetRadius(opt)  # float
4046                opt = kargs.pop("height", None)
4047                if opt is not None:
4048                    self.strategy.SetAutoHeight(False)
4049                    self.strategy.SetHeight(opt)  # float
4050            else:
4051                self.strategy = vtki.new("CircularLayoutStrategy")
4052                self.zrange = kargs.pop("zrange", 0)
4053
4054        elif "cone" in s:
4055            self.strategy = vtki.new("ConeLayoutStrategy")
4056            self.rotX = 180
4057            opt = kargs.pop("compactness", None)
4058            if opt is not None:
4059                self.strategy.SetCompactness(opt)
4060            opt = kargs.pop("compression", None)
4061            if opt is not None:
4062                self.strategy.SetCompression(opt)
4063            opt = kargs.pop("spacing", None)
4064            if opt is not None:
4065                self.strategy.SetSpacing(opt)
4066
4067        elif "force" in s:
4068            self.strategy = vtki.new("ForceDirectedLayoutStrategy")
4069            opt = kargs.pop("seed", None)
4070            if opt is not None:
4071                self.strategy.SetRandomSeed(opt)
4072            opt = kargs.pop("bounds", None)
4073            if opt is not None:
4074                self.strategy.SetAutomaticBoundsComputation(False)
4075                self.strategy.SetGraphBounds(opt)  # list
4076            opt = kargs.pop("nmax", None)
4077            if opt is not None:
4078                self.strategy.SetMaxNumberOfIterations(opt)  # int
4079            opt = kargs.pop("three_dimensional", True)
4080            if opt is not None:
4081                self.strategy.SetThreeDimensionalLayout(opt)  # bool
4082            opt = kargs.pop("random_initial_points", None)
4083            if opt is not None:
4084                self.strategy.SetRandomInitialPoints(opt)  # bool
4085
4086        elif "tree" in s:
4087            self.strategy = vtki.new("SpanTreeLayoutStrategy")
4088            self.rotX = 180
4089
4090        else:
4091            vedo.logger.error(f"Cannot understand layout {s}. Available layouts:")
4092            vedo.logger.error("[2d,fast2d,clustering2d,circular,circular3d,cone,force,tree]")
4093            raise RuntimeError()
4094
4095        self.gl.SetLayoutStrategy(self.strategy)
4096
4097        if len(kargs) > 0:
4098            vedo.logger.error(f"Cannot understand options: {kargs}")

A graph consists of a collection of nodes (without postional information) and a collection of edges connecting pairs of nodes. The task is to determine the node positions only based on their connections.

This class is derived from class Assembly, and it assembles 4 Mesh objects representing the graph, the node labels, edge labels and edge arrows.

Arguments:
  • c : (color) Color of the Graph
  • n : (int) number of the initial set of nodes
  • layout : (int, str) layout in ['2d', 'fast2d', 'clustering2d', 'circular', 'circular3d', 'cone', 'force', 'tree']. Each of these layouts has different available options.

Options for layouts '2d', 'fast2d' and 'clustering2d'
Arguments:
  • seed : (int) seed of the random number generator used to jitter point positions
  • rest_distance : (float) manually set the resting distance
  • nmax : (int) the maximum number of iterations to be used
  • zrange : (list) expand 2d graph along z axis.

Options for layouts 'circular', and 'circular3d':
Arguments:
  • radius : (float) set the radius of the circles
  • height : (float) set the vertical (local z) distance between the circles
  • zrange : (float) expand 2d graph along z axis

Options for layout 'cone'
Arguments:
  • compactness : (float) ratio between the average width of a cone in the tree, and the height of the cone.
  • compression : (bool) put children closer together, possibly allowing sub-trees to overlap. This is useful if the tree is actually the spanning tree of a graph.
  • spacing : (float) space between layers of the tree

Options for layout 'force'
Arguments:
  • seed : (int) seed the random number generator used to jitter point positions
  • bounds : (list) set the region in space in which to place the final graph
  • nmax : (int) the maximum number of iterations to be used
  • three_dimensional : (bool) allow optimization in the 3rd dimension too
  • random_initial_points : (bool) use random positions within the graph bounds as initial points
Examples:
def add_node(self, label='id') -> int:
4100    def add_node(self, label="id") -> int:
4101        """Add a new node to the `Graph`."""
4102        v = self.mdg.AddVertex()  # vtk calls it vertex..
4103        self.nodes.append(v)
4104        if label == "id":
4105            label = int(v)
4106        self._node_labels.append(str(label))
4107        return v

Add a new node to the Graph.

def add_edge(self, v1, v2, label='') -> int:
4109    def add_edge(self, v1, v2, label="") -> int:
4110        """Add a new edge between to nodes.
4111        An extra node is created automatically if needed."""
4112        nv = len(self.nodes)
4113        if v1 >= nv:
4114            for _ in range(nv, v1 + 1):
4115                self.add_node()
4116        nv = len(self.nodes)
4117        if v2 >= nv:
4118            for _ in range(nv, v2 + 1):
4119                self.add_node()
4120        e = self.mdg.AddEdge(v1, v2)
4121        self.edges.append(e)
4122        self._edge_labels.append(str(label))
4123        return e

Add a new edge between to nodes. An extra node is created automatically if needed.

def add_child(self, v, node_label='id', edge_label='') -> int:
4125    def add_child(self, v, node_label="id", edge_label="") -> int:
4126        """Add a new edge to a new node as its child.
4127        The extra node is created automatically if needed."""
4128        nv = len(self.nodes)
4129        if v >= nv:
4130            for _ in range(nv, v + 1):
4131                self.add_node()
4132        child = self.mdg.AddChild(v)
4133        self.edges.append((v, child))
4134        self.nodes.append(child)
4135        if node_label == "id":
4136            node_label = int(child)
4137        self._node_labels.append(str(node_label))
4138        self._edge_labels.append(str(edge_label))
4139        return child

Add a new edge to a new node as its child. The extra node is created automatically if needed.

def build(self):
4141    def build(self):
4142        """
4143        Build the `DirectedGraph(Assembly)`.
4144        Accessory objects are also created for labels and arrows.
4145        """
4146        self.gl.SetZRange(self.zrange)
4147        self.gl.SetInputData(self.mdg)
4148        self.gl.Update()
4149
4150        gr2poly = vtki.new("GraphToPolyData")
4151        gr2poly.EdgeGlyphOutputOn()
4152        gr2poly.SetEdgeGlyphPosition(self.edge_glyph_position)
4153        gr2poly.SetInputData(self.gl.GetOutput())
4154        gr2poly.Update()
4155
4156        dgraph = Mesh(gr2poly.GetOutput(0))
4157        # dgraph.clean() # WRONG!!! dont uncomment
4158        dgraph.flat().color(self._c).lw(2)
4159        dgraph.name = "DirectedGraph"
4160
4161        diagsz = self.diagonal_size() / 1.42
4162        if not diagsz:
4163            return None
4164
4165        dgraph.scale(1 / diagsz)
4166        if self.rotX:
4167            dgraph.rotate_x(self.rotX)
4168        if self.rotY:
4169            dgraph.rotate_y(self.rotY)
4170        if self.rotZ:
4171            dgraph.rotate_z(self.rotZ)
4172
4173        vecs = gr2poly.GetOutput(1).GetPointData().GetVectors()
4174        self.edge_orientations = utils.vtk2numpy(vecs)
4175
4176        # Use Glyph3D to repeat the glyph on all edges.
4177        arrows = None
4178        if self.arrow_scale:
4179            arrow_source = vtki.new("GlyphSource2D")
4180            arrow_source.SetGlyphTypeToEdgeArrow()
4181            arrow_source.SetScale(self.arrow_scale)
4182            arrow_source.Update()
4183            arrow_glyph = vtki.vtkGlyph3D()
4184            arrow_glyph.SetInputData(0, gr2poly.GetOutput(1))
4185            arrow_glyph.SetInputData(1, arrow_source.GetOutput())
4186            arrow_glyph.Update()
4187            arrows = Mesh(arrow_glyph.GetOutput())
4188            arrows.scale(1 / diagsz)
4189            arrows.lighting("off").color(self._c)
4190            if self.rotX:
4191                arrows.rotate_x(self.rotX)
4192            if self.rotY:
4193                arrows.rotate_y(self.rotY)
4194            if self.rotZ:
4195                arrows.rotate_z(self.rotZ)
4196            arrows.name = "DirectedGraphArrows"
4197
4198        node_labels = None
4199        if self._node_labels:
4200            node_labels = dgraph.labels(
4201                self._node_labels,
4202                scale=self.node_label_scale,
4203                precision=0,
4204                font=self.font,
4205                justify=self.node_label_justify,
4206            )
4207            node_labels.color(self._c).pickable(True)
4208            node_labels.name = "DirectedGraphNodeLabels"
4209
4210        edge_labels = None
4211        if self._edge_labels:
4212            edge_labels = dgraph.labels(
4213                self._edge_labels, on="cells", scale=self.edge_label_scale, precision=0, font=self.font
4214            )
4215            edge_labels.color(self._c).pickable(True)
4216            edge_labels.name = "DirectedGraphEdgeLabels"
4217
4218        super().__init__([dgraph, node_labels, edge_labels, arrows])
4219        self.name = "DirectedGraphAssembly"
4220        return self

Build the DirectedGraph(Assembly). Accessory objects are also created for labels and arrows.