vedo.pyplot

Advanced plotting functionalities.

   1#!/usr/bin/env python3
   2# -*- coding: utf-8 -*-
   3from typing import Union
   4from typing_extensions import Self
   5import numpy as np
   6
   7import vedo.vtkclasses as vtki
   8
   9import vedo
  10from vedo import settings
  11from vedo.transformations import cart2spher, spher2cart
  12from vedo import addons
  13from vedo import colors
  14from vedo import utils
  15from vedo import shapes
  16from vedo.pointcloud import merge
  17from vedo.mesh import Mesh
  18from vedo.assembly import Assembly
  19
  20__docformat__ = "google"
  21
  22__doc__ = """
  23Advanced plotting functionalities.
  24
  25![](https://vedo.embl.es/images/pyplot/fitPolynomial2.png)
  26"""
  27
  28__all__ = [
  29    "Figure",
  30    "Histogram1D",
  31    "Histogram2D",
  32    "PlotXY",
  33    "PlotBars",
  34    "plot",
  35    "histogram",
  36    "fit",
  37    "pie_chart",
  38    "violin",
  39    "whisker",
  40    "streamplot",
  41    "matrix",
  42    "DirectedGraph",
  43]
  44
  45
  46##########################################################################
  47class LabelData:
  48    """Helper internal class to hold label information."""
  49
  50    def __init__(self):
  51        """Helper internal class to hold label information."""
  52        self.text   = "dataset"
  53        self.tcolor = "black"
  54        self.marker = "s"
  55        self.mcolor = "black"
  56
  57
  58##########################################################################
  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
 629
 630
 631#########################################################################################
 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)
1004
1005
1006#########################################################################################
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"
1205
1206
1207#########################################################################################
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"
1424
1425
1426#########################################################################################
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"
1757
1758
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)
2118
2119
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
2419
2420
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
2575
2576
2577def _plot_fxy(
2578    z,
2579    xlim=(0, 3),
2580    ylim=(0, 3),
2581    zlim=(None, None),
2582    show_nan=True,
2583    zlevels=10,
2584    c=None,
2585    bc="aqua",
2586    alpha=1,
2587    texture="",
2588    bins=(100, 100),
2589    axes=True,
2590):
2591    import warnings
2592
2593    if c is not None:
2594        texture = None  # disable
2595
2596    ps = vtki.new("PlaneSource")
2597    ps.SetResolution(bins[0], bins[1])
2598    ps.SetNormal([0, 0, 1])
2599    ps.Update()
2600    poly = ps.GetOutput()
2601    dx = xlim[1] - xlim[0]
2602    dy = ylim[1] - ylim[0]
2603    todel, nans = [], []
2604
2605    for i in range(poly.GetNumberOfPoints()):
2606        px, py, _ = poly.GetPoint(i)
2607        xv = (px + 0.5) * dx + xlim[0]
2608        yv = (py + 0.5) * dy + ylim[0]
2609        try:
2610            with warnings.catch_warnings():
2611                warnings.simplefilter("ignore")
2612                zv = z(xv, yv)
2613                if np.isnan(zv) or np.isinf(zv) or np.iscomplex(zv):
2614                    zv = 0
2615                    todel.append(i)
2616                    nans.append([xv, yv, 0])
2617        except:
2618            zv = 0
2619            todel.append(i)
2620            nans.append([xv, yv, 0])
2621        poly.GetPoints().SetPoint(i, [xv, yv, zv])
2622
2623    if todel:
2624        cellIds = vtki.vtkIdList()
2625        poly.BuildLinks()
2626        for i in todel:
2627            poly.GetPointCells(i, cellIds)
2628            for j in range(cellIds.GetNumberOfIds()):
2629                poly.DeleteCell(cellIds.GetId(j))  # flag cell
2630        poly.RemoveDeletedCells()
2631        cl = vtki.new("CleanPolyData")
2632        cl.SetInputData(poly)
2633        cl.Update()
2634        poly = cl.GetOutput()
2635
2636    if not poly.GetNumberOfPoints():
2637        vedo.logger.error("function is not real in the domain")
2638        return None
2639
2640    if zlim[0]:
2641        poly = Mesh(poly).cut_with_plane((0, 0, zlim[0]), (0, 0, 1)).dataset
2642    if zlim[1]:
2643        poly = Mesh(poly).cut_with_plane((0, 0, zlim[1]), (0, 0, -1)).dataset
2644
2645    cmap = ""
2646    if c in colors.cmaps_names:
2647        cmap = c
2648        c = None
2649        bc = None
2650
2651    mesh = Mesh(poly, c, alpha).compute_normals().lighting("plastic")
2652
2653    if cmap:
2654        mesh.compute_elevation().cmap(cmap)
2655    if bc:
2656        mesh.bc(bc)
2657    if texture:
2658        mesh.texture(texture)
2659
2660    acts = [mesh]
2661    if zlevels:
2662        elevation = vtki.new("ElevationFilter")
2663        elevation.SetInputData(poly)
2664        bounds = poly.GetBounds()
2665        elevation.SetLowPoint(0, 0, bounds[4])
2666        elevation.SetHighPoint(0, 0, bounds[5])
2667        elevation.Update()
2668        bcf = vtki.new("BandedPolyDataContourFilter")
2669        bcf.SetInputData(elevation.GetOutput())
2670        bcf.SetScalarModeToValue()
2671        bcf.GenerateContourEdgesOn()
2672        bcf.GenerateValues(zlevels, elevation.GetScalarRange())
2673        bcf.Update()
2674        zpoly = bcf.GetContourEdgesOutput()
2675        zbandsact = Mesh(zpoly, "k", alpha).lw(1).lighting("off")
2676        zbandsact.mapper.SetResolveCoincidentTopologyToPolygonOffset()
2677        acts.append(zbandsact)
2678
2679    if show_nan and todel:
2680        bb = mesh.bounds()
2681        if bb[4] <= 0 and bb[5] >= 0:
2682            zm = 0.0
2683        else:
2684            zm = (bb[4] + bb[5]) / 2
2685        nans = np.array(nans) + [0, 0, zm]
2686        nansact = shapes.Points(nans, r=2, c="red5", alpha=alpha)
2687        nansact.properties.RenderPointsAsSpheresOff()
2688        acts.append(nansact)
2689
2690    if isinstance(axes, dict):
2691        axs = addons.Axes(mesh, **axes)
2692        acts.append(axs)
2693    elif axes:
2694        axs = addons.Axes(mesh)
2695        acts.append(axs)
2696
2697    assem = Assembly(acts)
2698    assem.name = "PlotFxy"
2699    return assem
2700
2701
2702def _plot_fz(
2703    z,
2704    x=(-1, 1),
2705    y=(-1, 1),
2706    zlimits=(None, None),
2707    cmap="PiYG",
2708    alpha=1,
2709    lw=0.1,
2710    bins=(75, 75),
2711    axes=True,
2712):
2713    ps = vtki.new("PlaneSource")
2714    ps.SetResolution(bins[0], bins[1])
2715    ps.SetNormal([0, 0, 1])
2716    ps.Update()
2717    poly = ps.GetOutput()
2718    dx = x[1] - x[0]
2719    dy = y[1] - y[0]
2720
2721    arrImg = []
2722    for i in range(poly.GetNumberOfPoints()):
2723        px, py, _ = poly.GetPoint(i)
2724        xv = (px + 0.5) * dx + x[0]
2725        yv = (py + 0.5) * dy + y[0]
2726        try:
2727            zv = z(complex(xv), complex(yv))
2728        except:
2729            zv = 0
2730        poly.GetPoints().SetPoint(i, [xv, yv, np.real(zv)])
2731        arrImg.append(np.imag(zv))
2732
2733    mesh = Mesh(poly, alpha).lighting("plastic")
2734    v = max(abs(np.min(arrImg)), abs(np.max(arrImg)))
2735    mesh.cmap(cmap, arrImg, vmin=-v, vmax=v)
2736    mesh.compute_normals().lw(lw)
2737
2738    if zlimits[0]:
2739        mesh.cut_with_plane((0, 0, zlimits[0]), (0, 0, 1))
2740    if zlimits[1]:
2741        mesh.cut_with_plane((0, 0, zlimits[1]), (0, 0, -1))
2742
2743    acts = [mesh]
2744    if axes:
2745        axs = addons.Axes(mesh, ztitle="Real part")
2746        acts.append(axs)
2747    asse = Assembly(acts)
2748    asse.name = "PlotFz"
2749    if isinstance(z, str):
2750        asse.name += " " + z
2751    return asse
2752
2753
2754def _plot_polar(
2755    rphi,
2756    title="",
2757    tsize=0.1,
2758    lsize=0.05,
2759    r1=0,
2760    r2=1,
2761    c="blue",
2762    bc="k",
2763    alpha=1,
2764    ps=5,
2765    lw=3,
2766    deg=False,
2767    vmax=None,
2768    fill=False,
2769    splined=False,
2770    nrays=8,
2771    show_disc=True,
2772    show_lines=True,
2773    show_angles=True,
2774):
2775    if len(rphi) == 2:
2776        rphi = np.stack((rphi[0], rphi[1]), axis=1)
2777
2778    rphi = np.array(rphi, dtype=float)
2779    thetas = rphi[:, 0]
2780    radii = rphi[:, 1]
2781
2782    k = 180 / np.pi
2783    if deg:
2784        thetas = np.array(thetas, dtype=float) / k
2785
2786    vals = []
2787    for v in thetas:  # normalize range
2788        t = np.arctan2(np.sin(v), np.cos(v))
2789        if t < 0:
2790            t += 2 * np.pi
2791        vals.append(t)
2792    thetas = np.array(vals, dtype=float)
2793
2794    if vmax is None:
2795        vmax = np.max(radii)
2796
2797    angles = []
2798    points = []
2799    for t, r in zip(thetas, radii):
2800        r = r / vmax * r2 + r1
2801        ct, st = np.cos(t), np.sin(t)
2802        points.append([r * ct, r * st, 0])
2803    p0 = points[0]
2804    points.append(p0)
2805
2806    r2e = r1 + r2
2807    lines = None
2808    if splined:
2809        lines = shapes.KSpline(points, closed=True)
2810        lines.c(c).lw(lw).alpha(alpha)
2811    elif lw:
2812        lines = shapes.Line(points)
2813        lines.c(c).lw(lw).alpha(alpha)
2814
2815    points.pop()
2816
2817    ptsact = None
2818    if ps:
2819        ptsact = shapes.Points(points, r=ps, c=c, alpha=alpha)
2820
2821    filling = None
2822    if fill and lw:
2823        faces = []
2824        coords = [[0, 0, 0]] + lines.vertices.tolist()
2825        for i in range(1, lines.npoints):
2826            faces.append([0, i, i + 1])
2827        filling = Mesh([coords, faces]).c(c).alpha(alpha)
2828
2829    back = None
2830    back2 = None
2831    if show_disc:
2832        back = shapes.Disc(r1=r2e, r2=r2e * 1.01, c=bc, res=(1, 360))
2833        back.z(-0.01).lighting("off").alpha(alpha)
2834        back2 = shapes.Disc(r1=r2e / 2, r2=r2e / 2 * 1.005, c=bc, res=(1, 360))
2835        back2.z(-0.01).lighting("off").alpha(alpha)
2836
2837    ti = None
2838    if title:
2839        ti = shapes.Text3D(title, (0, 0, 0), s=tsize, depth=0, justify="top-center")
2840        ti.pos(0, -r2e * 1.15, 0.01)
2841
2842    rays = []
2843    if show_disc:
2844        rgap = 0.05
2845        for t in np.linspace(0, 2 * np.pi, num=nrays, endpoint=False):
2846            ct, st = np.cos(t), np.sin(t)
2847            if show_lines:
2848                l = shapes.Line((0, 0, -0.01), (r2e * ct * 1.03, r2e * st * 1.03, -0.01))
2849                rays.append(l)
2850                ct2, st2 = np.cos(t + np.pi / nrays), np.sin(t + np.pi / nrays)
2851                lm = shapes.DashedLine((0, 0, -0.01), (r2e * ct2, r2e * st2, -0.01), spacing=0.25)
2852                rays.append(lm)
2853            elif show_angles:  # just the ticks
2854                l = shapes.Line(
2855                    (r2e * ct * 0.98, r2e * st * 0.98, -0.01),
2856                    (r2e * ct * 1.03, r2e * st * 1.03, -0.01),
2857                )
2858            if show_angles:
2859                if 0 <= t < np.pi / 2:
2860                    ju = "bottom-left"
2861                elif t == np.pi / 2:
2862                    ju = "bottom-center"
2863                elif np.pi / 2 < t <= np.pi:
2864                    ju = "bottom-right"
2865                elif np.pi < t < np.pi * 3 / 2:
2866                    ju = "top-right"
2867                elif t == np.pi * 3 / 2:
2868                    ju = "top-center"
2869                else:
2870                    ju = "top-left"
2871                a = shapes.Text3D(int(t * k), pos=(0, 0, 0), s=lsize, depth=0, justify=ju)
2872                a.pos(r2e * ct * (1 + rgap), r2e * st * (1 + rgap), -0.01)
2873                angles.append(a)
2874
2875    mrg = merge(back, back2, angles, rays, ti)
2876    if mrg:
2877        mrg.color(bc).alpha(alpha).lighting("off")
2878    rh = Assembly([lines, ptsact, filling] + [mrg])
2879    rh.name = "PlotPolar"
2880    return rh
2881
2882
2883def _plot_spheric(rfunc, normalize=True, res=33, scalarbar=True, c="grey", alpha=0.05, cmap="jet"):
2884    sg = shapes.Sphere(res=res, quads=True)
2885    sg.alpha(alpha).c(c).wireframe()
2886
2887    cgpts = sg.vertices
2888    r, theta, phi = cart2spher(*cgpts.T)
2889
2890    newr, inans = [], []
2891    for i in range(len(r)):
2892        try:
2893            ri = rfunc(theta[i], phi[i])
2894            if np.isnan(ri):
2895                inans.append(i)
2896                newr.append(1)
2897            else:
2898                newr.append(ri)
2899        except:
2900            inans.append(i)
2901            newr.append(1)
2902
2903    newr = np.array(newr, dtype=float)
2904    if normalize:
2905        newr = newr / np.max(newr)
2906        newr[inans] = 1
2907
2908    nanpts = []
2909    if inans:
2910        redpts = spher2cart(newr[inans], theta[inans], phi[inans]).T
2911        nanpts.append(shapes.Points(redpts, r=4, c="r"))
2912
2913    pts = spher2cart(newr, theta, phi).T
2914    ssurf = sg.clone()
2915    ssurf.vertices = pts
2916    if inans:
2917        ssurf.delete_cells_by_point_index(inans)
2918
2919    ssurf.alpha(1).wireframe(0).lw(0.1)
2920
2921    ssurf.cmap(cmap, newr)
2922    ssurf.compute_normals()
2923
2924    if scalarbar:
2925        xm = np.max([np.max(pts[0]), 1])
2926        ym = np.max([np.abs(np.max(pts[1])), 1])
2927        ssurf.mapper.SetScalarRange(np.min(newr), np.max(newr))
2928        sb3d = ssurf.add_scalarbar3d(size=(xm * 0.07, ym), c="k").scalarbar
2929        sb3d.rotate_x(90).pos(xm * 1.1, 0, -0.5)
2930    else:
2931        sb3d = None
2932
2933    sg.pickable(False)
2934    asse = Assembly([ssurf, sg] + nanpts + [sb3d])
2935    asse.name = "PlotSpheric"
2936    return asse
2937
2938
2939def _histogram_quad_bin(x, y, **kwargs):
2940    # generate a histogram with 3D bars
2941    #
2942    histo = Histogram2D(x, y, **kwargs)
2943
2944    gap = kwargs.pop("gap", 0)
2945    zscale = kwargs.pop("zscale", 1)
2946    cmap = kwargs.pop("cmap", "Blues_r")
2947
2948    gr = histo.objects[2]
2949    d = gr.diagonal_size()
2950    tol = d / 1_000_000  # tolerance
2951    if gap >= 0:
2952        gr.shrink(1 - gap - tol)
2953    gr.map_cells_to_points()
2954
2955    faces = np.array(gr.cells)
2956    s = 1 / histo.entries * len(faces) * zscale
2957    zvals = gr.pointdata["Scalars"] * s
2958
2959    pts1 = gr.vertices
2960    pts2 = np.copy(pts1)
2961    pts2[:, 2] = zvals + tol
2962    newpts = np.vstack([pts1, pts2])
2963    newzvals = np.hstack([zvals, zvals]) / s
2964
2965    n = pts1.shape[0]
2966    newfaces = []
2967    for f in faces:
2968        f0, f1, f2, f3 = f
2969        f0n, f1n, f2n, f3n = f + n
2970        newfaces.extend(
2971            [
2972                [f0, f1, f2, f3],
2973                [f0n, f1n, f2n, f3n],
2974                [f0, f1, f1n, f0n],
2975                [f1, f2, f2n, f1n],
2976                [f2, f3, f3n, f2n],
2977                [f3, f0, f0n, f3n],
2978            ]
2979        )
2980
2981    msh = Mesh([newpts, newfaces]).pickable(False)
2982    msh.cmap(cmap, newzvals, name="Frequency")
2983    msh.lw(1).lighting("ambient")
2984
2985    histo.objects[2] = msh
2986    histo.RemovePart(gr.actor)
2987    histo.AddPart(msh.actor)
2988    histo.objects.append(msh)
2989    return histo
2990
2991
2992def _histogram_hex_bin(
2993    xvalues, yvalues, bins=12, norm=1, fill=True, c=None, cmap="terrain_r", alpha=1
2994) -> "Assembly":
2995    xmin, xmax = np.min(xvalues), np.max(xvalues)
2996    ymin, ymax = np.min(yvalues), np.max(yvalues)
2997    dx, dy = xmax - xmin, ymax - ymin
2998
2999    if utils.is_sequence(bins):
3000        n, m = bins
3001    else:
3002        if xmax - xmin < ymax - ymin:
3003            n = bins
3004            m = np.rint(dy / dx * n / 1.2 + 0.5).astype(int)
3005        else:
3006            m = bins
3007            n = np.rint(dx / dy * m * 1.2 + 0.5).astype(int)
3008
3009    values = np.stack((xvalues, yvalues), axis=1)
3010    zs = [[0.0]] * len(values)
3011    values = np.append(values, zs, axis=1)
3012    cloud = vedo.Points(values)
3013
3014    col = None
3015    if c is not None:
3016        col = colors.get_color(c)
3017
3018    hexs, binmax = [], 0
3019    ki, kj = 1.33, 1.12
3020    r = 0.47 / n * 1.2 * dx
3021    for i in range(n + 3):
3022        for j in range(m + 2):
3023            cyl = vtki.new("CylinderSource")
3024            cyl.SetResolution(6)
3025            cyl.CappingOn()
3026            cyl.SetRadius(0.5)
3027            cyl.SetHeight(0.1)
3028            cyl.Update()
3029            t = vtki.vtkTransform()
3030            if not i % 2:
3031                p = (i / ki, j / kj, 0)
3032            else:
3033                p = (i / ki, j / kj + 0.45, 0)
3034            q = (p[0] / n * 1.2 * dx + xmin, p[1] / m * dy + ymin, 0)
3035            ne = len(cloud.closest_point(q, radius=r))
3036            if fill:
3037                t.Translate(p[0], p[1], ne / 2)
3038                t.Scale(1, 1, ne * 10)
3039            else:
3040                t.Translate(p[0], p[1], ne)
3041            t.RotateX(90)  # put it along Z
3042            tf = vtki.new("TransformPolyDataFilter")
3043            tf.SetInputData(cyl.GetOutput())
3044            tf.SetTransform(t)
3045            tf.Update()
3046            if c is None:
3047                col = i
3048            h = Mesh(tf.GetOutput(), c=col, alpha=alpha).flat()
3049            h.lighting("plastic")
3050            h.actor.PickableOff()
3051            hexs.append(h)
3052            if ne > binmax:
3053                binmax = ne
3054
3055    if cmap is not None:
3056        for h in hexs:
3057            z = h.bounds()[5]
3058            col = colors.color_map(z, cmap, 0, binmax)
3059            h.color(col)
3060
3061    asse = Assembly(hexs)
3062    asse.scale([1.2 / n * dx, 1 / m * dy, norm / binmax * (dx + dy) / 4])
3063    asse.pos([xmin, ymin, 0])
3064    asse.name = "HistogramHexBin"
3065    return asse
3066
3067
3068def _histogram_polar(
3069    values,
3070    weights=None,
3071    title="",
3072    tsize=0.1,
3073    bins=16,
3074    r1=0.25,
3075    r2=1,
3076    phigap=0.5,
3077    rgap=0.05,
3078    lpos=1,
3079    lsize=0.04,
3080    c="grey",
3081    bc="k",
3082    alpha=1,
3083    cmap=None,
3084    deg=False,
3085    vmin=None,
3086    vmax=None,
3087    labels=(),
3088    show_disc=True,
3089    nrays=8,
3090    show_lines=True,
3091    show_angles=True,
3092    show_errors=False,
3093):
3094    k = 180 / np.pi
3095    if deg:
3096        values = np.array(values, dtype=float) / k
3097    else:
3098        values = np.array(values, dtype=float)
3099
3100    vals = []
3101    for v in values:  # normalize range
3102        t = np.arctan2(np.sin(v), np.cos(v))
3103        if t < 0:
3104            t += 2 * np.pi
3105        vals.append(t + 0.00001)
3106
3107    histodata, edges = np.histogram(vals, weights=weights, bins=bins, range=(0, 2 * np.pi))
3108
3109    thetas = []
3110    for i in range(bins):
3111        thetas.append((edges[i] + edges[i + 1]) / 2)
3112
3113    if vmin is None:
3114        vmin = np.min(histodata)
3115    if vmax is None:
3116        vmax = np.max(histodata)
3117
3118    errors = np.sqrt(histodata)
3119    r2e = r1 + r2
3120    if show_errors:
3121        r2e += np.max(errors) / vmax * 1.5
3122
3123    back = None
3124    if show_disc:
3125        back = shapes.Disc(r1=r2e, r2=r2e * 1.01, c=bc, res=(1, 360))
3126        back.z(-0.01)
3127
3128    slices = []
3129    lines = []
3130    angles = []
3131    errbars = []
3132
3133    for i, t in enumerate(thetas):
3134        r = histodata[i] / vmax * r2
3135        d = shapes.Disc((0, 0, 0), r1, r1 + r, res=(1, 360))
3136        delta = np.pi / bins - np.pi / 2 - phigap / k
3137        d.cut_with_plane(normal=(np.cos(t + delta), np.sin(t + delta), 0))
3138        d.cut_with_plane(normal=(np.cos(t - delta), np.sin(t - delta), 0))
3139        if cmap is not None:
3140            cslice = colors.color_map(histodata[i], cmap, vmin, vmax)
3141            d.color(cslice)
3142        else:
3143            if c is None:
3144                d.color(i)
3145            elif utils.is_sequence(c) and len(c) == bins:
3146                d.color(c[i])
3147            else:
3148                d.color(c)
3149        d.alpha(alpha).lighting("off")
3150        slices.append(d)
3151
3152        ct, st = np.cos(t), np.sin(t)
3153
3154        if show_errors:
3155            show_lines = False
3156            err = np.sqrt(histodata[i]) / vmax * r2
3157            errl = shapes.Line(
3158                ((r1 + r - err) * ct, (r1 + r - err) * st, 0.01),
3159                ((r1 + r + err) * ct, (r1 + r + err) * st, 0.01),
3160            )
3161            errl.alpha(alpha).lw(3).color(bc)
3162            errbars.append(errl)
3163
3164    labs = []
3165    rays = []
3166    if show_disc:
3167        outerdisc = shapes.Disc(r1=r2e, r2=r2e * 1.01, c=bc, res=(1, 360))
3168        outerdisc.z(-0.01)
3169        innerdisc = shapes.Disc(r1=r2e / 2, r2=r2e / 2 * 1.005, c=bc, res=(1, 360))
3170        innerdisc.z(-0.01)
3171        rays.append(outerdisc)
3172        rays.append(innerdisc)
3173
3174        rgap = 0.05
3175        for t in np.linspace(0, 2 * np.pi, num=nrays, endpoint=False):
3176            ct, st = np.cos(t), np.sin(t)
3177            if show_lines:
3178                l = shapes.Line((0, 0, -0.01), (r2e * ct * 1.03, r2e * st * 1.03, -0.01))
3179                rays.append(l)
3180                ct2, st2 = np.cos(t + np.pi / nrays), np.sin(t + np.pi / nrays)
3181                lm = shapes.DashedLine((0, 0, -0.01), (r2e * ct2, r2e * st2, -0.01), spacing=0.25)
3182                rays.append(lm)
3183            elif show_angles:  # just the ticks
3184                l = shapes.Line(
3185                    (r2e * ct * 0.98, r2e * st * 0.98, -0.01),
3186                    (r2e * ct * 1.03, r2e * st * 1.03, -0.01),
3187                )
3188            if show_angles:
3189                if 0 <= t < np.pi / 2:
3190                    ju = "bottom-left"
3191                elif t == np.pi / 2:
3192                    ju = "bottom-center"
3193                elif np.pi / 2 < t <= np.pi:
3194                    ju = "bottom-right"
3195                elif np.pi < t < np.pi * 3 / 2:
3196                    ju = "top-right"
3197                elif t == np.pi * 3 / 2:
3198                    ju = "top-center"
3199                else:
3200                    ju = "top-left"
3201                a = shapes.Text3D(int(t * k), pos=(0, 0, 0), s=lsize, depth=0, justify=ju)
3202                a.pos(r2e * ct * (1 + rgap), r2e * st * (1 + rgap), -0.01)
3203                angles.append(a)
3204
3205    ti = None
3206    if title:
3207        ti = shapes.Text3D(title, (0, 0, 0), s=tsize, depth=0, justify="top-center")
3208        ti.pos(0, -r2e * 1.15, 0.01)
3209
3210    for i, t in enumerate(thetas):
3211        if i < len(labels):
3212            lab = shapes.Text3D(
3213                labels[i], (0, 0, 0), s=lsize, depth=0, justify="center"  # font="VTK",
3214            )
3215            lab.pos(
3216                r2e * np.cos(t) * (1 + rgap) * lpos / 2,
3217                r2e * np.sin(t) * (1 + rgap) * lpos / 2,
3218                0.01,
3219            )
3220            labs.append(lab)
3221
3222    mrg = merge(lines, angles, rays, ti, labs)
3223    if mrg:
3224        mrg.color(bc).lighting("off")
3225
3226    acts = slices + errbars + [mrg]
3227    asse = Assembly(acts)
3228    asse.frequencies = histodata
3229    asse.bins = edges
3230    asse.name = "HistogramPolar"
3231    return asse
3232
3233
3234def _histogram_spheric(thetavalues, phivalues, rmax=1.2, res=8, cmap="rainbow", gap=0.1):
3235
3236    x, y, z = spher2cart(np.ones_like(thetavalues) * 1.1, thetavalues, phivalues)
3237    ptsvals = np.c_[x, y, z]
3238
3239    sg = shapes.Sphere(res=res, quads=True).shrink(1 - gap)
3240    sgfaces = sg.cells
3241    sgpts = sg.vertices
3242
3243    cntrs = sg.cell_centers
3244    counts = np.zeros(len(cntrs))
3245    for p in ptsvals:
3246        cell = sg.closest_point(p, return_cell_id=True)
3247        counts[cell] += 1
3248    acounts = np.array(counts, dtype=float)
3249    counts *= (rmax - 1) / np.max(counts)
3250
3251    for cell, cn in enumerate(counts):
3252        if not cn:
3253            continue
3254        fs = sgfaces[cell]
3255        pts = sgpts[fs]
3256        _, t1, p1 = cart2spher(pts[:, 0], pts[:, 1], pts[:, 2])
3257        x, y, z = spher2cart(1 + cn, t1, p1)
3258        sgpts[fs] = np.c_[x, y, z]
3259
3260    sg.vertices = sgpts
3261    sg.cmap(cmap, acounts, on="cells")
3262    vals = sg.celldata["Scalars"]
3263
3264    faces = sg.cells
3265    points = sg.vertices.tolist() + [[0.0, 0.0, 0.0]]
3266    lp = len(points) - 1
3267    newfaces = []
3268    newvals = []
3269    for i, f in enumerate(faces):
3270        p0, p1, p2, p3 = f
3271        newfaces.append(f)
3272        newfaces.append([p0, lp, p1])
3273        newfaces.append([p1, lp, p2])
3274        newfaces.append([p2, lp, p3])
3275        newfaces.append([p3, lp, p0])
3276        for _ in range(5):
3277            newvals.append(vals[i])
3278
3279    newsg = Mesh([points, newfaces]).cmap(cmap, newvals, on="cells")
3280    newsg.compute_normals().flat()
3281    newsg.name = "HistogramSpheric"
3282    return newsg
3283
3284
3285def pie_chart(
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
3379
3380
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
3482
3483
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
3544
3545
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
3604
3605
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
3741
3742
3743def CornerPlot(points, pos=1, s=0.2, title="", c="b", bg="k", lines=True, dots=True):
3744    """
3745    Return a `vtkXYPlotActor` that is a plot of `x` versus `y`,
3746    where `points` is a list of `(x,y)` points.
3747
3748    Assign position following this convention:
3749
3750        - 1, topleft,
3751        - 2, topright,
3752        - 3, bottomleft,
3753        - 4, bottomright.
3754    """
3755    if len(points) == 2:  # passing [allx, ally]
3756        points = np.stack((points[0], points[1]), axis=1)
3757
3758    c = colors.get_color(c)  # allow different codings
3759    array_x = vtki.vtkFloatArray()
3760    array_y = vtki.vtkFloatArray()
3761    array_x.SetNumberOfTuples(len(points))
3762    array_y.SetNumberOfTuples(len(points))
3763    for i, p in enumerate(points):
3764        array_x.InsertValue(i, p[0])
3765        array_y.InsertValue(i, p[1])
3766    field = vtki.vtkFieldData()
3767    field.AddArray(array_x)
3768    field.AddArray(array_y)
3769    data = vtki.vtkDataObject()
3770    data.SetFieldData(field)
3771
3772    xyplot = vtki.new("XYPlotActor")
3773    xyplot.AddDataObjectInput(data)
3774    xyplot.SetDataObjectXComponent(0, 0)
3775    xyplot.SetDataObjectYComponent(0, 1)
3776    xyplot.SetXValuesToValue()
3777    xyplot.SetAdjustXLabels(0)
3778    xyplot.SetAdjustYLabels(0)
3779    xyplot.SetNumberOfXLabels(3)
3780
3781    xyplot.GetProperty().SetPointSize(5)
3782    xyplot.GetProperty().SetLineWidth(2)
3783    xyplot.GetProperty().SetColor(colors.get_color(bg))
3784    xyplot.SetPlotColor(0, c[0], c[1], c[2])
3785
3786    xyplot.SetXTitle(title)
3787    xyplot.SetYTitle("")
3788    xyplot.ExchangeAxesOff()
3789    xyplot.SetPlotPoints(dots)
3790
3791    if not lines:
3792        xyplot.PlotLinesOff()
3793
3794    if isinstance(pos, str):
3795        spos = 2
3796        if "top" in pos:
3797            if "left" in pos:
3798                spos = 1
3799            elif "right" in pos:
3800                spos = 2
3801        elif "bottom" in pos:
3802            if "left" in pos:
3803                spos = 3
3804            elif "right" in pos:
3805                spos = 4
3806        pos = spos
3807    if pos == 1:
3808        xyplot.GetPositionCoordinate().SetValue(0.0, 0.8, 0)
3809    elif pos == 2:
3810        xyplot.GetPositionCoordinate().SetValue(0.76, 0.8, 0)
3811    elif pos == 3:
3812        xyplot.GetPositionCoordinate().SetValue(0.0, 0.0, 0)
3813    elif pos == 4:
3814        xyplot.GetPositionCoordinate().SetValue(0.76, 0.0, 0)
3815    else:
3816        xyplot.GetPositionCoordinate().SetValue(pos[0], pos[1], 0)
3817
3818    xyplot.GetPosition2Coordinate().SetValue(s, s, 0)
3819    return xyplot
3820
3821
3822def CornerHistogram(
3823    values,
3824    bins=20,
3825    vrange=None,
3826    minbin=0,
3827    logscale=False,
3828    title="",
3829    c="g",
3830    bg="k",
3831    alpha=1,
3832    pos="bottom-left",
3833    s=0.175,
3834    lines=True,
3835    dots=False,
3836    nmax=None,
3837):
3838    """
3839    Build a histogram from a list of values in n bins.
3840    The resulting object is a 2D actor.
3841
3842    Use `vrange` to restrict the range of the histogram.
3843
3844    Use `nmax` to limit the sampling to this max nr of entries
3845
3846    Use `pos` to assign its position:
3847        - 1, topleft,
3848        - 2, topright,
3849        - 3, bottomleft,
3850        - 4, bottomright,
3851        - (x, y), as fraction of the rendering window
3852    """
3853    if hasattr(values, "dataset"):
3854        values = utils.vtk2numpy(values.dataset.GetPointData().GetScalars())
3855
3856    n = values.shape[0]
3857    if nmax and nmax < n:
3858        # subsample:
3859        idxs = np.linspace(0, n, num=int(nmax), endpoint=False).astype(int)
3860        values = values[idxs]
3861
3862    fs, edges = np.histogram(values, bins=bins, range=vrange)
3863
3864    if minbin:
3865        fs = fs[minbin:-1]
3866    if logscale:
3867        fs = np.log10(fs + 1)
3868    pts = []
3869    for i in range(len(fs)):
3870        pts.append([(edges[i] + edges[i + 1]) / 2, fs[i]])
3871
3872    cplot = CornerPlot(pts, pos, s, title, c, bg, lines, dots)
3873    cplot.SetNumberOfYLabels(2)
3874    cplot.SetNumberOfXLabels(3)
3875    tprop = vtki.vtkTextProperty()
3876    tprop.SetColor(colors.get_color(bg))
3877    tprop.SetFontFamily(vtki.VTK_FONT_FILE)
3878    tprop.SetFontFile(utils.get_font_path("Calco"))
3879    tprop.SetOpacity(alpha)
3880    cplot.SetAxisTitleTextProperty(tprop)
3881    cplot.GetProperty().SetOpacity(alpha)
3882    cplot.GetXAxisActor2D().SetLabelTextProperty(tprop)
3883    cplot.GetXAxisActor2D().SetTitleTextProperty(tprop)
3884    cplot.GetXAxisActor2D().SetFontFactor(0.55)
3885    cplot.GetYAxisActor2D().SetLabelFactor(0.0)
3886    cplot.GetYAxisActor2D().LabelVisibilityOff()
3887    return cplot
3888
3889
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
class Figure(vedo.assembly.Assembly):
 60class Figure(Assembly):
 61    """Format class for figures."""
 62
 63    def __init__(self, xlim, ylim, aspect=4 / 3, padding=(0.05, 0.05, 0.05, 0.05), **kwargs):
 64        """
 65        Create an empty formatted figure for plotting.
 66
 67        Arguments:
 68            xlim : (list)
 69                range of the x-axis as [x0, x1]
 70            ylim : (list)
 71                range of the y-axis as [y0, y1]
 72            aspect : (float, str)
 73                the desired aspect ratio of the histogram. Default is 4/3.
 74                Use `aspect="equal"` to force the same units in x and y.
 75            padding : (float, list)
 76                keep a padding space from the axes (as a fraction of the axis size).
 77                This can be a list of four numbers.
 78            xtitle : (str)
 79                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
 80            ytitle : (str)
 81                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
 82            grid : (bool)
 83                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
 84            axes : (dict)
 85                an extra dictionary of options for the `vedo.addons.Axes` object
 86        """
 87
 88        self.verbose = True  # printing to stdout on every mouse click
 89
 90        self.xlim = np.asarray(xlim)
 91        self.ylim = np.asarray(ylim)
 92        self.aspect = aspect
 93        self.padding = padding
 94        if not utils.is_sequence(self.padding):
 95            self.padding = [self.padding, self.padding, self.padding, self.padding]
 96
 97        self.force_scaling_types = (
 98            shapes.Glyph,
 99            shapes.Line,
100            shapes.Rectangle,
101            shapes.DashedLine,
102            shapes.Tube,
103            shapes.Ribbon,
104            shapes.GeoCircle,
105            shapes.Arc,
106            shapes.Grid,
107            # shapes.Arrows, # todo
108            # shapes.Arrows2D, # todo
109            shapes.Brace,  # todo
110        )
111
112        options = dict(kwargs)
113
114        self.title  = options.pop("title", "")
115        self.xtitle = options.pop("xtitle", " ")
116        self.ytitle = options.pop("ytitle", " ")
117        number_of_divisions = 6
118
119        self.legend = None
120        self.labels = []
121        self.label = options.pop("label", None)
122        if self.label:
123            self.labels = [self.label]
124
125        self.axopts = options.pop("axes", {})
126        if isinstance(self.axopts, (bool, int, float)):
127            if self.axopts:
128                self.axopts = {}
129        if self.axopts or isinstance(self.axopts, dict):
130            number_of_divisions = self.axopts.pop("number_of_divisions", number_of_divisions)
131
132            self.axopts["xtitle"] = self.xtitle
133            self.axopts["ytitle"] = self.ytitle
134
135            if "xygrid" not in self.axopts:  ## modify the default
136                self.axopts["xygrid"] = options.pop("grid", False)
137
138            if "xygrid_transparent" not in self.axopts:  ## modify the default
139                self.axopts["xygrid_transparent"] = True
140
141            if "xtitle_position" not in self.axopts:  ## modify the default
142                self.axopts["xtitle_position"] = 0.5
143                self.axopts["xtitle_justify"] = "top-center"
144
145            if "ytitle_position" not in self.axopts:  ## modify the default
146                self.axopts["ytitle_position"] = 0.5
147                self.axopts["ytitle_justify"] = "bottom-center"
148
149            if self.label:
150                if "c" in self.axopts:
151                    self.label.tcolor = self.axopts["c"]
152
153        x0, x1 = self.xlim
154        y0, y1 = self.ylim
155        dx = x1 - x0
156        dy = y1 - y0
157        x0lim, x1lim = (x0 - self.padding[0] * dx, x1 + self.padding[1] * dx)
158        y0lim, y1lim = (y0 - self.padding[2] * dy, y1 + self.padding[3] * dy)
159        dy = y1lim - y0lim
160
161        self.axes = None
162        if xlim[0] >= xlim[1] or ylim[0] >= ylim[1]:
163            vedo.logger.warning(f"Null range for Figure {self.title}... returning an empty Assembly.")
164            super().__init__()
165            self.yscale = 0
166            return
167
168        if aspect == "equal":
169            self.aspect = dx / dy  # so that yscale becomes 1
170
171        self.yscale = dx / dy / self.aspect
172
173        y0lim *= self.yscale
174        y1lim *= self.yscale
175
176        self.x0lim = x0lim
177        self.x1lim = x1lim
178        self.y0lim = y0lim
179        self.y1lim = y1lim
180
181        self.ztolerance = options.pop("ztolerance", None)
182        if self.ztolerance is None:
183            self.ztolerance = dx / 5000
184
185        ############## create axes
186        if self.axopts:
187            axes_opts = self.axopts
188            if self.axopts is True or self.axopts == 1:
189                axes_opts = {}
190
191            tp, ts = utils.make_ticks(y0lim / self.yscale, 
192                                      y1lim / self.yscale, number_of_divisions)
193            labs = []
194            for i in range(1, len(tp) - 1):
195                ynew = utils.lin_interpolate(tp[i], [0, 1], [y0lim, y1lim])
196                labs.append([ynew, ts[i]])
197
198            if self.title:
199                axes_opts["htitle"] = self.title
200            axes_opts["y_values_and_labels"] = labs
201            axes_opts["xrange"] = (x0lim, x1lim)
202            axes_opts["yrange"] = (y0lim, y1lim)
203            axes_opts["zrange"] = (0, 0)
204            axes_opts["y_use_bounds"] = True
205
206            if "c" not in axes_opts and "ac" in options:
207                axes_opts["c"] = options["ac"]
208
209            self.axes = addons.Axes(**axes_opts)
210
211        super().__init__([self.axes])
212        self.name = "Figure"
213
214        vedo.last_figure = self if settings.remember_last_figure_format else None
215
216
217    ##################################################################
218    def _repr_html_(self):
219        """
220        HTML representation of the Figure object for Jupyter Notebooks.
221
222        Returns:
223            HTML text with the image and some properties.
224        """
225        import io
226        import base64
227        from PIL import Image
228
229        library_name = "vedo.pyplot.Figure"
230        help_url = "https://vedo.embl.es/docs/vedo/pyplot.html#Figure"
231
232        arr = self.thumbnail(zoom=1.1)
233
234        im = Image.fromarray(arr)
235        buffered = io.BytesIO()
236        im.save(buffered, format="PNG", quality=100)
237        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
238        url = "data:image/png;base64," + encoded
239        image = f"<img src='{url}'></img>"
240
241        bounds = "<br/>".join(
242            [
243                vedo.utils.precision(min_x, 4) + " ... " + vedo.utils.precision(max_x, 4)
244                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
245            ]
246        )
247
248        help_text = ""
249        if self.name:
250            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
251        help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>"
252        if self.filename:
253            dots = ""
254            if len(self.filename) > 30:
255                dots = "..."
256            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"
257
258        all = [
259            "<table>",
260            "<tr>",
261            "<td>",
262            image,
263            "</td>",
264            "<td style='text-align: center; vertical-align: center;'><br/>",
265            help_text,
266            "<table>",
267            "<tr><td><b> nr. of parts </b></td><td>" + str(self.GetNumberOfPaths()) + "</td></tr>",
268            "<tr><td><b> position </b></td><td>" + str(self.GetPosition()) + "</td></tr>",
269            "<tr><td><b> x-limits </b></td><td>" + utils.precision(self.xlim, 4) + "</td></tr>",
270            "<tr><td><b> y-limits </b></td><td>" + utils.precision(self.ylim, 4) + "</td></tr>",
271            "<tr><td><b> world bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>",
272            "</table>",
273            "</table>",
274        ]
275        return "\n".join(all)
276
277    def __add__(self, *obj):
278        # just to avoid confusion, supersede Assembly.__add__
279        return self.__iadd__(*obj)
280
281    def __iadd__(self, *obj):
282        if len(obj) == 1 and isinstance(obj[0], Figure):
283            return self._check_unpack_and_insert(obj[0])
284
285        obj = utils.flatten(obj)
286        return self.insert(*obj)
287
288    def _check_unpack_and_insert(self, fig: "Figure") -> Self:
289
290        if fig.label:
291            self.labels.append(fig.label)
292
293        if abs(self.yscale - fig.yscale) > 0.0001:
294
295            colors.printc(":bomb:ERROR: adding incompatible Figure. Y-scales are different:", c='r', invert=True)
296            colors.printc("  first  figure:", self.yscale, c='r')
297            colors.printc("  second figure:", fig.yscale, c='r')
298
299            colors.printc("One or more of these parameters can be the cause:", c="r")
300            if list(self.xlim) != list(fig.xlim):
301                colors.printc("xlim --------------------------------------------\n",
302                              " first  figure:", self.xlim, "\n",
303                              " second figure:", fig.xlim, c='r')
304            if list(self.ylim) != list(fig.ylim):
305                colors.printc("ylim --------------------------------------------\n",
306                              " first  figure:", self.ylim, "\n",
307                              " second figure:", fig.ylim, c='r')
308            if list(self.padding) != list(fig.padding):
309                colors.printc("padding -----------------------------------------\n",
310                              " first  figure:", self.padding,
311                              " second figure:", fig.padding, c='r')
312            if self.aspect != fig.aspect:
313                colors.printc("aspect ------------------------------------------\n",
314                              " first  figure:", self.aspect, "\n",
315                              " second figure:", fig.aspect, c='r')
316
317            colors.printc("\n:idea: Consider using fig2 = histogram(..., like=fig1)", c="r")
318            colors.printc(" Or fig += histogram(..., like=fig)\n", c="r")
319            return self
320
321        offset = self.zbounds()[1] + self.ztolerance
322
323        for ele in fig.unpack():
324            if "Axes" in ele.name:
325                continue
326            ele.z(offset)
327            self.insert(ele, rescale=False)
328
329        return self
330
331    def insert(self, *objs, rescale=True, as3d=True, adjusted=False, cut=True) -> Self:
332        """
333        Insert objects into a Figure.
334
335        The recommended syntax is to use "+=", which calls `insert()` under the hood.
336        If a whole Figure is added with "+=", it is unpacked and its objects are added
337        one by one.
338
339        Arguments:
340            rescale : (bool)
341                rescale the y axis position while inserting the object.
342            as3d : (bool)
343                if True keep the aspect ratio of the 3d object, otherwise stretch it in y.
344            adjusted : (bool)
345                adjust the scaling according to the shortest axis
346            cut : (bool)
347                cut off the parts of the object which go beyond the axes frame.
348        """
349        for a in objs:
350
351            if a in self.objects:
352                # should not add twice the same object in plot
353                continue
354
355            if isinstance(a, vedo.Points):  # hacky way to identify Points
356                if a.ncells == a.npoints:
357                    poly = a.dataset
358                    if poly.GetNumberOfPolys() == 0 and poly.GetNumberOfLines() == 0:
359                        as3d = False
360                        rescale = True
361
362            if isinstance(a, (shapes.Arrow, shapes.Arrow2D)):
363                # discard input Arrow and substitute it with a brand new one
364                # (because scaling would fatally distort the shape)
365
366                py = a.base[1]
367                a.top[1] = (a.top[1] - py) * self.yscale + py
368                b = shapes.Arrow2D(a.base, a.top, s=a.s, fill=a.fill).z(a.z())
369
370                prop = a.properties
371                prop.LightingOff()
372                b.actor.SetProperty(prop)
373                b.properties = prop
374                b.y(py * self.yscale)
375                a = b
376
377            # elif isinstance(a, shapes.Rectangle) and a.radius is not None:
378            #     # discard input Rectangle and substitute it with a brand new one
379            #     # (because scaling would fatally distort the shape of the corners)
380            #     py = a.corner1[1]
381            #     rx1,ry1,rz1 = a.corner1
382            #     rx2,ry2,rz2 = a.corner2
383            #     ry2 = (ry2-py) * self.yscale + py
384            #     b = shapes.Rectangle([rx1,0,rz1], [rx2,ry2,rz2], radius=a.radius).z(a.z())
385            #     b.SetProperty(a.properties)
386            #     b.y(py / self.yscale)
387            #     a = b
388
389            else:
390
391                if rescale:
392
393                    if not isinstance(a, Figure):
394
395                        if as3d and not isinstance(a, self.force_scaling_types):
396                            if adjusted:
397                                scl = np.min([1, self.yscale])
398                            else:
399                                scl = self.yscale
400
401                            a.scale(scl)
402
403                        else:
404                            a.scale([1, self.yscale, 1])
405
406                    # shift it in y
407                    a.y(a.y() * self.yscale)
408
409            if cut:
410                try:
411                    bx0, bx1, by0, by1, _, _ = a.bounds()
412                    if self.y0lim > by0:
413                        a.cut_with_plane([0, self.y0lim, 0], [0, 1, 0])
414                    if self.y1lim < by1:
415                        a.cut_with_plane([0, self.y1lim, 0], [0, -1, 0])
416                    if self.x0lim > bx0:
417                        a.cut_with_plane([self.x0lim, 0, 0], [1, 0, 0])
418                    if self.x1lim < bx1:
419                        a.cut_with_plane([self.x1lim, 0, 0], [-1, 0, 0])
420                except:
421                    # print("insert(): cannot cut", [a])
422                    pass
423
424            self.AddPart(a.actor)
425            self.objects.append(a)
426
427        return self
428
429    def add_label(self, text: str, c=None, marker="", mc="black") -> Self:
430        """
431        Manually add en entry label to the legend.
432
433        Arguments:
434            text : (str)
435                text string for the label.
436            c : (str)
437                color of the text
438            marker : (str), Mesh
439                a marker char or a Mesh object to be used as marker
440            mc : (str)
441                color for the marker
442        """
443        newlabel = LabelData()
444        newlabel.text = text.replace("\n", " ")
445        newlabel.tcolor = c
446        newlabel.marker = marker
447        newlabel.mcolor = mc
448        self.labels.append(newlabel)
449        return self
450
451    def add_legend(
452        self,
453        pos="top-right",
454        relative=True,
455        font=None,
456        s=1,
457        c=None,
458        vspace=1.75,
459        padding=0.1,
460        radius=0,
461        alpha=1,
462        bc="k7",
463        lw=1,
464        lc="k4",
465        z=0,
466    ) -> Self:
467        """
468        Add existing labels to form a legend box.
469        Labels have been previously filled with eg: `plot(..., label="text")`
470
471        Arguments:
472            pos : (str, list)
473                A string or 2D coordinates. The default is "top-right".
474            relative : (bool)
475                control whether `pos` is absolute or relative, e.i. normalized
476                to the x and y ranges so that x and y in `pos=[x,y]` should be
477                both in the range [0,1].
478                This flag is ignored if a string despcriptor is passed.
479                Default is True.
480            font : (str, int)
481                font name or number.
482                Check [available fonts here](https://vedo.embl.es/fonts).
483            s : (float)
484                global size of the legend
485            c : (str)
486                color of the text
487            vspace : (float)
488                vertical spacing of lines
489            padding : (float)
490                padding of the box as a fraction of the text size
491            radius : (float)
492                border radius of the box
493            alpha : (float)
494                opacity of the box. Values below 1 may cause poor rendering
495                because of antialiasing.
496                Use alpha = 0 to remove the box.
497            bc : (str)
498                box color
499            lw : (int)
500                border line width of the box in pixel units
501            lc : (int)
502                border line color of the box
503            z : (float)
504                set the zorder as z position (useful to avoid overlap)
505        """
506        sx = self.x1lim - self.x0lim
507        s = s * sx / 55  # so that input can be about 1
508
509        ds = 0
510        texts = []
511        mks = []
512        for i, t in enumerate(self.labels):
513            label = self.labels[i]
514            t = label.text
515
516            if label.tcolor is not None:
517                c = label.tcolor
518
519            tx = vedo.shapes.Text3D(t, s=s, c=c, justify="center-left", font=font)
520            y0, y1 = tx.ybounds()
521            ds = max(y1 - y0, ds)
522            texts.append(tx)
523
524            mk = label.marker
525            if isinstance(mk, vedo.Points):
526                mk = mk.clone(deep=False).lighting("off")
527                cm = mk.center_of_mass()
528                ty0, ty1 = tx.ybounds()
529                oby0, oby1 = mk.ybounds()
530                mk.shift(-cm)
531                mk.SetOrigin(cm)
532                mk.scale((ty1 - ty0) / (oby1 - oby0))
533                mk.scale([1.1, 1.1, 0.01])
534            elif mk == "-":
535                mk = vedo.shapes.Marker(mk, s=s * 2)
536                mk.color(label.mcolor)
537            else:
538                mk = vedo.shapes.Marker(mk, s=s)
539                mk.color(label.mcolor)
540            mks.append(mk)
541
542        for i, tx in enumerate(texts):
543            tx.shift(0, -(i + 0) * ds * vspace)
544
545        for i, mk in enumerate(mks):
546            mk.shift(-ds * 1.75, -(i + 0) * ds * vspace, 0)
547
548        acts = texts + mks
549
550        aleg = Assembly(acts)  # .show(axes=1).close()
551        x0, x1, y0, y1, _, _ = aleg.GetBounds()
552
553        if alpha:
554            dx = x1 - x0
555            dy = y1 - y0
556
557            if not utils.is_sequence(padding):
558                padding = [padding] * 4
559            padding = min(padding)
560            padding = min(padding * dx, padding * dy)
561            if len(self.labels) == 1:
562                padding *= 4
563            x0 -= padding
564            x1 += padding
565            y0 -= padding
566            y1 += padding
567
568            box = shapes.Rectangle([x0, y0], [x1, y1], radius=radius, c=bc, alpha=alpha)
569            box.shift(0, 0, -dy / 100).pickable(False)
570            if lc:
571                box.lc(lc).lw(lw)
572            aleg.AddPart(box.actor)
573            aleg.objects.append(box)
574
575        xlim = self.xlim
576        ylim = self.ylim
577        if isinstance(pos, str):
578            px, py = 0.0, 0.0
579            rx, ry = (xlim[1] + xlim[0]) / 2, (ylim[1] + ylim[0]) / 2
580            shx, shy = 0.0, 0.0
581            if "top" in pos:
582                if "cent" in pos:
583                    px, py = rx, ylim[1]
584                    shx, shy = (x0 + x1) / 2, y1
585                elif "left" in pos:
586                    px, py = xlim[0], ylim[1]
587                    shx, shy = x0, y1
588                else:  # "right"
589                    px, py = xlim[1], ylim[1]
590                    shx, shy = x1, y1
591            elif "bot" in pos:
592                if "left" in pos:
593                    px, py = xlim[0], ylim[0]
594                    shx, shy = x0, y0
595                elif "right" in pos:
596                    px, py = xlim[1], ylim[0]
597                    shx, shy = x1, y0
598                else:  # "cent"
599                    px, py = rx, ylim[0]
600                    shx, shy = (x0 + x1) / 2, y0
601            elif "cent" in pos:
602                if "left" in pos:
603                    px, py = xlim[0], ry
604                    shx, shy = x0, (y0 + y1) / 2
605                elif "right" in pos:
606                    px, py = xlim[1], ry
607                    shx, shy = x1, (y0 + y1) / 2
608            else:
609                vedo.logger.error(f"in add_legend(), cannot understand {pos}")
610                raise RuntimeError
611
612        else:
613
614            if relative:
615                rx, ry = pos[0], pos[1]
616                px = (xlim[1] - xlim[0]) * rx + xlim[0]
617                py = (ylim[1] - ylim[0]) * ry + ylim[0]
618                z *= xlim[1] - xlim[0]
619            else:
620                px, py = pos[0], pos[1]
621            shx, shy = x0, y1
622
623        zpos = aleg.pos()[2]
624        aleg.pos(px - shx, py * self.yscale - shy, zpos + sx / 50 + z)
625
626        self.insert(aleg, rescale=False, cut=False)
627        self.legend = aleg
628        aleg.name = "Legend"
629        return self

Format class for figures.

Figure( xlim, ylim, aspect=1.3333333333333333, padding=(0.05, 0.05, 0.05, 0.05), **kwargs)
 63    def __init__(self, xlim, ylim, aspect=4 / 3, padding=(0.05, 0.05, 0.05, 0.05), **kwargs):
 64        """
 65        Create an empty formatted figure for plotting.
 66
 67        Arguments:
 68            xlim : (list)
 69                range of the x-axis as [x0, x1]
 70            ylim : (list)
 71                range of the y-axis as [y0, y1]
 72            aspect : (float, str)
 73                the desired aspect ratio of the histogram. Default is 4/3.
 74                Use `aspect="equal"` to force the same units in x and y.
 75            padding : (float, list)
 76                keep a padding space from the axes (as a fraction of the axis size).
 77                This can be a list of four numbers.
 78            xtitle : (str)
 79                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
 80            ytitle : (str)
 81                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
 82            grid : (bool)
 83                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
 84            axes : (dict)
 85                an extra dictionary of options for the `vedo.addons.Axes` object
 86        """
 87
 88        self.verbose = True  # printing to stdout on every mouse click
 89
 90        self.xlim = np.asarray(xlim)
 91        self.ylim = np.asarray(ylim)
 92        self.aspect = aspect
 93        self.padding = padding
 94        if not utils.is_sequence(self.padding):
 95            self.padding = [self.padding, self.padding, self.padding, self.padding]
 96
 97        self.force_scaling_types = (
 98            shapes.Glyph,
 99            shapes.Line,
100            shapes.Rectangle,
101            shapes.DashedLine,
102            shapes.Tube,
103            shapes.Ribbon,
104            shapes.GeoCircle,
105            shapes.Arc,
106            shapes.Grid,
107            # shapes.Arrows, # todo
108            # shapes.Arrows2D, # todo
109            shapes.Brace,  # todo
110        )
111
112        options = dict(kwargs)
113
114        self.title  = options.pop("title", "")
115        self.xtitle = options.pop("xtitle", " ")
116        self.ytitle = options.pop("ytitle", " ")
117        number_of_divisions = 6
118
119        self.legend = None
120        self.labels = []
121        self.label = options.pop("label", None)
122        if self.label:
123            self.labels = [self.label]
124
125        self.axopts = options.pop("axes", {})
126        if isinstance(self.axopts, (bool, int, float)):
127            if self.axopts:
128                self.axopts = {}
129        if self.axopts or isinstance(self.axopts, dict):
130            number_of_divisions = self.axopts.pop("number_of_divisions", number_of_divisions)
131
132            self.axopts["xtitle"] = self.xtitle
133            self.axopts["ytitle"] = self.ytitle
134
135            if "xygrid" not in self.axopts:  ## modify the default
136                self.axopts["xygrid"] = options.pop("grid", False)
137
138            if "xygrid_transparent" not in self.axopts:  ## modify the default
139                self.axopts["xygrid_transparent"] = True
140
141            if "xtitle_position" not in self.axopts:  ## modify the default
142                self.axopts["xtitle_position"] = 0.5
143                self.axopts["xtitle_justify"] = "top-center"
144
145            if "ytitle_position" not in self.axopts:  ## modify the default
146                self.axopts["ytitle_position"] = 0.5
147                self.axopts["ytitle_justify"] = "bottom-center"
148
149            if self.label:
150                if "c" in self.axopts:
151                    self.label.tcolor = self.axopts["c"]
152
153        x0, x1 = self.xlim
154        y0, y1 = self.ylim
155        dx = x1 - x0
156        dy = y1 - y0
157        x0lim, x1lim = (x0 - self.padding[0] * dx, x1 + self.padding[1] * dx)
158        y0lim, y1lim = (y0 - self.padding[2] * dy, y1 + self.padding[3] * dy)
159        dy = y1lim - y0lim
160
161        self.axes = None
162        if xlim[0] >= xlim[1] or ylim[0] >= ylim[1]:
163            vedo.logger.warning(f"Null range for Figure {self.title}... returning an empty Assembly.")
164            super().__init__()
165            self.yscale = 0
166            return
167
168        if aspect == "equal":
169            self.aspect = dx / dy  # so that yscale becomes 1
170
171        self.yscale = dx / dy / self.aspect
172
173        y0lim *= self.yscale
174        y1lim *= self.yscale
175
176        self.x0lim = x0lim
177        self.x1lim = x1lim
178        self.y0lim = y0lim
179        self.y1lim = y1lim
180
181        self.ztolerance = options.pop("ztolerance", None)
182        if self.ztolerance is None:
183            self.ztolerance = dx / 5000
184
185        ############## create axes
186        if self.axopts:
187            axes_opts = self.axopts
188            if self.axopts is True or self.axopts == 1:
189                axes_opts = {}
190
191            tp, ts = utils.make_ticks(y0lim / self.yscale, 
192                                      y1lim / self.yscale, number_of_divisions)
193            labs = []
194            for i in range(1, len(tp) - 1):
195                ynew = utils.lin_interpolate(tp[i], [0, 1], [y0lim, y1lim])
196                labs.append([ynew, ts[i]])
197
198            if self.title:
199                axes_opts["htitle"] = self.title
200            axes_opts["y_values_and_labels"] = labs
201            axes_opts["xrange"] = (x0lim, x1lim)
202            axes_opts["yrange"] = (y0lim, y1lim)
203            axes_opts["zrange"] = (0, 0)
204            axes_opts["y_use_bounds"] = True
205
206            if "c" not in axes_opts and "ac" in options:
207                axes_opts["c"] = options["ac"]
208
209            self.axes = addons.Axes(**axes_opts)
210
211        super().__init__([self.axes])
212        self.name = "Figure"
213
214        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:
331    def insert(self, *objs, rescale=True, as3d=True, adjusted=False, cut=True) -> Self:
332        """
333        Insert objects into a Figure.
334
335        The recommended syntax is to use "+=", which calls `insert()` under the hood.
336        If a whole Figure is added with "+=", it is unpacked and its objects are added
337        one by one.
338
339        Arguments:
340            rescale : (bool)
341                rescale the y axis position while inserting the object.
342            as3d : (bool)
343                if True keep the aspect ratio of the 3d object, otherwise stretch it in y.
344            adjusted : (bool)
345                adjust the scaling according to the shortest axis
346            cut : (bool)
347                cut off the parts of the object which go beyond the axes frame.
348        """
349        for a in objs:
350
351            if a in self.objects:
352                # should not add twice the same object in plot
353                continue
354
355            if isinstance(a, vedo.Points):  # hacky way to identify Points
356                if a.ncells == a.npoints:
357                    poly = a.dataset
358                    if poly.GetNumberOfPolys() == 0 and poly.GetNumberOfLines() == 0:
359                        as3d = False
360                        rescale = True
361
362            if isinstance(a, (shapes.Arrow, shapes.Arrow2D)):
363                # discard input Arrow and substitute it with a brand new one
364                # (because scaling would fatally distort the shape)
365
366                py = a.base[1]
367                a.top[1] = (a.top[1] - py) * self.yscale + py
368                b = shapes.Arrow2D(a.base, a.top, s=a.s, fill=a.fill).z(a.z())
369
370                prop = a.properties
371                prop.LightingOff()
372                b.actor.SetProperty(prop)
373                b.properties = prop
374                b.y(py * self.yscale)
375                a = b
376
377            # elif isinstance(a, shapes.Rectangle) and a.radius is not None:
378            #     # discard input Rectangle and substitute it with a brand new one
379            #     # (because scaling would fatally distort the shape of the corners)
380            #     py = a.corner1[1]
381            #     rx1,ry1,rz1 = a.corner1
382            #     rx2,ry2,rz2 = a.corner2
383            #     ry2 = (ry2-py) * self.yscale + py
384            #     b = shapes.Rectangle([rx1,0,rz1], [rx2,ry2,rz2], radius=a.radius).z(a.z())
385            #     b.SetProperty(a.properties)
386            #     b.y(py / self.yscale)
387            #     a = b
388
389            else:
390
391                if rescale:
392
393                    if not isinstance(a, Figure):
394
395                        if as3d and not isinstance(a, self.force_scaling_types):
396                            if adjusted:
397                                scl = np.min([1, self.yscale])
398                            else:
399                                scl = self.yscale
400
401                            a.scale(scl)
402
403                        else:
404                            a.scale([1, self.yscale, 1])
405
406                    # shift it in y
407                    a.y(a.y() * self.yscale)
408
409            if cut:
410                try:
411                    bx0, bx1, by0, by1, _, _ = a.bounds()
412                    if self.y0lim > by0:
413                        a.cut_with_plane([0, self.y0lim, 0], [0, 1, 0])
414                    if self.y1lim < by1:
415                        a.cut_with_plane([0, self.y1lim, 0], [0, -1, 0])
416                    if self.x0lim > bx0:
417                        a.cut_with_plane([self.x0lim, 0, 0], [1, 0, 0])
418                    if self.x1lim < bx1:
419                        a.cut_with_plane([self.x1lim, 0, 0], [-1, 0, 0])
420                except:
421                    # print("insert(): cannot cut", [a])
422                    pass
423
424            self.AddPart(a.actor)
425            self.objects.append(a)
426
427        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:
429    def add_label(self, text: str, c=None, marker="", mc="black") -> Self:
430        """
431        Manually add en entry label to the legend.
432
433        Arguments:
434            text : (str)
435                text string for the label.
436            c : (str)
437                color of the text
438            marker : (str), Mesh
439                a marker char or a Mesh object to be used as marker
440            mc : (str)
441                color for the marker
442        """
443        newlabel = LabelData()
444        newlabel.text = text.replace("\n", " ")
445        newlabel.tcolor = c
446        newlabel.marker = marker
447        newlabel.mcolor = mc
448        self.labels.append(newlabel)
449        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:
451    def add_legend(
452        self,
453        pos="top-right",
454        relative=True,
455        font=None,
456        s=1,
457        c=None,
458        vspace=1.75,
459        padding=0.1,
460        radius=0,
461        alpha=1,
462        bc="k7",
463        lw=1,
464        lc="k4",
465        z=0,
466    ) -> Self:
467        """
468        Add existing labels to form a legend box.
469        Labels have been previously filled with eg: `plot(..., label="text")`
470
471        Arguments:
472            pos : (str, list)
473                A string or 2D coordinates. The default is "top-right".
474            relative : (bool)
475                control whether `pos` is absolute or relative, e.i. normalized
476                to the x and y ranges so that x and y in `pos=[x,y]` should be
477                both in the range [0,1].
478                This flag is ignored if a string despcriptor is passed.
479                Default is True.
480            font : (str, int)
481                font name or number.
482                Check [available fonts here](https://vedo.embl.es/fonts).
483            s : (float)
484                global size of the legend
485            c : (str)
486                color of the text
487            vspace : (float)
488                vertical spacing of lines
489            padding : (float)
490                padding of the box as a fraction of the text size
491            radius : (float)
492                border radius of the box
493            alpha : (float)
494                opacity of the box. Values below 1 may cause poor rendering
495                because of antialiasing.
496                Use alpha = 0 to remove the box.
497            bc : (str)
498                box color
499            lw : (int)
500                border line width of the box in pixel units
501            lc : (int)
502                border line color of the box
503            z : (float)
504                set the zorder as z position (useful to avoid overlap)
505        """
506        sx = self.x1lim - self.x0lim
507        s = s * sx / 55  # so that input can be about 1
508
509        ds = 0
510        texts = []
511        mks = []
512        for i, t in enumerate(self.labels):
513            label = self.labels[i]
514            t = label.text
515
516            if label.tcolor is not None:
517                c = label.tcolor
518
519            tx = vedo.shapes.Text3D(t, s=s, c=c, justify="center-left", font=font)
520            y0, y1 = tx.ybounds()
521            ds = max(y1 - y0, ds)
522            texts.append(tx)
523
524            mk = label.marker
525            if isinstance(mk, vedo.Points):
526                mk = mk.clone(deep=False).lighting("off")
527                cm = mk.center_of_mass()
528                ty0, ty1 = tx.ybounds()
529                oby0, oby1 = mk.ybounds()
530                mk.shift(-cm)
531                mk.SetOrigin(cm)
532                mk.scale((ty1 - ty0) / (oby1 - oby0))
533                mk.scale([1.1, 1.1, 0.01])
534            elif mk == "-":
535                mk = vedo.shapes.Marker(mk, s=s * 2)
536                mk.color(label.mcolor)
537            else:
538                mk = vedo.shapes.Marker(mk, s=s)
539                mk.color(label.mcolor)
540            mks.append(mk)
541
542        for i, tx in enumerate(texts):
543            tx.shift(0, -(i + 0) * ds * vspace)
544
545        for i, mk in enumerate(mks):
546            mk.shift(-ds * 1.75, -(i + 0) * ds * vspace, 0)
547
548        acts = texts + mks
549
550        aleg = Assembly(acts)  # .show(axes=1).close()
551        x0, x1, y0, y1, _, _ = aleg.GetBounds()
552
553        if alpha:
554            dx = x1 - x0
555            dy = y1 - y0
556
557            if not utils.is_sequence(padding):
558                padding = [padding] * 4
559            padding = min(padding)
560            padding = min(padding * dx, padding * dy)
561            if len(self.labels) == 1:
562                padding *= 4
563            x0 -= padding
564            x1 += padding
565            y0 -= padding
566            y1 += padding
567
568            box = shapes.Rectangle([x0, y0], [x1, y1], radius=radius, c=bc, alpha=alpha)
569            box.shift(0, 0, -dy / 100).pickable(False)
570            if lc:
571                box.lc(lc).lw(lw)
572            aleg.AddPart(box.actor)
573            aleg.objects.append(box)
574
575        xlim = self.xlim
576        ylim = self.ylim
577        if isinstance(pos, str):
578            px, py = 0.0, 0.0
579            rx, ry = (xlim[1] + xlim[0]) / 2, (ylim[1] + ylim[0]) / 2
580            shx, shy = 0.0, 0.0
581            if "top" in pos:
582                if "cent" in pos:
583                    px, py = rx, ylim[1]
584                    shx, shy = (x0 + x1) / 2, y1
585                elif "left" in pos:
586                    px, py = xlim[0], ylim[1]
587                    shx, shy = x0, y1
588                else:  # "right"
589                    px, py = xlim[1], ylim[1]
590                    shx, shy = x1, y1
591            elif "bot" in pos:
592                if "left" in pos:
593                    px, py = xlim[0], ylim[0]
594                    shx, shy = x0, y0
595                elif "right" in pos:
596                    px, py = xlim[1], ylim[0]
597                    shx, shy = x1, y0
598                else:  # "cent"
599                    px, py = rx, ylim[0]
600                    shx, shy = (x0 + x1) / 2, y0
601            elif "cent" in pos:
602                if "left" in pos:
603                    px, py = xlim[0], ry
604                    shx, shy = x0, (y0 + y1) / 2
605                elif "right" in pos:
606                    px, py = xlim[1], ry
607                    shx, shy = x1, (y0 + y1) / 2
608            else:
609                vedo.logger.error(f"in add_legend(), cannot understand {pos}")
610                raise RuntimeError
611
612        else:
613
614            if relative:
615                rx, ry = pos[0], pos[1]
616                px = (xlim[1] - xlim[0]) * rx + xlim[0]
617                py = (ylim[1] - ylim[0]) * ry + ylim[0]
618                z *= xlim[1] - xlim[0]
619            else:
620                px, py = pos[0], pos[1]
621            shx, shy = x0, y1
622
623        zpos = aleg.pos()[2]
624        aleg.pos(px - shx, py * self.yscale - shy, zpos + sx / 50 + z)
625
626        self.insert(aleg, rescale=False, cut=False)
627        self.legend = aleg
628        aleg.name = "Legend"
629        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):
 633class Histogram1D(Figure):
 634    "1D histogramming."
 635
 636    def __init__(
 637        self,
 638        data,
 639        weights=None,
 640        bins=None,
 641        errors=False,
 642        density=False,
 643        logscale=False,
 644        max_entries=None,
 645        fill=True,
 646        radius=0.075,
 647        c="olivedrab",
 648        gap=0.0,
 649        alpha=1,
 650        outline=False,
 651        lw=2,
 652        lc="k",
 653        texture="",
 654        marker="",
 655        ms=None,
 656        mc=None,
 657        ma=None,
 658        # Figure and axes options:
 659        like=None,
 660        xlim=None,
 661        ylim=(0, None),
 662        aspect=4 / 3,
 663        padding=(0.0, 0.0, 0.0, 0.05),
 664        title="",
 665        xtitle=" ",
 666        ytitle=" ",
 667        ac="k",
 668        grid=False,
 669        ztolerance=None,
 670        label="",
 671        **fig_kwargs,
 672    ):
 673        """
 674        Creates a `Histogram1D(Figure)` object.
 675
 676        Arguments:
 677            weights : (list)
 678                An array of weights, of the same shape as `data`. Each value in `data`
 679                only contributes its associated weight towards the bin count (instead of 1).
 680            bins : (int)
 681                number of bins
 682            density : (bool)
 683                normalize the area to 1 by dividing by the nr of entries and bin size
 684            logscale : (bool)
 685                use logscale on y-axis
 686            max_entries : (int)
 687                if `data` is larger than `max_entries`, a random sample of `max_entries` is used
 688            fill : (bool)
 689                fill bars with solid color `c`
 690            gap : (float)
 691                leave a small space btw bars
 692            radius : (float)
 693                border radius of the top of the histogram bar. Default value is 0.1.
 694            texture : (str)
 695                url or path to an image to be used as texture for the bin
 696            outline : (bool)
 697                show outline of the bins
 698            errors : (bool)
 699                show error bars
 700            xtitle : (str)
 701                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
 702            ytitle : (str)
 703                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
 704            padding : (float), list
 705                keep a padding space from the axes (as a fraction of the axis size).
 706                This can be a list of four numbers.
 707            aspect : (float)
 708                the desired aspect ratio of the histogram. Default is 4/3.
 709            grid : (bool)
 710                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
 711            ztolerance : (float)
 712                a tolerance factor to superimpose objects (along the z-axis).
 713
 714        Examples:
 715            - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
 716            - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
 717            - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
 718            - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)
 719
 720            ![](https://vedo.embl.es/images/pyplot/histo_1D.png)
 721        """
 722
 723        if max_entries and data.shape[0] > max_entries:
 724            data = np.random.choice(data, int(max_entries))
 725
 726        # purge NaN from data
 727        valid_ids = np.all(np.logical_not(np.isnan(data)))
 728        data = np.asarray(data[valid_ids]).ravel()
 729
 730        # if data.dtype is integer try to center bins by default
 731        if like is None and bins is None and np.issubdtype(data.dtype, np.integer):
 732            if xlim is None and ylim == (0, None):
 733                x1, x0 = data.max(), data.min()
 734                if 0 < x1 - x0 <= 100:
 735                    bins = x1 - x0 + 1
 736                    xlim = (x0 - 0.5, x1 + 0.5)
 737
 738        if like is None and vedo.last_figure is not None:
 739            if xlim is None and ylim == (0, None):
 740                like = vedo.last_figure
 741
 742        if like is not None:
 743            xlim = like.xlim
 744            ylim = like.ylim
 745            aspect = like.aspect
 746            padding = like.padding
 747            if bins is None:
 748                bins = like.bins
 749        if bins is None:
 750            bins = 20
 751
 752        if utils.is_sequence(xlim):
 753            # deal with user passing eg [x0, None]
 754            _x0, _x1 = xlim
 755            if _x0 is None:
 756                _x0 = data.min()
 757            if _x1 is None:
 758                _x1 = data.max()
 759            xlim = [_x0, _x1]
 760
 761        fs, edges = np.histogram(data, bins=bins, weights=weights, range=xlim)
 762        binsize = edges[1] - edges[0]
 763        ntot = data.shape[0]
 764
 765        fig_kwargs["title"] = title
 766        fig_kwargs["xtitle"] = xtitle
 767        fig_kwargs["ytitle"] = ytitle
 768        fig_kwargs["ac"] = ac
 769        fig_kwargs["ztolerance"] = ztolerance
 770        fig_kwargs["grid"] = grid
 771
 772        unscaled_errors = np.sqrt(fs)
 773        if density:
 774            scaled_errors = unscaled_errors / (ntot * binsize)
 775            fs = fs / (ntot * binsize)
 776            if ytitle == " ":
 777                ytitle = f"counts / ({ntot} x {utils.precision(binsize,3)})"
 778                fig_kwargs["ytitle"] = ytitle
 779        elif logscale:
 780            se_up = np.log10(fs + unscaled_errors / 2 + 1)
 781            se_dw = np.log10(fs - unscaled_errors / 2 + 1)
 782            scaled_errors = np.c_[se_up, se_dw]
 783            fs = np.log10(fs + 1)
 784            if ytitle == " ":
 785                ytitle = "log_10 (counts+1)"
 786                fig_kwargs["ytitle"] = ytitle
 787
 788        x0, x1 = np.min(edges), np.max(edges)
 789        y0, y1 = ylim[0], np.max(fs)
 790
 791        _errors = []
 792        if errors:
 793            if density:
 794                y1 += max(scaled_errors) / 2
 795                _errors = scaled_errors
 796            elif logscale:
 797                y1 = max(scaled_errors[:, 0])
 798                _errors = scaled_errors
 799            else:
 800                y1 += max(unscaled_errors) / 2
 801                _errors = unscaled_errors
 802
 803        if like is None:
 804            ylim = list(ylim)
 805            if xlim is None:
 806                xlim = [x0, x1]
 807            if ylim[1] is None:
 808                ylim[1] = y1
 809            if ylim[0] != 0:
 810                ylim[0] = y0
 811
 812        self.title = title
 813        self.xtitle = xtitle
 814        self.ytitle = ytitle
 815        self.entries = ntot
 816        self.frequencies = fs
 817        self.errors = _errors
 818        self.edges = edges
 819        self.centers = (edges[0:-1] + edges[1:]) / 2
 820        self.mean = data.mean()
 821        self.mode = self.centers[np.argmax(fs)]
 822        self.std = data.std()
 823        self.bins = edges  # internally used by "like"
 824
 825        ############################### stats legend as htitle
 826        addstats = False
 827        if not title:
 828            if "axes" not in fig_kwargs:
 829                addstats = True
 830                axes_opts = {}
 831                fig_kwargs["axes"] = axes_opts
 832            elif fig_kwargs["axes"] is False:
 833                pass
 834            else:
 835                axes_opts = fig_kwargs["axes"]
 836                if "htitle" not in axes_opts:
 837                    addstats = True
 838
 839        if addstats:
 840            htitle = f"Entries:~~{int(self.entries)}  "
 841            htitle += f"Mean:~~{utils.precision(self.mean, 4)}  "
 842            htitle += f"STD:~~{utils.precision(self.std, 4)}  "
 843
 844            axes_opts["htitle"] = htitle
 845            axes_opts["htitle_justify"] = "bottom-left"
 846            axes_opts["htitle_size"] = 0.016
 847            # axes_opts["htitle_offset"] = [-0.49, 0.01, 0]
 848
 849        if mc is None:
 850            mc = lc
 851        if ma is None:
 852            ma = alpha
 853
 854        if label:
 855            nlab = LabelData()
 856            nlab.text = label
 857            nlab.tcolor = ac
 858            nlab.marker = marker
 859            nlab.mcolor = mc
 860            if not marker:
 861                nlab.marker = "s"
 862                nlab.mcolor = c
 863            fig_kwargs["label"] = nlab
 864
 865        ############################################### Figure init
 866        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
 867
 868        if not self.yscale:
 869            return
 870
 871        if utils.is_sequence(bins):
 872            myedges = np.array(bins)
 873            bins = len(bins) - 1
 874        else:
 875            myedges = edges
 876
 877        bin_centers = []
 878        for i in range(bins):
 879            x = (myedges[i] + myedges[i + 1]) / 2
 880            bin_centers.append([x, fs[i], 0])
 881
 882        rs = []
 883        maxheigth = 0
 884        if not fill and not outline and not errors and not marker:
 885            outline = True  # otherwise it's empty..
 886
 887        if fill:  #####################
 888            if outline:
 889                gap = 0
 890
 891            for i in range(bins):
 892                F = fs[i]
 893                if not F:
 894                    continue
 895                p0 = (myedges[i] + gap * binsize, 0, 0)
 896                p1 = (myedges[i + 1] - gap * binsize, F, 0)
 897
 898                if radius:
 899                    if gap:
 900                        rds = np.array([0, 0, radius, radius])
 901                    else:
 902                        rd1 = 0 if i < bins - 1 and fs[i + 1] >= F else radius / 2
 903                        rd2 = 0 if i > 0 and fs[i - 1] >= F else radius / 2
 904                        rds = np.array([0, 0, rd1, rd2])
 905                    p1_yscaled = [p1[0], p1[1] * self.yscale, 0]
 906                    r = shapes.Rectangle(p0, p1_yscaled, radius=rds * binsize, res=6)
 907                    r.scale([1, 1 / self.yscale, 1])
 908                    r.radius = None  # so it doesnt get recreated and rescaled by insert()
 909                else:
 910                    r = shapes.Rectangle(p0, p1)
 911
 912                if texture:
 913                    r.texture(texture)
 914                    c = "w"
 915
 916                r.actor.PickableOff()
 917                maxheigth = max(maxheigth, p1[1])
 918                if c in colors.cmaps_names:
 919                    col = colors.color_map((p0[0] + p1[0]) / 2, c, myedges[0], myedges[-1])
 920                else:
 921                    col = c
 922                r.color(col).alpha(alpha).lighting("off")
 923                r.z(self.ztolerance)
 924                rs.append(r)
 925
 926        if outline:  #####################
 927            lns = [[myedges[0], 0, 0]]
 928            for i in range(bins):
 929                lns.append([myedges[i], fs[i], 0])
 930                lns.append([myedges[i + 1], fs[i], 0])
 931                maxheigth = max(maxheigth, fs[i])
 932            lns.append([myedges[-1], 0, 0])
 933            outl = shapes.Line(lns, c=lc, alpha=alpha, lw=lw)
 934            outl.z(self.ztolerance * 2)
 935            rs.append(outl)
 936
 937        if errors:  #####################
 938            for i in range(bins):
 939                x = self.centers[i]
 940                f = fs[i]
 941                if not f:
 942                    continue
 943                err = _errors[i]
 944                if utils.is_sequence(err):
 945                    el = shapes.Line([x, err[0], 0], [x, err[1], 0], c=lc, alpha=alpha, lw=lw)
 946                else:
 947                    el = shapes.Line(
 948                        [x, f - err / 2, 0], [x, f + err / 2, 0], c=lc, alpha=alpha, lw=lw
 949                    )
 950                el.z(self.ztolerance * 3)
 951                rs.append(el)
 952
 953        if marker:  #####################
 954
 955            # remove empty bins (we dont want a marker there)
 956            bin_centers = np.array(bin_centers)
 957            bin_centers = bin_centers[bin_centers[:, 1] > 0]
 958
 959            if utils.is_sequence(ms):  ### variable point size
 960                mk = shapes.Marker(marker, s=1)
 961                mk.scale([1, 1 / self.yscale, 1])
 962                msv = np.zeros_like(bin_centers)
 963                msv[:, 0] = ms
 964                marked = shapes.Glyph(
 965                    bin_centers, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
 966                )
 967            else:  ### fixed point size
 968
 969                if ms is None:
 970                    ms = (xlim[1] - xlim[0]) / 100.0
 971                else:
 972                    ms = (xlim[1] - xlim[0]) / 100.0 * ms
 973
 974                if utils.is_sequence(mc):
 975                    mk = shapes.Marker(marker, s=ms)
 976                    mk.scale([1, 1 / self.yscale, 1])
 977                    msv = np.zeros_like(bin_centers)
 978                    msv[:, 0] = 1
 979                    marked = shapes.Glyph(
 980                        bin_centers, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
 981                    )
 982                else:
 983                    mk = shapes.Marker(marker, s=ms)
 984                    mk.scale([1, 1 / self.yscale, 1])
 985                    marked = shapes.Glyph(bin_centers, mk, c=mc)
 986
 987            marked.alpha(ma)
 988            marked.z(self.ztolerance * 4)
 989            rs.append(marked)
 990
 991        self.insert(*rs, as3d=False)
 992        self.name = "Histogram1D"
 993
 994    def print(self, **kwargs) -> None:
 995        """Print infos about this histogram"""
 996        txt = (
 997            f"{self.name}  {self.title}\n"
 998            f"    xtitle  = '{self.xtitle}'\n"
 999            f"    ytitle  = '{self.ytitle}'\n"
1000            f"    entries = {self.entries}\n"
1001            f"    mean    = {self.mean}\n"
1002            f"    std     = {self.std}"
1003        )
1004        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)
636    def __init__(
637        self,
638        data,
639        weights=None,
640        bins=None,
641        errors=False,
642        density=False,
643        logscale=False,
644        max_entries=None,
645        fill=True,
646        radius=0.075,
647        c="olivedrab",
648        gap=0.0,
649        alpha=1,
650        outline=False,
651        lw=2,
652        lc="k",
653        texture="",
654        marker="",
655        ms=None,
656        mc=None,
657        ma=None,
658        # Figure and axes options:
659        like=None,
660        xlim=None,
661        ylim=(0, None),
662        aspect=4 / 3,
663        padding=(0.0, 0.0, 0.0, 0.05),
664        title="",
665        xtitle=" ",
666        ytitle=" ",
667        ac="k",
668        grid=False,
669        ztolerance=None,
670        label="",
671        **fig_kwargs,
672    ):
673        """
674        Creates a `Histogram1D(Figure)` object.
675
676        Arguments:
677            weights : (list)
678                An array of weights, of the same shape as `data`. Each value in `data`
679                only contributes its associated weight towards the bin count (instead of 1).
680            bins : (int)
681                number of bins
682            density : (bool)
683                normalize the area to 1 by dividing by the nr of entries and bin size
684            logscale : (bool)
685                use logscale on y-axis
686            max_entries : (int)
687                if `data` is larger than `max_entries`, a random sample of `max_entries` is used
688            fill : (bool)
689                fill bars with solid color `c`
690            gap : (float)
691                leave a small space btw bars
692            radius : (float)
693                border radius of the top of the histogram bar. Default value is 0.1.
694            texture : (str)
695                url or path to an image to be used as texture for the bin
696            outline : (bool)
697                show outline of the bins
698            errors : (bool)
699                show error bars
700            xtitle : (str)
701                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
702            ytitle : (str)
703                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
704            padding : (float), list
705                keep a padding space from the axes (as a fraction of the axis size).
706                This can be a list of four numbers.
707            aspect : (float)
708                the desired aspect ratio of the histogram. Default is 4/3.
709            grid : (bool)
710                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
711            ztolerance : (float)
712                a tolerance factor to superimpose objects (along the z-axis).
713
714        Examples:
715            - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
716            - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
717            - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
718            - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)
719
720            ![](https://vedo.embl.es/images/pyplot/histo_1D.png)
721        """
722
723        if max_entries and data.shape[0] > max_entries:
724            data = np.random.choice(data, int(max_entries))
725
726        # purge NaN from data
727        valid_ids = np.all(np.logical_not(np.isnan(data)))
728        data = np.asarray(data[valid_ids]).ravel()
729
730        # if data.dtype is integer try to center bins by default
731        if like is None and bins is None and np.issubdtype(data.dtype, np.integer):
732            if xlim is None and ylim == (0, None):
733                x1, x0 = data.max(), data.min()
734                if 0 < x1 - x0 <= 100:
735                    bins = x1 - x0 + 1
736                    xlim = (x0 - 0.5, x1 + 0.5)
737
738        if like is None and vedo.last_figure is not None:
739            if xlim is None and ylim == (0, None):
740                like = vedo.last_figure
741
742        if like is not None:
743            xlim = like.xlim
744            ylim = like.ylim
745            aspect = like.aspect
746            padding = like.padding
747            if bins is None:
748                bins = like.bins
749        if bins is None:
750            bins = 20
751
752        if utils.is_sequence(xlim):
753            # deal with user passing eg [x0, None]
754            _x0, _x1 = xlim
755            if _x0 is None:
756                _x0 = data.min()
757            if _x1 is None:
758                _x1 = data.max()
759            xlim = [_x0, _x1]
760
761        fs, edges = np.histogram(data, bins=bins, weights=weights, range=xlim)
762        binsize = edges[1] - edges[0]
763        ntot = data.shape[0]
764
765        fig_kwargs["title"] = title
766        fig_kwargs["xtitle"] = xtitle
767        fig_kwargs["ytitle"] = ytitle
768        fig_kwargs["ac"] = ac
769        fig_kwargs["ztolerance"] = ztolerance
770        fig_kwargs["grid"] = grid
771
772        unscaled_errors = np.sqrt(fs)
773        if density:
774            scaled_errors = unscaled_errors / (ntot * binsize)
775            fs = fs / (ntot * binsize)
776            if ytitle == " ":
777                ytitle = f"counts / ({ntot} x {utils.precision(binsize,3)})"
778                fig_kwargs["ytitle"] = ytitle
779        elif logscale:
780            se_up = np.log10(fs + unscaled_errors / 2 + 1)
781            se_dw = np.log10(fs - unscaled_errors / 2 + 1)
782            scaled_errors = np.c_[se_up, se_dw]
783            fs = np.log10(fs + 1)
784            if ytitle == " ":
785                ytitle = "log_10 (counts+1)"
786                fig_kwargs["ytitle"] = ytitle
787
788        x0, x1 = np.min(edges), np.max(edges)
789        y0, y1 = ylim[0], np.max(fs)
790
791        _errors = []
792        if errors:
793            if density:
794                y1 += max(scaled_errors) / 2
795                _errors = scaled_errors
796            elif logscale:
797                y1 = max(scaled_errors[:, 0])
798                _errors = scaled_errors
799            else:
800                y1 += max(unscaled_errors) / 2
801                _errors = unscaled_errors
802
803        if like is None:
804            ylim = list(ylim)
805            if xlim is None:
806                xlim = [x0, x1]
807            if ylim[1] is None:
808                ylim[1] = y1
809            if ylim[0] != 0:
810                ylim[0] = y0
811
812        self.title = title
813        self.xtitle = xtitle
814        self.ytitle = ytitle
815        self.entries = ntot
816        self.frequencies = fs
817        self.errors = _errors
818        self.edges = edges
819        self.centers = (edges[0:-1] + edges[1:]) / 2
820        self.mean = data.mean()
821        self.mode = self.centers[np.argmax(fs)]
822        self.std = data.std()
823        self.bins = edges  # internally used by "like"
824
825        ############################### stats legend as htitle
826        addstats = False
827        if not title:
828            if "axes" not in fig_kwargs:
829                addstats = True
830                axes_opts = {}
831                fig_kwargs["axes"] = axes_opts
832            elif fig_kwargs["axes"] is False:
833                pass
834            else:
835                axes_opts = fig_kwargs["axes"]
836                if "htitle" not in axes_opts:
837                    addstats = True
838
839        if addstats:
840            htitle = f"Entries:~~{int(self.entries)}  "
841            htitle += f"Mean:~~{utils.precision(self.mean, 4)}  "
842            htitle += f"STD:~~{utils.precision(self.std, 4)}  "
843
844            axes_opts["htitle"] = htitle
845            axes_opts["htitle_justify"] = "bottom-left"
846            axes_opts["htitle_size"] = 0.016
847            # axes_opts["htitle_offset"] = [-0.49, 0.01, 0]
848
849        if mc is None:
850            mc = lc
851        if ma is None:
852            ma = alpha
853
854        if label:
855            nlab = LabelData()
856            nlab.text = label
857            nlab.tcolor = ac
858            nlab.marker = marker
859            nlab.mcolor = mc
860            if not marker:
861                nlab.marker = "s"
862                nlab.mcolor = c
863            fig_kwargs["label"] = nlab
864
865        ############################################### Figure init
866        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
867
868        if not self.yscale:
869            return
870
871        if utils.is_sequence(bins):
872            myedges = np.array(bins)
873            bins = len(bins) - 1
874        else:
875            myedges = edges
876
877        bin_centers = []
878        for i in range(bins):
879            x = (myedges[i] + myedges[i + 1]) / 2
880            bin_centers.append([x, fs[i], 0])
881
882        rs = []
883        maxheigth = 0
884        if not fill and not outline and not errors and not marker:
885            outline = True  # otherwise it's empty..
886
887        if fill:  #####################
888            if outline:
889                gap = 0
890
891            for i in range(bins):
892                F = fs[i]
893                if not F:
894                    continue
895                p0 = (myedges[i] + gap * binsize, 0, 0)
896                p1 = (myedges[i + 1] - gap * binsize, F, 0)
897
898                if radius:
899                    if gap:
900                        rds = np.array([0, 0, radius, radius])
901                    else:
902                        rd1 = 0 if i < bins - 1 and fs[i + 1] >= F else radius / 2
903                        rd2 = 0 if i > 0 and fs[i - 1] >= F else radius / 2
904                        rds = np.array([0, 0, rd1, rd2])
905                    p1_yscaled = [p1[0], p1[1] * self.yscale, 0]
906                    r = shapes.Rectangle(p0, p1_yscaled, radius=rds * binsize, res=6)
907                    r.scale([1, 1 / self.yscale, 1])
908                    r.radius = None  # so it doesnt get recreated and rescaled by insert()
909                else:
910                    r = shapes.Rectangle(p0, p1)
911
912                if texture:
913                    r.texture(texture)
914                    c = "w"
915
916                r.actor.PickableOff()
917                maxheigth = max(maxheigth, p1[1])
918                if c in colors.cmaps_names:
919                    col = colors.color_map((p0[0] + p1[0]) / 2, c, myedges[0], myedges[-1])
920                else:
921                    col = c
922                r.color(col).alpha(alpha).lighting("off")
923                r.z(self.ztolerance)
924                rs.append(r)
925
926        if outline:  #####################
927            lns = [[myedges[0], 0, 0]]
928            for i in range(bins):
929                lns.append([myedges[i], fs[i], 0])
930                lns.append([myedges[i + 1], fs[i], 0])
931                maxheigth = max(maxheigth, fs[i])
932            lns.append([myedges[-1], 0, 0])
933            outl = shapes.Line(lns, c=lc, alpha=alpha, lw=lw)
934            outl.z(self.ztolerance * 2)
935            rs.append(outl)
936
937        if errors:  #####################
938            for i in range(bins):
939                x = self.centers[i]
940                f = fs[i]
941                if not f:
942                    continue
943                err = _errors[i]
944                if utils.is_sequence(err):
945                    el = shapes.Line([x, err[0], 0], [x, err[1], 0], c=lc, alpha=alpha, lw=lw)
946                else:
947                    el = shapes.Line(
948                        [x, f - err / 2, 0], [x, f + err / 2, 0], c=lc, alpha=alpha, lw=lw
949                    )
950                el.z(self.ztolerance * 3)
951                rs.append(el)
952
953        if marker:  #####################
954
955            # remove empty bins (we dont want a marker there)
956            bin_centers = np.array(bin_centers)
957            bin_centers = bin_centers[bin_centers[:, 1] > 0]
958
959            if utils.is_sequence(ms):  ### variable point size
960                mk = shapes.Marker(marker, s=1)
961                mk.scale([1, 1 / self.yscale, 1])
962                msv = np.zeros_like(bin_centers)
963                msv[:, 0] = ms
964                marked = shapes.Glyph(
965                    bin_centers, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
966                )
967            else:  ### fixed point size
968
969                if ms is None:
970                    ms = (xlim[1] - xlim[0]) / 100.0
971                else:
972                    ms = (xlim[1] - xlim[0]) / 100.0 * ms
973
974                if utils.is_sequence(mc):
975                    mk = shapes.Marker(marker, s=ms)
976                    mk.scale([1, 1 / self.yscale, 1])
977                    msv = np.zeros_like(bin_centers)
978                    msv[:, 0] = 1
979                    marked = shapes.Glyph(
980                        bin_centers, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
981                    )
982                else:
983                    mk = shapes.Marker(marker, s=ms)
984                    mk.scale([1, 1 / self.yscale, 1])
985                    marked = shapes.Glyph(bin_centers, mk, c=mc)
986
987            marked.alpha(ma)
988            marked.z(self.ztolerance * 4)
989            rs.append(marked)
990
991        self.insert(*rs, as3d=False)
992        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:
 994    def print(self, **kwargs) -> None:
 995        """Print infos about this histogram"""
 996        txt = (
 997            f"{self.name}  {self.title}\n"
 998            f"    xtitle  = '{self.xtitle}'\n"
 999            f"    ytitle  = '{self.ytitle}'\n"
1000            f"    entries = {self.entries}\n"
1001            f"    mean    = {self.mean}\n"
1002            f"    std     = {self.std}"
1003        )
1004        colors.printc(txt, **kwargs)

Print infos about this histogram

class Histogram2D(Figure):
1008class Histogram2D(Figure):
1009    """2D histogramming."""
1010
1011    def __init__(
1012        self,
1013        xvalues,
1014        yvalues=None,
1015        bins=25,
1016        weights=None,
1017        cmap="cividis",
1018        alpha=1,
1019        gap=0,
1020        scalarbar=True,
1021        # Figure and axes options:
1022        like=None,
1023        xlim=None,
1024        ylim=(None, None),
1025        zlim=(None, None),
1026        aspect=1,
1027        title="",
1028        xtitle=" ",
1029        ytitle=" ",
1030        ztitle="",
1031        ac="k",
1032        **fig_kwargs,
1033    ):
1034        """
1035        Input data formats `[(x1,x2,..), (y1,y2,..)] or [(x1,y1), (x2,y2),..]`
1036        are both valid.
1037
1038        Use keyword `like=...` if you want to use the same format of a previously
1039        created Figure (useful when superimposing Figures) to make sure
1040        they are compatible and comparable. If they are not compatible
1041        you will receive an error message.
1042
1043        Arguments:
1044            bins : (list)
1045                binning as (nx, ny)
1046            weights : (list)
1047                array of weights to assign to each entry
1048            cmap : (str, lookuptable)
1049                color map name or look up table
1050            alpha : (float)
1051                opacity of the histogram
1052            gap : (float)
1053                separation between adjacent bins as a fraction for their size
1054            scalarbar : (bool)
1055                add a scalarbar to right of the histogram
1056            like : (Figure)
1057                grab and use the same format of the given Figure (for superimposing)
1058            xlim : (list)
1059                [x0, x1] range of interest. If left to None will automatically
1060                choose the minimum or the maximum of the data range.
1061                Data outside the range are completely ignored.
1062            ylim : (list)
1063                [y0, y1] range of interest. If left to None will automatically
1064                choose the minimum or the maximum of the data range.
1065                Data outside the range are completely ignored.
1066            aspect : (float)
1067                the desired aspect ratio of the figure.
1068            title : (str)
1069                title of the plot to appear on top.
1070                If left blank some statistics will be shown.
1071            xtitle : (str)
1072                x axis title
1073            ytitle : (str)
1074                y axis title
1075            ztitle : (str)
1076                title for the scalar bar
1077            ac : (str)
1078                axes color, additional keyword for Axes can also be added
1079                using e.g. `axes=dict(xygrid=True)`
1080
1081        Examples:
1082            - [histo_2d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_a.py)
1083            - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py)
1084
1085            ![](https://vedo.embl.es/images/pyplot/histo_2D.png)
1086        """
1087        xvalues = np.asarray(xvalues)
1088        if yvalues is None:
1089            # assume [(x1,y1), (x2,y2) ...] format
1090            yvalues = xvalues[:, 1]
1091            xvalues = xvalues[:, 0]
1092        else:
1093            yvalues = np.asarray(yvalues)
1094
1095        padding = [0, 0, 0, 0]
1096
1097        if like is None and vedo.last_figure is not None:
1098            if xlim is None and ylim == (None, None) and zlim == (None, None):
1099                like = vedo.last_figure
1100
1101        if like is not None:
1102            xlim = like.xlim
1103            ylim = like.ylim
1104            aspect = like.aspect
1105            padding = like.padding
1106            if bins is None:
1107                bins = like.bins
1108        if bins is None:
1109            bins = 20
1110
1111        if isinstance(bins, int):
1112            bins = (bins, bins)
1113
1114        if utils.is_sequence(xlim):
1115            # deal with user passing eg [x0, None]
1116            _x0, _x1 = xlim
1117            if _x0 is None:
1118                _x0 = xvalues.min()
1119            if _x1 is None:
1120                _x1 = xvalues.max()
1121            xlim = [_x0, _x1]
1122
1123        if utils.is_sequence(ylim):
1124            # deal with user passing eg [x0, None]
1125            _y0, _y1 = ylim
1126            if _y0 is None:
1127                _y0 = yvalues.min()
1128            if _y1 is None:
1129                _y1 = yvalues.max()
1130            ylim = [_y0, _y1]
1131
1132        H, xedges, yedges = np.histogram2d(
1133            xvalues, yvalues, weights=weights, bins=bins, range=(xlim, ylim)
1134        )
1135
1136        xlim = np.min(xedges), np.max(xedges)
1137        ylim = np.min(yedges), np.max(yedges)
1138        dx, dy = xlim[1] - xlim[0], ylim[1] - ylim[0]
1139
1140        fig_kwargs["title"] = title
1141        fig_kwargs["xtitle"] = xtitle
1142        fig_kwargs["ytitle"] = ytitle
1143        fig_kwargs["ac"] = ac
1144
1145        self.entries = len(xvalues)
1146        self.frequencies = H
1147        self.edges = (xedges, yedges)
1148        self.mean = (xvalues.mean(), yvalues.mean())
1149        self.std = (xvalues.std(), yvalues.std())
1150        self.bins = bins  # internally used by "like"
1151
1152        ############################### stats legend as htitle
1153        addstats = False
1154        if not title:
1155            if "axes" not in fig_kwargs:
1156                addstats = True
1157                axes_opts = {}
1158                fig_kwargs["axes"] = axes_opts
1159            elif fig_kwargs["axes"] is False:
1160                pass
1161            else:
1162                axes_opts = fig_kwargs["axes"]
1163                if "htitle" not in fig_kwargs["axes"]:
1164                    addstats = True
1165
1166        if addstats:
1167            htitle = f"Entries:~~{int(self.entries)}  "
1168            htitle += f"Mean:~~{utils.precision(self.mean, 3)}  "
1169            htitle += f"STD:~~{utils.precision(self.std, 3)}  "
1170            axes_opts["htitle"] = htitle
1171            axes_opts["htitle_justify"] = "bottom-left"
1172            axes_opts["htitle_size"] = 0.0175
1173
1174        ############################################### Figure init
1175        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1176
1177        if self.yscale:
1178            ##################### the grid
1179            acts = []
1180            g = shapes.Grid(
1181                pos=[(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, 0], s=(dx, dy), res=bins[:2]
1182            )
1183            g.alpha(alpha).lw(0).wireframe(False).flat().lighting("off")
1184            g.cmap(cmap, np.ravel(H.T), on="cells", vmin=zlim[0], vmax=zlim[1])
1185            if gap:
1186                g.shrink(abs(1 - gap))
1187
1188            if scalarbar:
1189                sc = g.add_scalarbar3d(ztitle, c=ac).scalarbar
1190
1191                # print(" g.GetBounds()[0]", g.bounds()[:2])
1192                # print("sc.GetBounds()[0]",sc.GetBounds()[:2])
1193                delta = sc.GetBounds()[0] - g.bounds()[1]
1194
1195                sc_size = sc.GetBounds()[1] - sc.GetBounds()[0]
1196
1197                sc.SetOrigin(sc.GetBounds()[0], 0, 0)
1198                sc.scale([self.yscale, 1, 1])  ## prescale trick
1199                sc.shift(-delta + 0.25*sc_size*self.yscale)
1200
1201                acts.append(sc)
1202            acts.append(g)
1203
1204            self.insert(*acts, as3d=False)
1205            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)
1011    def __init__(
1012        self,
1013        xvalues,
1014        yvalues=None,
1015        bins=25,
1016        weights=None,
1017        cmap="cividis",
1018        alpha=1,
1019        gap=0,
1020        scalarbar=True,
1021        # Figure and axes options:
1022        like=None,
1023        xlim=None,
1024        ylim=(None, None),
1025        zlim=(None, None),
1026        aspect=1,
1027        title="",
1028        xtitle=" ",
1029        ytitle=" ",
1030        ztitle="",
1031        ac="k",
1032        **fig_kwargs,
1033    ):
1034        """
1035        Input data formats `[(x1,x2,..), (y1,y2,..)] or [(x1,y1), (x2,y2),..]`
1036        are both valid.
1037
1038        Use keyword `like=...` if you want to use the same format of a previously
1039        created Figure (useful when superimposing Figures) to make sure
1040        they are compatible and comparable. If they are not compatible
1041        you will receive an error message.
1042
1043        Arguments:
1044            bins : (list)
1045                binning as (nx, ny)
1046            weights : (list)
1047                array of weights to assign to each entry
1048            cmap : (str, lookuptable)
1049                color map name or look up table
1050            alpha : (float)
1051                opacity of the histogram
1052            gap : (float)
1053                separation between adjacent bins as a fraction for their size
1054            scalarbar : (bool)
1055                add a scalarbar to right of the histogram
1056            like : (Figure)
1057                grab and use the same format of the given Figure (for superimposing)
1058            xlim : (list)
1059                [x0, x1] range of interest. If left to None will automatically
1060                choose the minimum or the maximum of the data range.
1061                Data outside the range are completely ignored.
1062            ylim : (list)
1063                [y0, y1] range of interest. If left to None will automatically
1064                choose the minimum or the maximum of the data range.
1065                Data outside the range are completely ignored.
1066            aspect : (float)
1067                the desired aspect ratio of the figure.
1068            title : (str)
1069                title of the plot to appear on top.
1070                If left blank some statistics will be shown.
1071            xtitle : (str)
1072                x axis title
1073            ytitle : (str)
1074                y axis title
1075            ztitle : (str)
1076                title for the scalar bar
1077            ac : (str)
1078                axes color, additional keyword for Axes can also be added
1079                using e.g. `axes=dict(xygrid=True)`
1080
1081        Examples:
1082            - [histo_2d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_a.py)
1083            - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py)
1084
1085            ![](https://vedo.embl.es/images/pyplot/histo_2D.png)
1086        """
1087        xvalues = np.asarray(xvalues)
1088        if yvalues is None:
1089            # assume [(x1,y1), (x2,y2) ...] format
1090            yvalues = xvalues[:, 1]
1091            xvalues = xvalues[:, 0]
1092        else:
1093            yvalues = np.asarray(yvalues)
1094
1095        padding = [0, 0, 0, 0]
1096
1097        if like is None and vedo.last_figure is not None:
1098            if xlim is None and ylim == (None, None) and zlim == (None, None):
1099                like = vedo.last_figure
1100
1101        if like is not None:
1102            xlim = like.xlim
1103            ylim = like.ylim
1104            aspect = like.aspect
1105            padding = like.padding
1106            if bins is None:
1107                bins = like.bins
1108        if bins is None:
1109            bins = 20
1110
1111        if isinstance(bins, int):
1112            bins = (bins, bins)
1113
1114        if utils.is_sequence(xlim):
1115            # deal with user passing eg [x0, None]
1116            _x0, _x1 = xlim
1117            if _x0 is None:
1118                _x0 = xvalues.min()
1119            if _x1 is None:
1120                _x1 = xvalues.max()
1121            xlim = [_x0, _x1]
1122
1123        if utils.is_sequence(ylim):
1124            # deal with user passing eg [x0, None]
1125            _y0, _y1 = ylim
1126            if _y0 is None:
1127                _y0 = yvalues.min()
1128            if _y1 is None:
1129                _y1 = yvalues.max()
1130            ylim = [_y0, _y1]
1131
1132        H, xedges, yedges = np.histogram2d(
1133            xvalues, yvalues, weights=weights, bins=bins, range=(xlim, ylim)
1134        )
1135
1136        xlim = np.min(xedges), np.max(xedges)
1137        ylim = np.min(yedges), np.max(yedges)
1138        dx, dy = xlim[1] - xlim[0], ylim[1] - ylim[0]
1139
1140        fig_kwargs["title"] = title
1141        fig_kwargs["xtitle"] = xtitle
1142        fig_kwargs["ytitle"] = ytitle
1143        fig_kwargs["ac"] = ac
1144
1145        self.entries = len(xvalues)
1146        self.frequencies = H
1147        self.edges = (xedges, yedges)
1148        self.mean = (xvalues.mean(), yvalues.mean())
1149        self.std = (xvalues.std(), yvalues.std())
1150        self.bins = bins  # internally used by "like"
1151
1152        ############################### stats legend as htitle
1153        addstats = False
1154        if not title:
1155            if "axes" not in fig_kwargs:
1156                addstats = True
1157                axes_opts = {}
1158                fig_kwargs["axes"] = axes_opts
1159            elif fig_kwargs["axes"] is False:
1160                pass
1161            else:
1162                axes_opts = fig_kwargs["axes"]
1163                if "htitle" not in fig_kwargs["axes"]:
1164                    addstats = True
1165
1166        if addstats:
1167            htitle = f"Entries:~~{int(self.entries)}  "
1168            htitle += f"Mean:~~{utils.precision(self.mean, 3)}  "
1169            htitle += f"STD:~~{utils.precision(self.std, 3)}  "
1170            axes_opts["htitle"] = htitle
1171            axes_opts["htitle_justify"] = "bottom-left"
1172            axes_opts["htitle_size"] = 0.0175
1173
1174        ############################################### Figure init
1175        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1176
1177        if self.yscale:
1178            ##################### the grid
1179            acts = []
1180            g = shapes.Grid(
1181                pos=[(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, 0], s=(dx, dy), res=bins[:2]
1182            )
1183            g.alpha(alpha).lw(0).wireframe(False).flat().lighting("off")
1184            g.cmap(cmap, np.ravel(H.T), on="cells", vmin=zlim[0], vmax=zlim[1])
1185            if gap:
1186                g.shrink(abs(1 - gap))
1187
1188            if scalarbar:
1189                sc = g.add_scalarbar3d(ztitle, c=ac).scalarbar
1190
1191                # print(" g.GetBounds()[0]", g.bounds()[:2])
1192                # print("sc.GetBounds()[0]",sc.GetBounds()[:2])
1193                delta = sc.GetBounds()[0] - g.bounds()[1]
1194
1195                sc_size = sc.GetBounds()[1] - sc.GetBounds()[0]
1196
1197                sc.SetOrigin(sc.GetBounds()[0], 0, 0)
1198                sc.scale([self.yscale, 1, 1])  ## prescale trick
1199                sc.shift(-delta + 0.25*sc_size*self.yscale)
1200
1201                acts.append(sc)
1202            acts.append(g)
1203
1204            self.insert(*acts, as3d=False)
1205            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):
1428class PlotXY(Figure):
1429    """Creates a `PlotXY(Figure)` object."""
1430
1431    def __init__(
1432        self,
1433        #
1434        data,
1435        xerrors=None,
1436        yerrors=None,
1437        #
1438        lw=2,
1439        lc=None,
1440        la=1,
1441        dashed=False,
1442        splined=False,
1443        #
1444        elw=2,  # error line width
1445        ec=None,  # error line or band color
1446        error_band=False,  # errors in x are ignored
1447        #
1448        marker="",
1449        ms=None,
1450        mc=None,
1451        ma=None,
1452        # Figure and axes options:
1453        like=None,
1454        xlim=None,
1455        ylim=(None, None),
1456        aspect=4 / 3,
1457        padding=0.05,
1458        #
1459        title="",
1460        xtitle=" ",
1461        ytitle=" ",
1462        ac="k",
1463        grid=True,
1464        ztolerance=None,
1465        label="",
1466        **fig_kwargs,
1467    ):
1468        """
1469        Arguments:
1470            xerrors : (bool)
1471                show error bars associated to each point in x
1472            yerrors : (bool)
1473                show error bars associated to each point in y
1474            lw : (int)
1475                width of the line connecting points in pixel units.
1476                Set it to 0 to remove the line.
1477            lc : (str)
1478                line color
1479            la : (float)
1480                line "alpha", opacity of the line
1481            dashed : (bool)
1482                draw a dashed line instead of a continuous line
1483            splined : (bool)
1484                spline the line joining the point as a countinous curve
1485            elw : (int)
1486                width of error bar lines in units of pixels
1487            ec : (color)
1488                color of error bar, by default the same as marker color
1489            error_band : (bool)
1490                represent errors on y as a filled error band.
1491                Use `ec` keyword to modify its color.
1492            marker : (str, int)
1493                use a marker for the data points
1494            ms : (float)
1495                marker size
1496            mc : (color)
1497                color of the marker
1498            ma : (float)
1499                opacity of the marker
1500            xlim : (list)
1501                set limits to the range for the x variable
1502            ylim : (list)
1503                set limits to the range for the y variable
1504            aspect : (float, str)
1505                Desired aspect ratio.
1506                Use `aspect="equal"` to force the same units in x and y.
1507                Scaling factor is saved in Figure.yscale.
1508            padding : (float, list)
1509                keep a padding space from the axes (as a fraction of the axis size).
1510                This can be a list of four numbers.
1511            title : (str)
1512                title to appear on the top of the frame, like a header.
1513            xtitle : (str)
1514                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1515            ytitle : (str)
1516                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1517            ac : (str)
1518                axes color
1519            grid : (bool)
1520                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1521            ztolerance : (float)
1522                a tolerance factor to superimpose objects (along the z-axis).
1523
1524        Example:
1525            ```python
1526            import numpy as np
1527            from vedo.pyplot import plot
1528            x = np.arange(0, np.pi, 0.1)
1529            fig = plot(x, np.sin(2*x), 'r0-', aspect='equal')
1530            fig+= plot(x, np.cos(2*x), 'blue4 o-', like=fig)
1531            fig.show().close()
1532            ```
1533            ![](https://vedo.embl.es/images/feats/plotxy.png)
1534
1535        Examples:
1536            - [plot_errbars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errbars.py)
1537            - [plot_errband.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errband.py)
1538            - [plot_pip.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_pip.py)
1539
1540                ![](https://vedo.embl.es/images/pyplot/plot_pip.png)
1541
1542            - [scatter1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter1.py)
1543            - [scatter2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter2.py)
1544
1545                ![](https://vedo.embl.es/images/pyplot/scatter2.png)
1546        """
1547        line = False
1548        if lw > 0:
1549            line = True
1550        if marker == "" and not line and not splined:
1551            marker = "o"
1552
1553        if like is None and vedo.last_figure is not None:
1554            if xlim is None and ylim == (None, None):
1555                like = vedo.last_figure
1556
1557        if like is not None:
1558            xlim = like.xlim
1559            ylim = like.ylim
1560            aspect = like.aspect
1561            padding = like.padding
1562
1563        if utils.is_sequence(xlim):
1564            # deal with user passing eg [x0, None]
1565            _x0, _x1 = xlim
1566            if _x0 is None:
1567                _x0 = data.min()
1568            if _x1 is None:
1569                _x1 = data.max()
1570            xlim = [_x0, _x1]
1571
1572        # purge NaN from data
1573        validIds = np.all(np.logical_not(np.isnan(data)))
1574        data = np.array(data[validIds])[0]
1575
1576        fig_kwargs["title"] = title
1577        fig_kwargs["xtitle"] = xtitle
1578        fig_kwargs["ytitle"] = ytitle
1579        fig_kwargs["ac"] = ac
1580        fig_kwargs["ztolerance"] = ztolerance
1581        fig_kwargs["grid"] = grid
1582
1583        x0, y0 = np.min(data, axis=0)
1584        x1, y1 = np.max(data, axis=0)
1585        if xerrors is not None and not error_band:
1586            x0 = min(data[:, 0] - xerrors)
1587            x1 = max(data[:, 0] + xerrors)
1588        if yerrors is not None:
1589            y0 = min(data[:, 1] - yerrors)
1590            y1 = max(data[:, 1] + yerrors)
1591
1592        if like is None:
1593            if xlim is None:
1594                xlim = (None, None)
1595            xlim = list(xlim)
1596            if xlim[0] is None:
1597                xlim[0] = x0
1598            if xlim[1] is None:
1599                xlim[1] = x1
1600            ylim = list(ylim)
1601            if ylim[0] is None:
1602                ylim[0] = y0
1603            if ylim[1] is None:
1604                ylim[1] = y1
1605
1606        self.entries = len(data)
1607        self.mean = data.mean()
1608        self.std = data.std()
1609        
1610        self.ztolerance = 0
1611        
1612        ######### the PlotXY marker
1613        # fall back solutions logic for colors
1614        if "c" in fig_kwargs:
1615            if mc is None:
1616                mc = fig_kwargs["c"]
1617            if lc is None:
1618                lc = fig_kwargs["c"]
1619            if ec is None:
1620                ec = fig_kwargs["c"]
1621        if lc is None:
1622            lc = "k"
1623        if mc is None:
1624            mc = lc
1625        if ma is None:
1626            ma = la
1627        if ec is None:
1628            if mc is None:
1629                ec = lc
1630            else:
1631                ec = mc
1632
1633        if label:
1634            nlab = LabelData()
1635            nlab.text = label
1636            nlab.tcolor = ac
1637            nlab.marker = marker
1638            if line and marker == "":
1639                nlab.marker = "-"
1640            nlab.mcolor = mc
1641            fig_kwargs["label"] = nlab
1642
1643        ############################################### Figure init
1644        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1645
1646        if not self.yscale:
1647            return
1648
1649        acts = []
1650
1651        ######### the PlotXY Line or Spline
1652        if dashed:
1653            l = shapes.DashedLine(data, c=lc, alpha=la, lw=lw)
1654            acts.append(l)
1655        elif splined:
1656            l = shapes.KSpline(data).lw(lw).c(lc).alpha(la)
1657            acts.append(l)
1658        elif line:
1659            l = shapes.Line(data, c=lc, alpha=la).lw(lw)
1660            acts.append(l)
1661
1662        if marker:
1663
1664            pts = np.c_[data, np.zeros(len(data))]
1665
1666            if utils.is_sequence(ms):
1667                ### variable point size
1668                mk = shapes.Marker(marker, s=1)
1669                mk.scale([1, 1 / self.yscale, 1])
1670                msv = np.zeros_like(pts)
1671                msv[:, 0] = ms
1672                marked = shapes.Glyph(
1673                    pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
1674                )
1675            else:
1676                ### fixed point size
1677                if ms is None:
1678                    ms = (xlim[1] - xlim[0]) / 100.0
1679
1680                if utils.is_sequence(mc):
1681                    fig_kwargs["marker_color"] = None  # for labels
1682                    mk = shapes.Marker(marker, s=ms)
1683                    mk.scale([1, 1 / self.yscale, 1])
1684                    msv = np.zeros_like(pts)
1685                    msv[:, 0] = 1
1686                    marked = shapes.Glyph(
1687                        pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
1688                    )
1689                else:
1690                    mk = shapes.Marker(marker, s=ms)
1691                    mk.scale([1, 1 / self.yscale, 1])
1692                    marked = shapes.Glyph(pts, mk, c=mc)
1693
1694            marked.name = "Marker"
1695            marked.alpha(ma)
1696            marked.z(3 * self.ztolerance)
1697            acts.append(marked)
1698
1699        ######### the PlotXY marker errors
1700        ztol = self.ztolerance
1701
1702        if error_band:
1703            yerrors = np.abs(yerrors)
1704            du = np.array(data)
1705            dd = np.array(data)
1706            du[:, 1] += yerrors
1707            dd[:, 1] -= yerrors
1708            if splined:
1709                res = len(data) * 20
1710                band1 = shapes.KSpline(du, res=res)
1711                band2 = shapes.KSpline(dd, res=res)
1712                band = shapes.Ribbon(band1, band2, res=(res, 2))
1713            else:
1714                dd = list(reversed(dd.tolist()))
1715                band = shapes.Line(du.tolist() + dd, closed=True)
1716                band.triangulate().lw(0)
1717            if ec is None:
1718                band.c(lc)
1719            else:
1720                band.c(ec)
1721            band.lighting("off").alpha(la).z(ztol / 20)
1722            acts.append(band)
1723
1724        else:
1725
1726            ## xerrors
1727            if xerrors is not None:
1728                if len(xerrors) == len(data):
1729                    errs = []
1730                    for i, val in enumerate(data):
1731                        xval, yval = val
1732                        xerr = xerrors[i] / 2
1733                        el = shapes.Line((xval - xerr, yval, ztol), (xval + xerr, yval, ztol))
1734                        el.lw(elw)
1735                        errs.append(el)
1736                    mxerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol)
1737                    acts.append(mxerrs)
1738                else:
1739                    vedo.logger.error("in PlotXY(xerrors=...): mismatch in array length")
1740
1741            ## yerrors
1742            if yerrors is not None:
1743                if len(yerrors) == len(data):
1744                    errs = []
1745                    for i, val in enumerate(data):
1746                        xval, yval = val
1747                        yerr = yerrors[i]
1748                        el = shapes.Line((xval, yval - yerr, ztol), (xval, yval + yerr, ztol))
1749                        el.lw(elw)
1750                        errs.append(el)
1751                    myerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol)
1752                    acts.append(myerrs)
1753                else:
1754                    vedo.logger.error("in PlotXY(yerrors=...): mismatch in array length")
1755
1756        self.insert(*acts, as3d=False)
1757        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)
1431    def __init__(
1432        self,
1433        #
1434        data,
1435        xerrors=None,
1436        yerrors=None,
1437        #
1438        lw=2,
1439        lc=None,
1440        la=1,
1441        dashed=False,
1442        splined=False,
1443        #
1444        elw=2,  # error line width
1445        ec=None,  # error line or band color
1446        error_band=False,  # errors in x are ignored
1447        #
1448        marker="",
1449        ms=None,
1450        mc=None,
1451        ma=None,
1452        # Figure and axes options:
1453        like=None,
1454        xlim=None,
1455        ylim=(None, None),
1456        aspect=4 / 3,
1457        padding=0.05,
1458        #
1459        title="",
1460        xtitle=" ",
1461        ytitle=" ",
1462        ac="k",
1463        grid=True,
1464        ztolerance=None,
1465        label="",
1466        **fig_kwargs,
1467    ):
1468        """
1469        Arguments:
1470            xerrors : (bool)
1471                show error bars associated to each point in x
1472            yerrors : (bool)
1473                show error bars associated to each point in y
1474            lw : (int)
1475                width of the line connecting points in pixel units.
1476                Set it to 0 to remove the line.
1477            lc : (str)
1478                line color
1479            la : (float)
1480                line "alpha", opacity of the line
1481            dashed : (bool)
1482                draw a dashed line instead of a continuous line
1483            splined : (bool)
1484                spline the line joining the point as a countinous curve
1485            elw : (int)
1486                width of error bar lines in units of pixels
1487            ec : (color)
1488                color of error bar, by default the same as marker color
1489            error_band : (bool)
1490                represent errors on y as a filled error band.
1491                Use `ec` keyword to modify its color.
1492            marker : (str, int)
1493                use a marker for the data points
1494            ms : (float)
1495                marker size
1496            mc : (color)
1497                color of the marker
1498            ma : (float)
1499                opacity of the marker
1500            xlim : (list)
1501                set limits to the range for the x variable
1502            ylim : (list)
1503                set limits to the range for the y variable
1504            aspect : (float, str)
1505                Desired aspect ratio.
1506                Use `aspect="equal"` to force the same units in x and y.
1507                Scaling factor is saved in Figure.yscale.
1508            padding : (float, list)
1509                keep a padding space from the axes (as a fraction of the axis size).
1510                This can be a list of four numbers.
1511            title : (str)
1512                title to appear on the top of the frame, like a header.
1513            xtitle : (str)
1514                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1515            ytitle : (str)
1516                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1517            ac : (str)
1518                axes color
1519            grid : (bool)
1520                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1521            ztolerance : (float)
1522                a tolerance factor to superimpose objects (along the z-axis).
1523
1524        Example:
1525            ```python
1526            import numpy as np
1527            from vedo.pyplot import plot
1528            x = np.arange(0, np.pi, 0.1)
1529            fig = plot(x, np.sin(2*x), 'r0-', aspect='equal')
1530            fig+= plot(x, np.cos(2*x), 'blue4 o-', like=fig)
1531            fig.show().close()
1532            ```
1533            ![](https://vedo.embl.es/images/feats/plotxy.png)
1534
1535        Examples:
1536            - [plot_errbars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errbars.py)
1537            - [plot_errband.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errband.py)
1538            - [plot_pip.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_pip.py)
1539
1540                ![](https://vedo.embl.es/images/pyplot/plot_pip.png)
1541
1542            - [scatter1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter1.py)
1543            - [scatter2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter2.py)
1544
1545                ![](https://vedo.embl.es/images/pyplot/scatter2.png)
1546        """
1547        line = False
1548        if lw > 0:
1549            line = True
1550        if marker == "" and not line and not splined:
1551            marker = "o"
1552
1553        if like is None and vedo.last_figure is not None:
1554            if xlim is None and ylim == (None, None):
1555                like = vedo.last_figure
1556
1557        if like is not None:
1558            xlim = like.xlim
1559            ylim = like.ylim
1560            aspect = like.aspect
1561            padding = like.padding
1562
1563        if utils.is_sequence(xlim):
1564            # deal with user passing eg [x0, None]
1565            _x0, _x1 = xlim
1566            if _x0 is None:
1567                _x0 = data.min()
1568            if _x1 is None:
1569                _x1 = data.max()
1570            xlim = [_x0, _x1]
1571
1572        # purge NaN from data
1573        validIds = np.all(np.logical_not(np.isnan(data)))
1574        data = np.array(data[validIds])[0]
1575
1576        fig_kwargs["title"] = title
1577        fig_kwargs["xtitle"] = xtitle
1578        fig_kwargs["ytitle"] = ytitle
1579        fig_kwargs["ac"] = ac
1580        fig_kwargs["ztolerance"] = ztolerance
1581        fig_kwargs["grid"] = grid
1582
1583        x0, y0 = np.min(data, axis=0)
1584        x1, y1 = np.max(data, axis=0)
1585        if xerrors is not None and not error_band:
1586            x0 = min(data[:, 0] - xerrors)
1587            x1 = max(data[:, 0] + xerrors)
1588        if yerrors is not None:
1589            y0 = min(data[:, 1] - yerrors)
1590            y1 = max(data[:, 1] + yerrors)
1591
1592        if like is None:
1593            if xlim is None:
1594                xlim = (None, None)
1595            xlim = list(xlim)
1596            if xlim[0] is None:
1597                xlim[0] = x0
1598            if xlim[1] is None:
1599                xlim[1] = x1
1600            ylim = list(ylim)
1601            if ylim[0] is None:
1602                ylim[0] = y0
1603            if ylim[1] is None:
1604                ylim[1] = y1
1605
1606        self.entries = len(data)
1607        self.mean = data.mean()
1608        self.std = data.std()
1609        
1610        self.ztolerance = 0
1611        
1612        ######### the PlotXY marker
1613        # fall back solutions logic for colors
1614        if "c" in fig_kwargs:
1615            if mc is None:
1616                mc = fig_kwargs["c"]
1617            if lc is None:
1618                lc = fig_kwargs["c"]
1619            if ec is None:
1620                ec = fig_kwargs["c"]
1621        if lc is None:
1622            lc = "k"
1623        if mc is None:
1624            mc = lc
1625        if ma is None:
1626            ma = la
1627        if ec is None:
1628            if mc is None:
1629                ec = lc
1630            else:
1631                ec = mc
1632
1633        if label:
1634            nlab = LabelData()
1635            nlab.text = label
1636            nlab.tcolor = ac
1637            nlab.marker = marker
1638            if line and marker == "":
1639                nlab.marker = "-"
1640            nlab.mcolor = mc
1641            fig_kwargs["label"] = nlab
1642
1643        ############################################### Figure init
1644        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1645
1646        if not self.yscale:
1647            return
1648
1649        acts = []
1650
1651        ######### the PlotXY Line or Spline
1652        if dashed:
1653            l = shapes.DashedLine(data, c=lc, alpha=la, lw=lw)
1654            acts.append(l)
1655        elif splined:
1656            l = shapes.KSpline(data).lw(lw).c(lc).alpha(la)
1657            acts.append(l)
1658        elif line:
1659            l = shapes.Line(data, c=lc, alpha=la).lw(lw)
1660            acts.append(l)
1661
1662        if marker:
1663
1664            pts = np.c_[data, np.zeros(len(data))]
1665
1666            if utils.is_sequence(ms):
1667                ### variable point size
1668                mk = shapes.Marker(marker, s=1)
1669                mk.scale([1, 1 / self.yscale, 1])
1670                msv = np.zeros_like(pts)
1671                msv[:, 0] = ms
1672                marked = shapes.Glyph(
1673                    pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
1674                )
1675            else:
1676                ### fixed point size
1677                if ms is None:
1678                    ms = (xlim[1] - xlim[0]) / 100.0
1679
1680                if utils.is_sequence(mc):
1681                    fig_kwargs["marker_color"] = None  # for labels
1682                    mk = shapes.Marker(marker, s=ms)
1683                    mk.scale([1, 1 / self.yscale, 1])
1684                    msv = np.zeros_like(pts)
1685                    msv[:, 0] = 1
1686                    marked = shapes.Glyph(
1687                        pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
1688                    )
1689                else:
1690                    mk = shapes.Marker(marker, s=ms)
1691                    mk.scale([1, 1 / self.yscale, 1])
1692                    marked = shapes.Glyph(pts, mk, c=mc)
1693
1694            marked.name = "Marker"
1695            marked.alpha(ma)
1696            marked.z(3 * self.ztolerance)
1697            acts.append(marked)
1698
1699        ######### the PlotXY marker errors
1700        ztol = self.ztolerance
1701
1702        if error_band:
1703            yerrors = np.abs(yerrors)
1704            du = np.array(data)
1705            dd = np.array(data)
1706            du[:, 1] += yerrors
1707            dd[:, 1] -= yerrors
1708            if splined:
1709                res = len(data) * 20
1710                band1 = shapes.KSpline(du, res=res)
1711                band2 = shapes.KSpline(dd, res=res)
1712                band = shapes.Ribbon(band1, band2, res=(res, 2))
1713            else:
1714                dd = list(reversed(dd.tolist()))
1715                band = shapes.Line(du.tolist() + dd, closed=True)
1716                band.triangulate().lw(0)
1717            if ec is None:
1718                band.c(lc)
1719            else:
1720                band.c(ec)
1721            band.lighting("off").alpha(la).z(ztol / 20)
1722            acts.append(band)
1723
1724        else:
1725
1726            ## xerrors
1727            if xerrors is not None:
1728                if len(xerrors) == len(data):
1729                    errs = []
1730                    for i, val in enumerate(data):
1731                        xval, yval = val
1732                        xerr = xerrors[i] / 2
1733                        el = shapes.Line((xval - xerr, yval, ztol), (xval + xerr, yval, ztol))
1734                        el.lw(elw)
1735                        errs.append(el)
1736                    mxerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol)
1737                    acts.append(mxerrs)
1738                else:
1739                    vedo.logger.error("in PlotXY(xerrors=...): mismatch in array length")
1740
1741            ## yerrors
1742            if yerrors is not None:
1743                if len(yerrors) == len(data):
1744                    errs = []
1745                    for i, val in enumerate(data):
1746                        xval, yval = val
1747                        yerr = yerrors[i]
1748                        el = shapes.Line((xval, yval - yerr, ztol), (xval, yval + yerr, ztol))
1749                        el.lw(elw)
1750                        errs.append(el)
1751                    myerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol)
1752                    acts.append(myerrs)
1753                else:
1754                    vedo.logger.error("in PlotXY(yerrors=...): mismatch in array length")
1755
1756        self.insert(*acts, as3d=False)
1757        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):
1209class PlotBars(Figure):
1210    """Creates a `PlotBars(Figure)` object."""
1211
1212    def __init__(
1213        self,
1214        data,
1215        errors=False,
1216        logscale=False,
1217        fill=True,
1218        gap=0.02,
1219        radius=0.05,
1220        c="olivedrab",
1221        alpha=1,
1222        texture="",
1223        outline=False,
1224        lw=2,
1225        lc="k",
1226        # Figure and axes options:
1227        like=None,
1228        xlim=(None, None),
1229        ylim=(0, None),
1230        aspect=4 / 3,
1231        padding=(0.025, 0.025, 0, 0.05),
1232        #
1233        title="",
1234        xtitle=" ",
1235        ytitle=" ",
1236        ac="k",
1237        grid=False,
1238        ztolerance=None,
1239        **fig_kwargs,
1240    ):
1241        """
1242        Input must be in format `[counts, labels, colors, edges]`.
1243        Either or both `edges` and `colors` are optional and can be omitted.
1244
1245        Use keyword `like=...` if you want to use the same format of a previously
1246        created Figure (useful when superimposing Figures) to make sure
1247        they are compatible and comparable. If they are not compatible
1248        you will receive an error message.
1249
1250        Arguments:
1251            errors : (bool)
1252                show error bars
1253            logscale : (bool)
1254                use logscale on y-axis
1255            fill : (bool)
1256                fill bars with solid color `c`
1257            gap : (float)
1258                leave a small space btw bars
1259            radius : (float)
1260                border radius of the top of the histogram bar. Default value is 0.1.
1261            texture : (str)
1262                url or path to an image to be used as texture for the bin
1263            outline : (bool)
1264                show outline of the bins
1265            xtitle : (str)
1266                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1267            ytitle : (str)
1268                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1269            ac : (str)
1270                axes color
1271            padding : (float, list)
1272                keep a padding space from the axes (as a fraction of the axis size).
1273                This can be a list of four numbers.
1274            aspect : (float)
1275                the desired aspect ratio of the figure. Default is 4/3.
1276            grid : (bool)
1277                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1278
1279        Examples:
1280            - [plot_bars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_bars.py)
1281
1282               ![](https://vedo.embl.es/images/pyplot/plot_bars.png)
1283        """
1284        ndata = len(data)
1285        if ndata == 4:
1286            counts, xlabs, cols, edges = data
1287        elif ndata == 3:
1288            counts, xlabs, cols = data
1289            edges = np.array(range(len(counts) + 1)) + 0.5
1290        elif ndata == 2:
1291            counts, xlabs = data
1292            edges = np.array(range(len(counts) + 1)) + 0.5
1293            cols = [c] * len(counts)
1294        else:
1295            m = "barplot error: data must be given as [counts, labels, colors, edges] not\n"
1296            vedo.logger.error(m + f" {data}\n     bin edges and colors are optional.")
1297            raise RuntimeError()
1298
1299        # sanity checks
1300        assert len(counts) == len(xlabs)
1301        assert len(counts) == len(cols)
1302        assert len(counts) == len(edges) - 1
1303
1304        counts = np.asarray(counts)
1305        edges = np.asarray(edges)
1306
1307        if logscale:
1308            counts = np.log10(counts + 1)
1309            if ytitle == " ":
1310                ytitle = "log_10 (counts+1)"
1311
1312        if like is None and vedo.last_figure is not None:
1313            if xlim == (None, None) and ylim == (0, None):
1314                like = vedo.last_figure
1315
1316        if like is not None:
1317            xlim = like.xlim
1318            ylim = like.ylim
1319            aspect = like.aspect
1320            padding = like.padding
1321
1322        if utils.is_sequence(xlim):
1323            # deal with user passing eg [x0, None]
1324            _x0, _x1 = xlim
1325            if _x0 is None:
1326                _x0 = np.min(edges)
1327            if _x1 is None:
1328                _x1 = np.max(edges)
1329            xlim = [_x0, _x1]
1330
1331        x0, x1 = np.min(edges), np.max(edges)
1332        y0, y1 = ylim[0], np.max(counts)
1333
1334        if like is None:
1335            ylim = list(ylim)
1336            if xlim is None:
1337                xlim = [x0, x1]
1338            if ylim[1] is None:
1339                ylim[1] = y1
1340            if ylim[0] != 0:
1341                ylim[0] = y0
1342
1343        fig_kwargs["title"] = title
1344        fig_kwargs["xtitle"] = xtitle
1345        fig_kwargs["ytitle"] = ytitle
1346        fig_kwargs["ac"] = ac
1347        fig_kwargs["ztolerance"] = ztolerance
1348        fig_kwargs["grid"] = grid
1349
1350        centers = (edges[0:-1] + edges[1:]) / 2
1351        binsizes = (centers - edges[0:-1]) * 2
1352
1353        if "axes" not in fig_kwargs:
1354            fig_kwargs["axes"] = {}
1355
1356        _xlabs = []
1357        for center, xlb in zip(centers, xlabs):
1358            _xlabs.append([center, str(xlb)])
1359        fig_kwargs["axes"]["x_values_and_labels"] = _xlabs
1360
1361        ############################################### Figure
1362        self.statslegend = ""
1363        self.edges = edges
1364        self.centers = centers
1365        self.bins = edges  # internal used by "like"
1366        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1367        if not self.yscale:
1368            return
1369
1370        rs = []
1371        maxheigth = 0
1372        if fill:  #####################
1373            if outline:
1374                gap = 0
1375
1376            for i in range(len(centers)):
1377                binsize = binsizes[i]
1378                p0 = (edges[i] + gap * binsize, 0, 0)
1379                p1 = (edges[i + 1] - gap * binsize, counts[i], 0)
1380
1381                if radius:
1382                    rds = np.array([0, 0, radius, radius])
1383                    p1_yscaled = [p1[0], p1[1] * self.yscale, 0]
1384                    r = shapes.Rectangle(p0, p1_yscaled, radius=rds * binsize, res=6)
1385                    r.scale([1, 1 / self.yscale, 1])
1386                    r.radius = None  # so it doesnt get recreated and rescaled by insert()
1387                else:
1388                    r = shapes.Rectangle(p0, p1)
1389
1390                if texture:
1391                    r.texture(texture)
1392                    c = "w"
1393
1394                r.actor.PickableOff()
1395                maxheigth = max(maxheigth, p1[1])
1396                if c in colors.cmaps_names:
1397                    col = colors.color_map((p0[0] + p1[0]) / 2, c, edges[0], edges[-1])
1398                else:
1399                    col = cols[i]
1400                r.color(col).alpha(alpha).lighting("off")
1401                r.name = f"bar_{i}"
1402                r.z(self.ztolerance)
1403                rs.append(r)
1404
1405        elif outline:  #####################
1406            lns = [[edges[0], 0, 0]]
1407            for i in range(len(centers)):
1408                lns.append([edges[i], counts[i], 0])
1409                lns.append([edges[i + 1], counts[i], 0])
1410                maxheigth = max(maxheigth, counts[i])
1411            lns.append([edges[-1], 0, 0])
1412            outl = shapes.Line(lns, c=lc, alpha=alpha, lw=lw).z(self.ztolerance)
1413            outl.name = f"bar_outline_{i}"
1414            rs.append(outl)
1415
1416        if errors:  #####################
1417            for x, f in centers:
1418                err = np.sqrt(f)
1419                el = shapes.Line([x, f - err / 2, 0], [x, f + err / 2, 0], c=lc, alpha=alpha, lw=lw)
1420                el.z(self.ztolerance * 2)
1421                rs.append(el)
1422
1423        self.insert(*rs, as3d=False)
1424        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)
1212    def __init__(
1213        self,
1214        data,
1215        errors=False,
1216        logscale=False,
1217        fill=True,
1218        gap=0.02,
1219        radius=0.05,
1220        c="olivedrab",
1221        alpha=1,
1222        texture="",
1223        outline=False,
1224        lw=2,
1225        lc="k",
1226        # Figure and axes options:
1227        like=None,
1228        xlim=(None, None),
1229        ylim=(0, None),
1230        aspect=4 / 3,
1231        padding=(0.025, 0.025, 0, 0.05),
1232        #
1233        title="",
1234        xtitle=" ",
1235        ytitle=" ",
1236        ac="k",
1237        grid=False,
1238        ztolerance=None,
1239        **fig_kwargs,
1240    ):
1241        """
1242        Input must be in format `[counts, labels, colors, edges]`.
1243        Either or both `edges` and `colors` are optional and can be omitted.
1244
1245        Use keyword `like=...` if you want to use the same format of a previously
1246        created Figure (useful when superimposing Figures) to make sure
1247        they are compatible and comparable. If they are not compatible
1248        you will receive an error message.
1249
1250        Arguments:
1251            errors : (bool)
1252                show error bars
1253            logscale : (bool)
1254                use logscale on y-axis
1255            fill : (bool)
1256                fill bars with solid color `c`
1257            gap : (float)
1258                leave a small space btw bars
1259            radius : (float)
1260                border radius of the top of the histogram bar. Default value is 0.1.
1261            texture : (str)
1262                url or path to an image to be used as texture for the bin
1263            outline : (bool)
1264                show outline of the bins
1265            xtitle : (str)
1266                title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1267            ytitle : (str)
1268                title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1269            ac : (str)
1270                axes color
1271            padding : (float, list)
1272                keep a padding space from the axes (as a fraction of the axis size).
1273                This can be a list of four numbers.
1274            aspect : (float)
1275                the desired aspect ratio of the figure. Default is 4/3.
1276            grid : (bool)
1277                show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1278
1279        Examples:
1280            - [plot_bars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_bars.py)
1281
1282               ![](https://vedo.embl.es/images/pyplot/plot_bars.png)
1283        """
1284        ndata = len(data)
1285        if ndata == 4:
1286            counts, xlabs, cols, edges = data
1287        elif ndata == 3:
1288            counts, xlabs, cols = data
1289            edges = np.array(range(len(counts) + 1)) + 0.5
1290        elif ndata == 2:
1291            counts, xlabs = data
1292            edges = np.array(range(len(counts) + 1)) + 0.5
1293            cols = [c] * len(counts)
1294        else:
1295            m = "barplot error: data must be given as [counts, labels, colors, edges] not\n"
1296            vedo.logger.error(m + f" {data}\n     bin edges and colors are optional.")
1297            raise RuntimeError()
1298
1299        # sanity checks
1300        assert len(counts) == len(xlabs)
1301        assert len(counts) == len(cols)
1302        assert len(counts) == len(edges) - 1
1303
1304        counts = np.asarray(counts)
1305        edges = np.asarray(edges)
1306
1307        if logscale:
1308            counts = np.log10(counts + 1)
1309            if ytitle == " ":
1310                ytitle = "log_10 (counts+1)"
1311
1312        if like is None and vedo.last_figure is not None:
1313            if xlim == (None, None) and ylim == (0, None):
1314                like = vedo.last_figure
1315
1316        if like is not None:
1317            xlim = like.xlim
1318            ylim = like.ylim
1319            aspect = like.aspect
1320            padding = like.padding
1321
1322        if utils.is_sequence(xlim):
1323            # deal with user passing eg [x0, None]
1324            _x0, _x1 = xlim
1325            if _x0 is None:
1326                _x0 = np.min(edges)
1327            if _x1 is None:
1328                _x1 = np.max(edges)
1329            xlim = [_x0, _x1]
1330
1331        x0, x1 = np.min(edges), np.max(edges)
1332        y0, y1 = ylim[0], np.max(counts)
1333
1334        if like is None:
1335            ylim = list(ylim)
1336            if xlim is None:
1337                xlim = [x0, x1]
1338            if ylim[1] is None:
1339                ylim[1] = y1
1340            if ylim[0] != 0:
1341                ylim[0] = y0
1342
1343        fig_kwargs["title"] = title
1344        fig_kwargs["xtitle"] = xtitle
1345        fig_kwargs["ytitle"] = ytitle
1346        fig_kwargs["ac"] = ac
1347        fig_kwargs["ztolerance"] = ztolerance
1348        fig_kwargs["grid"] = grid
1349
1350        centers = (edges[0:-1] + edges[1:]) / 2
1351        binsizes = (centers - edges[0:-1]) * 2
1352
1353        if "axes" not in fig_kwargs:
1354            fig_kwargs["axes"] = {}
1355
1356        _xlabs = []
1357        for center, xlb in zip(centers, xlabs):
1358            _xlabs.append([center, str(xlb)])
1359        fig_kwargs["axes"]["x_values_and_labels"] = _xlabs
1360
1361        ############################################### Figure
1362        self.statslegend = ""
1363        self.edges = edges
1364        self.centers = centers
1365        self.bins = edges  # internal used by "like"
1366        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
1367        if not self.yscale:
1368            return
1369
1370        rs = []
1371        maxheigth = 0
1372        if fill:  #####################
1373            if outline:
1374                gap = 0
1375
1376            for i in range(len(centers)):
1377                binsize = binsizes[i]
1378                p0 = (edges[i] + gap * binsize, 0, 0)
1379                p1 = (edges[i + 1] - gap * binsize, counts[i], 0)
1380
1381                if radius:
1382                    rds = np.array([0, 0, radius, radius])
1383                    p1_yscaled = [p1[0], p1[1] * self.yscale, 0]
1384                    r = shapes.Rectangle(p0, p1_yscaled, radius=rds * binsize, res=6)
1385                    r.scale([1, 1 / self.yscale, 1])
1386                    r.radius = None  # so it doesnt get recreated and rescaled by insert()
1387                else:
1388                    r = shapes.Rectangle(p0, p1)
1389
1390                if texture:
1391                    r.texture(texture)
1392                    c = "w"
1393
1394                r.actor.PickableOff()
1395                maxheigth = max(maxheigth, p1[1])
1396                if c in colors.cmaps_names:
1397                    col = colors.color_map((p0[0] + p1[0]) / 2, c, edges[0], edges[-1])
1398                else:
1399                    col = cols[i]
1400                r.color(col).alpha(alpha).lighting("off")
1401                r.name = f"bar_{i}"
1402                r.z(self.ztolerance)
1403                rs.append(r)
1404
1405        elif outline:  #####################
1406            lns = [[edges[0], 0, 0]]
1407            for i in range(len(centers)):
1408                lns.append([edges[i], counts[i], 0])
1409                lns.append([edges[i + 1], counts[i], 0])
1410                maxheigth = max(maxheigth, counts[i])
1411            lns.append([edges[-1], 0, 0])
1412            outl = shapes.Line(lns, c=lc, alpha=alpha, lw=lw).z(self.ztolerance)
1413            outl.name = f"bar_outline_{i}"
1414            rs.append(outl)
1415
1416        if errors:  #####################
1417            for x, f in centers:
1418                err = np.sqrt(f)
1419                el = shapes.Line([x, f - err / 2, 0], [x, f + err / 2, 0], c=lc, alpha=alpha, lw=lw)
1420                el.z(self.ztolerance * 2)
1421                rs.append(el)
1422
1423        self.insert(*rs, as3d=False)
1424        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):
1760def plot(*args, **kwargs):
1761    """
1762    Draw a 2D line plot, or scatter plot, of variable x vs variable y.
1763    Input format can be either `[allx], [allx, ally] or [(x1,y1), (x2,y2), ...]`
1764
1765    Use `like=...` if you want to use the same format of a previously
1766    created Figure (useful when superimposing Figures) to make sure
1767    they are compatible and comparable. If they are not compatible
1768    you will receive an error message.
1769
1770    Arguments:
1771        xerrors : (bool)
1772            show error bars associated to each point in x
1773        yerrors : (bool)
1774            show error bars associated to each point in y
1775        lw : (int)
1776            width of the line connecting points in pixel units.
1777            Set it to 0 to remove the line.
1778        lc : (str)
1779            line color
1780        la : (float)
1781            line "alpha", opacity of the line
1782        dashed : (bool)
1783            draw a dashed line instead of a continuous line
1784        splined : (bool)
1785            spline the line joining the point as a countinous curve
1786        elw : (int)
1787            width of error bar lines in units of pixels
1788        ec : (color)
1789            color of error bar, by default the same as marker color
1790        error_band : (bool)
1791            represent errors on y as a filled error band.
1792            Use `ec` keyword to modify its color.
1793        marker : (str, int)
1794            use a marker for the data points
1795        ms : (float)
1796            marker size
1797        mc : (color)
1798            color of the marker
1799        ma : (float)
1800            opacity of the marker
1801        xlim : (list)
1802            set limits to the range for the x variable
1803        ylim : (list)
1804            set limits to the range for the y variable
1805        aspect : (float)
1806            Desired aspect ratio.
1807            If None, it is automatically calculated to get a reasonable aspect ratio.
1808            Scaling factor is saved in Figure.yscale
1809        padding : (float, list)
1810            keep a padding space from the axes (as a fraction of the axis size).
1811            This can be a list of four numbers.
1812        title : (str)
1813            title to appear on the top of the frame, like a header.
1814        xtitle : (str)
1815            title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1816        ytitle : (str)
1817            title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1818        ac : (str)
1819            axes color
1820        grid : (bool)
1821            show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1822        ztolerance : (float)
1823            a tolerance factor to superimpose objects (along the z-axis).
1824
1825    Example:
1826        ```python
1827        import numpy as np
1828        from vedo.pyplot import plot
1829        from vedo import settings
1830        settings.remember_last_figure_format = True #############
1831        x = np.linspace(0, 6.28, num=50)
1832        fig = plot(np.sin(x), 'r-')
1833        fig+= plot(np.cos(x), 'bo-') # no need to specify like=...
1834        fig.show().close()
1835        ```
1836        <img src="https://user-images.githubusercontent.com/32848391/74363882-c3638300-4dcb-11ea-8a78-eb492ad9711f.png" width="600">
1837
1838    Examples:
1839        - [plot_errbars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errbars.py)
1840        - [plot_errband.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errband.py)
1841        - [plot_pip.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_pip.py)
1842
1843            ![](https://vedo.embl.es/images/pyplot/plot_pip.png)
1844
1845        - [scatter1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter1.py)
1846        - [scatter2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter2.py)
1847
1848
1849
1850    -------------------------------------------------------------------------
1851    .. note:: mode="bar"
1852
1853    Creates a `PlotBars(Figure)` object.
1854
1855    Input must be in format `[counts, labels, colors, edges]`.
1856    Either or both `edges` and `colors` are optional and can be omitted.
1857
1858    Arguments:
1859        errors : (bool)
1860            show error bars
1861        logscale : (bool)
1862            use logscale on y-axis
1863        fill : (bool)
1864            fill bars with solid color `c`
1865        gap : (float)
1866            leave a small space btw bars
1867        radius : (float)
1868            border radius of the top of the histogram bar. Default value is 0.1.
1869        texture : (str)
1870            url or path to an image to be used as texture for the bin
1871        outline : (bool)
1872            show outline of the bins
1873        xtitle : (str)
1874            title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
1875        ytitle : (str)
1876            title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
1877        ac : (str)
1878            axes color
1879        padding : (float, list)
1880            keep a padding space from the axes (as a fraction of the axis size).
1881            This can be a list of four numbers.
1882        aspect : (float)
1883            the desired aspect ratio of the figure. Default is 4/3.
1884        grid : (bool)
1885            show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
1886
1887    Examples:
1888        - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
1889        - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
1890        - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
1891        - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)
1892
1893        ![](https://vedo.embl.es/images/pyplot/histo_1D.png)
1894
1895
1896    ----------------------------------------------------------------------
1897    .. note:: 2D functions
1898
1899    If input is an external function or a formula, draw the surface
1900    representing the function `f(x,y)`.
1901
1902    Arguments:
1903        x : (float)
1904            x range of values
1905        y : (float)
1906            y range of values
1907        zlimits : (float)
1908            limit the z range of the independent variable
1909        zlevels : (int)
1910            will draw the specified number of z-levels contour lines
1911        show_nan : (bool)
1912            show where the function does not exist as red points
1913        bins : (list)
1914            number of bins in x and y
1915
1916    Examples:
1917        - [plot_fxy1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy1.py)
1918
1919            ![](https://vedo.embl.es/images/pyplot/plot_fxy.png)
1920        
1921        - [plot_fxy2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy2.py)
1922
1923
1924    --------------------------------------------------------------------
1925    .. note:: mode="complex"
1926
1927    If `mode='complex'` draw the real value of the function and color map the imaginary part.
1928
1929    Arguments:
1930        cmap : (str)
1931            diverging color map (white means `imag(z)=0`)
1932        lw : (float)
1933            line with of the binning
1934        bins : (list)
1935            binning in x and y
1936
1937    Examples:
1938        - [plot_fxy.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy.py)
1939
1940            ![](https://user-images.githubusercontent.com/32848391/73392962-1709a300-42db-11ea-9278-30c9d6e5eeaa.png)
1941
1942
1943    --------------------------------------------------------------------
1944    .. note:: mode="polar"
1945
1946    If `mode='polar'` input arrays are interpreted as a list of polar angles and radii.
1947    Build a polar (radar) plot by joining the set of points in polar coordinates.
1948
1949    Arguments:
1950        title : (str)
1951            plot title
1952        tsize : (float)
1953            title size
1954        bins : (int)
1955            number of bins in phi
1956        r1 : (float)
1957            inner radius
1958        r2 : (float)
1959            outer radius
1960        lsize : (float)
1961            label size
1962        c : (color)
1963            color of the line
1964        ac : (color)
1965            color of the frame and labels
1966        alpha : (float)
1967            opacity of the frame
1968        ps : (int)
1969            point size in pixels, if ps=0 no point is drawn
1970        lw : (int)
1971            line width in pixels, if lw=0 no line is drawn
1972        deg : (bool)
1973            input array is in degrees
1974        vmax : (float)
1975            normalize radius to this maximum value
1976        fill : (bool)
1977            fill convex area with solid color
1978        splined : (bool)
1979            interpolate the set of input points
1980        show_disc : (bool)
1981            draw the outer ring axis
1982        nrays : (int)
1983            draw this number of axis rays (continuous and dashed)
1984        show_lines : (bool)
1985            draw lines to the origin
1986        show_angles : (bool)
1987            draw angle values
1988
1989    Examples:
1990        - [histo_polar.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_polar.py)
1991
1992            ![](https://user-images.githubusercontent.com/32848391/64992590-7fc82400-d8d4-11e9-9c10-795f4756a73f.png)
1993
1994
1995    --------------------------------------------------------------------
1996    .. note:: mode="spheric"
1997
1998    If `mode='spheric'` input must be an external function rho(theta, phi).
1999    A surface is created in spherical coordinates.
2000
2001    Return an `Figure(Assembly)` of 2 objects: the unit
2002    sphere (in wireframe representation) and the surface `rho(theta, phi)`.
2003
2004    Arguments:
2005        rfunc : function
2006            handle to a user defined function `rho(theta, phi)`.
2007        normalize : (bool)
2008            scale surface to fit inside the unit sphere
2009        res : (int)
2010            grid resolution of the unit sphere
2011        scalarbar : (bool)
2012            add a 3D scalarbar to the plot for radius
2013        c : (color)
2014            color of the unit sphere
2015        alpha : (float)
2016            opacity of the unit sphere
2017        cmap : (str)
2018            color map for the surface
2019
2020    Examples:
2021        - [plot_spheric.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_spheric.py)
2022
2023            ![](https://vedo.embl.es/images/pyplot/plot_spheric.png)
2024    """
2025    mode = kwargs.pop("mode", "")
2026    if "spher" in mode:
2027        return _plot_spheric(args[0], **kwargs)
2028
2029    if "bar" in mode:
2030        return PlotBars(args[0], **kwargs)
2031
2032    if isinstance(args[0], str) or "function" in str(type(args[0])):
2033        if "complex" in mode:
2034            return _plot_fz(args[0], **kwargs)
2035        return _plot_fxy(args[0], **kwargs)
2036
2037    # grab the matplotlib-like options
2038    optidx = None
2039    for i, a in enumerate(args):
2040        if i > 0 and isinstance(a, str):
2041            optidx = i
2042            break
2043    if optidx:
2044        opts = args[optidx].replace(" ", "")
2045        if "--" in opts:
2046            opts = opts.replace("--", "")
2047            kwargs["dashed"] = True
2048        elif "-" in opts:
2049            opts = opts.replace("-", "")
2050        else:
2051            kwargs["lw"] = 0
2052
2053        symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"]
2054
2055        allcols = list(colors.colors.keys()) + list(colors.color_nicks.keys())
2056        for cc in allcols:
2057            if cc == "o":
2058                continue
2059            if cc in opts:
2060                opts = opts.replace(cc, "")
2061                kwargs["lc"] = cc
2062                kwargs["mc"] = cc
2063                break
2064
2065        for ss in symbs:
2066            if ss in opts:
2067                opts = opts.replace(ss, "", 1)
2068                kwargs["marker"] = ss
2069                break
2070
2071        opts.replace(" ", "")
2072        if opts:
2073            vedo.logger.error(f"in plot(), could not understand option(s): {opts}")
2074
2075    if optidx == 1 or optidx is None:
2076        if utils.is_sequence(args[0][0]) and len(args[0][0]) > 1:
2077            # print('------------- case 1', 'plot([(x,y),..])')
2078            data = np.asarray(args[0])  # (x,y)
2079            x = np.asarray(data[:, 0])
2080            y = np.asarray(data[:, 1])
2081
2082        elif len(args) == 1 or optidx == 1:
2083            # print('------------- case 2', 'plot(x)')
2084            if "pandas" in str(type(args[0])):
2085                if "ytitle" not in kwargs:
2086                    kwargs.update({"ytitle": args[0].name.replace("_", "_ ")})
2087            x = np.linspace(0, len(args[0]), num=len(args[0]))
2088            y = np.asarray(args[0]).ravel()
2089
2090        elif utils.is_sequence(args[1]):
2091            # print('------------- case 3', 'plot(allx,ally)',str(type(args[0])))
2092            if "pandas" in str(type(args[0])):
2093                if "xtitle" not in kwargs:
2094                    kwargs.update({"xtitle": args[0].name.replace("_", "_ ")})
2095            if "pandas" in str(type(args[1])):
2096                if "ytitle" not in kwargs:
2097                    kwargs.update({"ytitle": args[1].name.replace("_", "_ ")})
2098            x = np.asarray(args[0]).ravel()
2099            y = np.asarray(args[1]).ravel()
2100
2101        elif utils.is_sequence(args[0]) and utils.is_sequence(args[0][0]):
2102            # print('------------- case 4', 'plot([allx,ally])')
2103            x = np.asarray(args[0][0]).ravel()
2104            y = np.asarray(args[0][1]).ravel()
2105
2106    elif optidx == 2:
2107        # print('------------- case 5', 'plot(x,y)')
2108        x = np.asarray(args[0]).ravel()
2109        y = np.asarray(args[1]).ravel()
2110
2111    else:
2112        vedo.logger.error(f"plot(): Could not understand input arguments {args}")
2113        return None
2114
2115    if "polar" in mode:
2116        return _plot_polar(np.c_[x, y], **kwargs)
2117
2118    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):
2121def histogram(*args, **kwargs):
2122    """
2123    Histogramming for 1D and 2D data arrays.
2124
2125    This is meant as a convenience function that creates the appropriate object
2126    based on the shape of the provided input data.
2127
2128    Use keyword `like=...` if you want to use the same format of a previously
2129    created Figure (useful when superimposing Figures) to make sure
2130    they are compatible and comparable. If they are not compatible
2131    you will receive an error message.
2132
2133    -------------------------------------------------------------------------
2134    .. note:: default mode, for 1D arrays
2135
2136    Creates a `Histogram1D(Figure)` object.
2137
2138    Arguments:
2139        weights : (list)
2140            An array of weights, of the same shape as `data`. Each value in `data`
2141            only contributes its associated weight towards the bin count (instead of 1).
2142        bins : (int)
2143            number of bins
2144        vrange : (list)
2145            restrict the range of the histogram
2146        density : (bool)
2147            normalize the area to 1 by dividing by the nr of entries and bin size
2148        logscale : (bool)
2149            use logscale on y-axis
2150        fill : (bool)
2151            fill bars with solid color `c`
2152        gap : (float)
2153            leave a small space btw bars
2154        radius : (float)
2155            border radius of the top of the histogram bar. Default value is 0.1.
2156        texture : (str)
2157            url or path to an image to be used as texture for the bin
2158        outline : (bool)
2159            show outline of the bins
2160        errors : (bool)
2161            show error bars
2162        xtitle : (str)
2163            title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")`
2164        ytitle : (str)
2165            title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")`
2166        padding : (float, list)
2167            keep a padding space from the axes (as a fraction of the axis size).
2168            This can be a list of four numbers.
2169        aspect : (float)
2170            the desired aspect ratio of the histogram. Default is 4/3.
2171        grid : (bool)
2172            show the background grid for the axes, can also be set using `axes=dict(xygrid=True)`
2173        ztolerance : (float)
2174            a tolerance factor to superimpose objects (along the z-axis).
2175
2176    Examples:
2177        - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
2178        - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
2179        - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
2180        - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)
2181
2182        ![](https://vedo.embl.es/images/pyplot/histo_1D.png)
2183
2184
2185    -------------------------------------------------------------------------
2186    .. note:: default mode, for 2D arrays
2187
2188    Input data formats `[(x1,x2,..), (y1,y2,..)] or [(x1,y1), (x2,y2),..]`
2189    are both valid.
2190
2191    Arguments:
2192        bins : (list)
2193            binning as (nx, ny)
2194        weights : (list)
2195            array of weights to assign to each entry
2196        cmap : (str, lookuptable)
2197            color map name or look up table
2198        alpha : (float)
2199            opacity of the histogram
2200        gap : (float)
2201            separation between adjacent bins as a fraction for their size.
2202            Set gap=-1 to generate a quad surface.
2203        scalarbar : (bool)
2204            add a scalarbar to right of the histogram
2205        like : (Figure)
2206            grab and use the same format of the given Figure (for superimposing)
2207        xlim : (list)
2208            [x0, x1] range of interest. If left to None will automatically
2209            choose the minimum or the maximum of the data range.
2210            Data outside the range are completely ignored.
2211        ylim : (list)
2212            [y0, y1] range of interest. If left to None will automatically
2213            choose the minimum or the maximum of the data range.
2214            Data outside the range are completely ignored.
2215        aspect : (float)
2216            the desired aspect ratio of the figure.
2217        title : (str)
2218            title of the plot to appear on top.
2219            If left blank some statistics will be shown.
2220        xtitle : (str)
2221            x axis title
2222        ytitle : (str)
2223            y axis title
2224        ztitle : (str)
2225            title for the scalar bar
2226        ac : (str)
2227            axes color, additional keyword for Axes can also be added
2228            using e.g. `axes=dict(xygrid=True)`
2229
2230    Examples:
2231        - [histo_2d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_a.py)
2232        - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py)
2233
2234        ![](https://vedo.embl.es/images/pyplot/histo_2D.png)
2235
2236
2237    -------------------------------------------------------------------------
2238    .. note:: mode="3d"
2239
2240    If `mode='3d'`, build a 2D histogram as 3D bars from a list of x and y values.
2241
2242    Arguments:
2243        xtitle : (str)
2244            x axis title
2245        bins : (int)
2246            nr of bins for the smaller range in x or y
2247        vrange : (list)
2248            range in x and y in format `[(xmin,xmax), (ymin,ymax)]`
2249        norm : (float)
2250            sets a scaling factor for the z axis (frequency axis)
2251        fill : (bool)
2252            draw solid hexagons
2253        cmap : (str)
2254            color map name for elevation
2255        gap : (float)
2256            keep a internal empty gap between bins [0,1]
2257        zscale : (float)
2258            rescale the (already normalized) zaxis for visual convenience
2259
2260    Examples:
2261        - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/histo_2d_b.py)
2262
2263
2264    -------------------------------------------------------------------------
2265    .. note:: mode="hexbin"
2266
2267    If `mode='hexbin'`, build a hexagonal histogram from a list of x and y values.
2268
2269    Arguments:
2270        xtitle : (str)
2271            x axis title
2272        bins : (int)
2273            nr of bins for the smaller range in x or y
2274        vrange : (list)
2275            range in x and y in format `[(xmin,xmax), (ymin,ymax)]`
2276        norm : (float)
2277            sets a scaling factor for the z axis (frequency axis)
2278        fill : (bool)
2279            draw solid hexagons
2280        cmap : (str)
2281            color map name for elevation
2282
2283    Examples:
2284        - [histo_hexagonal.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_hexagonal.py)
2285
2286        ![](https://vedo.embl.es/images/pyplot/histo_hexagonal.png)
2287
2288
2289    -------------------------------------------------------------------------
2290    .. note:: mode="polar"
2291
2292    If `mode='polar'` assume input is polar coordinate system (rho, theta):
2293
2294    Arguments:
2295        weights : (list)
2296            Array of weights, of the same shape as the input.
2297            Each value only contributes its associated weight towards the bin count (instead of 1).
2298        title : (str)
2299            histogram title
2300        tsize : (float)
2301            title size
2302        bins : (int)
2303            number of bins in phi
2304        r1 : (float)
2305            inner radius
2306        r2 : (float)
2307            outer radius
2308        phigap : (float)
2309            gap angle btw 2 radial bars, in degrees
2310        rgap : (float)
2311            gap factor along radius of numeric angle labels
2312        lpos : (float)
2313            label gap factor along radius
2314        lsize : (float)
2315            label size
2316        c : (color)
2317            color of the histogram bars, can be a list of length `bins`
2318        bc : (color)
2319            color of the frame and labels
2320        alpha : (float)
2321            opacity of the frame
2322        cmap : (str)
2323            color map name
2324        deg : (bool)
2325            input array is in degrees
2326        vmin : (float)
2327            minimum value of the radial axis
2328        vmax : (float)
2329            maximum value of the radial axis
2330        labels : (list)
2331            list of labels, must be of length `bins`
2332        show_disc : (bool)
2333            show the outer ring axis
2334        nrays : (int)
2335            draw this number of axis rays (continuous and dashed)
2336        show_lines : (bool)
2337            show lines to the origin
2338        show_angles : (bool)
2339            show angular values
2340        show_errors : (bool)
2341            show error bars
2342
2343    Examples:
2344        - [histo_polar.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_polar.py)
2345
2346        ![](https://vedo.embl.es/images/pyplot/histo_polar.png)
2347
2348
2349    -------------------------------------------------------------------------
2350    .. note:: mode="spheric"
2351
2352    If `mode='spheric'`, build a histogram from list of theta and phi values.
2353
2354    Arguments:
2355        rmax : (float)
2356            maximum radial elevation of bin
2357        res : (int)
2358            sphere resolution
2359        cmap : (str)
2360            color map name
2361        lw : (int)
2362            line width of the bin edges
2363
2364    Examples:
2365        - [histo_spheric.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_spheric.py)
2366
2367        ![](https://vedo.embl.es/images/pyplot/histo_spheric.png)
2368    """
2369    mode = kwargs.pop("mode", "")
2370    if len(args) == 2:  # x, y
2371
2372        if "spher" in mode:
2373            return _histogram_spheric(args[0], args[1], **kwargs)
2374
2375        if "hex" in mode:
2376            return _histogram_hex_bin(args[0], args[1], **kwargs)
2377
2378        if "3d" in mode.lower():
2379            return _histogram_quad_bin(args[0], args[1], **kwargs)
2380
2381        return Histogram2D(args[0], args[1], **kwargs)
2382
2383    elif len(args) == 1:
2384
2385        if isinstance(args[0], vedo.Volume):
2386            data = args[0].pointdata[0]
2387        elif isinstance(args[0], vedo.Points):
2388            pd0 = args[0].pointdata[0]
2389            if pd0 is not None:
2390                data = pd0.ravel()
2391            else:
2392                data = args[0].celldata[0].ravel()
2393        else:
2394            try:
2395                if "pandas" in str(type(args[0])):
2396                    if "xtitle" not in kwargs:
2397                        kwargs.update({"xtitle": args[0].name.replace("_", "_ ")})
2398            except:
2399                pass
2400            data = np.asarray(args[0])
2401
2402        if "spher" in mode:
2403            return _histogram_spheric(args[0][:, 0], args[0][:, 1], **kwargs)
2404
2405        if data.ndim == 1:
2406            if "polar" in mode:
2407                return _histogram_polar(data, **kwargs)
2408            return Histogram1D(data, **kwargs)
2409
2410        if "hex" in mode:
2411            return _histogram_hex_bin(args[0][:, 0], args[0][:, 1], **kwargs)
2412
2413        if "3d" in mode.lower():
2414            return _histogram_quad_bin(args[0][:, 0], args[0][:, 1], **kwargs)
2415
2416        return Histogram2D(args[0], **kwargs)
2417
2418    vedo.logger.error(f"in histogram(): could not understand input {args[0]}")
2419    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:
2422def fit(
2423    points, deg=1, niter=0, nstd=3, xerrors=None, yerrors=None, vrange=None, res=250, lw=3, c="red4"
2424) -> "vedo.shapes.Line":
2425    """
2426    Polynomial fitting with parameter error and error bands calculation.
2427    Errors bars in both x and y are supported.
2428
2429    Returns a `vedo.shapes.Line` object.
2430
2431    Additional information about the fitting output can be accessed with:
2432
2433    `fitd = fit(pts)`
2434
2435    - `fitd.coefficients` will contain the coefficients of the polynomial fit
2436    - `fitd.coefficient_errors`, errors on the fitting coefficients
2437    - `fitd.monte_carlo_coefficients`, fitting coefficient set from MC generation
2438    - `fitd.covariance_matrix`, covariance matrix as a numpy array
2439    - `fitd.reduced_chi2`, reduced chi-square of the fitting
2440    - `fitd.ndof`, number of degrees of freedom
2441    - `fitd.data_sigma`, mean data dispersion from the central fit assuming `Chi2=1`
2442    - `fitd.error_lines`, a `vedo.shapes.Line` object for the upper and lower error band
2443    - `fitd.error_band`, the `vedo.mesh.Mesh` object representing the error band
2444
2445    Errors on x and y can be specified. If left to `None` an estimate is made from
2446    the statistical spread of the dataset itself. Errors are always assumed gaussian.
2447
2448    Arguments:
2449        deg : (int)
2450            degree of the polynomial to be fitted
2451        niter : (int)
2452            number of monte-carlo iterations to compute error bands.
2453            If set to 0, return the simple least-squares fit with naive error estimation
2454            on coefficients only. A reasonable non-zero value to set is about 500, in
2455            this case *error_lines*, *error_band* and the other class attributes are filled
2456        nstd : (float)
2457            nr. of standard deviation to use for error calculation
2458        xerrors : (list)
2459            array of the same length of points with the errors on x
2460        yerrors : (list)
2461            array of the same length of points with the errors on y
2462        vrange : (list)
2463            specify the domain range of the fitting line
2464            (only affects visualization, but can be used to extrapolate the fit
2465            outside the data range)
2466        res : (int)
2467            resolution of the output fitted line and error lines
2468
2469    Examples:
2470        - [fit_polynomial1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fit_polynomial1.py)
2471
2472        ![](https://vedo.embl.es/images/pyplot/fitPolynomial1.png)
2473    """
2474    if isinstance(points, vedo.pointcloud.Points):
2475        points = points.vertices
2476    points = np.asarray(points)
2477    if len(points) == 2:  # assume user is passing [x,y]
2478        points = np.c_[points[0], points[1]]
2479    x = points[:, 0]
2480    y = points[:, 1]  # ignore z
2481
2482    n = len(x)
2483    ndof = n - deg - 1
2484    if vrange is not None:
2485        x0, x1 = vrange
2486    else:
2487        x0, x1 = np.min(x), np.max(x)
2488        if xerrors is not None:
2489            x0 -= xerrors[0] / 2
2490            x1 += xerrors[-1] / 2
2491
2492    tol = (x1 - x0) / 10000
2493    xr = np.linspace(x0, x1, res)
2494
2495    # project x errs on y
2496    if xerrors is not None:
2497        xerrors = np.asarray(xerrors)
2498        if yerrors is not None:
2499            yerrors = np.asarray(yerrors)
2500            w = 1.0 / yerrors
2501            coeffs = np.polyfit(x, y, deg, w=w, rcond=None)
2502        else:
2503            coeffs = np.polyfit(x, y, deg, rcond=None)
2504        # update yerrors, 1 bootstrap iteration is enough
2505        p1d = np.poly1d(coeffs)
2506        der = (p1d(x + tol) - p1d(x)) / tol
2507        yerrors = np.sqrt(yerrors * yerrors + np.power(der * xerrors, 2))
2508
2509    if yerrors is not None:
2510        yerrors = np.asarray(yerrors)
2511        w = 1.0 / yerrors
2512        coeffs, V = np.polyfit(x, y, deg, w=w, rcond=None, cov=True)
2513    else:
2514        w = 1
2515        coeffs, V = np.polyfit(x, y, deg, rcond=None, cov=True)
2516
2517    p1d = np.poly1d(coeffs)
2518    theor = p1d(xr)
2519    fitl = shapes.Line(np.c_[xr, theor], lw=lw, c=c).z(tol * 2)
2520    fitl.coefficients = coeffs
2521    fitl.covariance_matrix = V
2522    residuals2_sum = np.sum(np.power(p1d(x) - y, 2)) / ndof
2523    sigma = np.sqrt(residuals2_sum)
2524    fitl.reduced_chi2 = np.sum(np.power((p1d(x) - y) * w, 2)) / ndof
2525    fitl.ndof = ndof
2526    fitl.data_sigma = sigma  # worked out from data using chi2=1 hypo
2527    fitl.name = "LinearPolynomialFit"
2528
2529    if not niter:
2530        fitl.coefficient_errors = np.sqrt(np.diag(V))
2531        return fitl  ################################
2532
2533    if yerrors is not None:
2534        sigma = yerrors
2535    else:
2536        w = None
2537        fitl.reduced_chi2 = 1
2538
2539    Theors, all_coeffs = [], []
2540    for i in range(niter):
2541        noise = np.random.randn(n) * sigma
2542        coeffs = np.polyfit(x, y + noise, deg, w=w, rcond=None)
2543        all_coeffs.append(coeffs)
2544        P1d = np.poly1d(coeffs)
2545        Theor = P1d(xr)
2546        Theors.append(Theor)
2547    # all_coeffs = np.array(all_coeffs)
2548    fitl.monte_carlo_coefficients = np.array(all_coeffs)
2549
2550    stds = np.std(Theors, axis=0)
2551    fitl.coefficient_errors = np.std(all_coeffs, axis=0)
2552
2553    # check distributions on the fly
2554    # for i in range(deg+1):
2555    #     histogram(all_coeffs[:,i],title='par'+str(i)).show(new=1)
2556    # histogram(all_coeffs[:,0], all_coeffs[:,1],
2557    #           xtitle='param0', ytitle='param1',scalarbar=1).show(new=1)
2558    # histogram(all_coeffs[:,1], all_coeffs[:,2],
2559    #           xtitle='param1', ytitle='param2').show(new=1)
2560    # histogram(all_coeffs[:,0], all_coeffs[:,2],
2561    #           xtitle='param0', ytitle='param2').show(new=1)
2562
2563    error_lines = []
2564    for i in [nstd, -nstd]:
2565        pp = np.c_[xr, theor + stds * i]
2566        el = shapes.Line(pp, lw=1, alpha=0.2, c="k").z(tol)
2567        error_lines.append(el)
2568        el.name = "ErrorLine for sigma=" + str(i)
2569
2570    fitl.error_lines = error_lines
2571    l1 = error_lines[0].vertices.tolist()
2572    cband = l1 + list(reversed(error_lines[1].vertices.tolist())) + [l1[0]]
2573    fitl.error_band = shapes.Line(cband).triangulate().lw(0).c("k", 0.15)
2574    fitl.error_band.name = "PolynomialFitErrorBand"
2575    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 pie_chart( 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:
3286def pie_chart(
3287    fractions,
3288    title="",
3289    tsize=0.3,
3290    r1=1.7,
3291    r2=1,
3292    phigap=0,
3293    lpos=0.8,
3294    lsize=0.15,
3295    c=None,
3296    bc="k",
3297    alpha=1,
3298    labels=(),
3299    show_disc=False,
3300) -> "Assembly":
3301    """
3302    Donut plot or pie chart.
3303
3304    Arguments:
3305        title : (str)
3306            plot title
3307        tsize : (float)
3308            title size
3309        r1 : (float) inner radius
3310        r2 : (float)
3311            outer radius, starting from r1
3312        phigap : (float)
3313            gap angle btw 2 radial bars, in degrees
3314        lpos : (float)
3315            label gap factor along radius
3316        lsize : (float)
3317            label size
3318        c : (color)
3319            color of the plot slices
3320        bc : (color)
3321            color of the disc frame
3322        alpha : (float)
3323            opacity of the disc frame
3324        labels : (list)
3325            list of labels
3326        show_disc : (bool)
3327            show the outer ring axis
3328
3329    Examples:
3330        - [donut.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/donut.py)
3331
3332            ![](https://vedo.embl.es/images/pyplot/donut.png)
3333    """
3334    fractions = np.array(fractions, dtype=float)
3335    angles = np.add.accumulate(2 * np.pi * fractions)
3336    angles[-1] = 2 * np.pi
3337    if angles[-2] > 2 * np.pi:
3338        print("Error in donut(): fractions must sum to 1.")
3339        raise RuntimeError
3340
3341    cols = []
3342    for i, th in enumerate(np.linspace(0, 2 * np.pi, 360, endpoint=False)):
3343        for ia, a in enumerate(angles):
3344            if th < a:
3345                cols.append(c[ia])
3346                break
3347    labs = []
3348    if labels:
3349        angles = np.concatenate([[0], angles])
3350        labs = [""] * 360
3351        for i in range(len(labels)):
3352            a = (angles[i + 1] + angles[i]) / 2
3353            j = int(a / np.pi * 180)
3354            labs[j] = labels[i]
3355
3356    data = np.linspace(0, 2 * np.pi, 360, endpoint=False) + 0.005
3357    dn = _histogram_polar(
3358        data,
3359        title=title,
3360        bins=360,
3361        r1=r1,
3362        r2=r2,
3363        phigap=phigap,
3364        lpos=lpos,
3365        lsize=lsize,
3366        tsize=tsize,
3367        c=cols,
3368        bc=bc,
3369        alpha=alpha,
3370        vmin=0,
3371        vmax=1,
3372        labels=labs,
3373        show_disc=show_disc,
3374        show_lines=0,
3375        show_angles=0,
3376        show_errors=0,
3377    )
3378    dn.name = "Donut"
3379    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:
3382def violin(
3383    values,
3384    bins=10,
3385    vlim=None,
3386    x=0,
3387    width=3,
3388    splined=True,
3389    fill=True,
3390    c="violet",
3391    alpha=1,
3392    outline=True,
3393    centerline=True,
3394    lc="darkorchid",
3395    lw=3,
3396) -> "Assembly":
3397    """
3398    Violin style histogram.
3399
3400    Arguments:
3401        bins : (int)
3402            number of bins
3403        vlim : (list)
3404            input value limits. Crop values outside range
3405        x : (float)
3406            x-position of the violin axis
3407        width : (float)
3408            width factor of the normalized distribution
3409        splined : (bool)
3410            spline the outline
3411        fill : (bool)
3412            fill violin with solid color
3413        outline : (bool)
3414            add the distribution outline
3415        centerline : (bool)
3416            add the vertical centerline at x
3417        lc : (color)
3418            line color
3419
3420    Examples:
3421        - [histo_violin.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/histo_violin.py)
3422
3423            ![](https://vedo.embl.es/images/pyplot/histo_violin.png)
3424    """
3425    fs, edges = np.histogram(values, bins=bins, range=vlim)
3426    mine, maxe = np.min(edges), np.max(edges)
3427    fs = fs.astype(float) / len(values) * width
3428
3429    rs = []
3430
3431    if splined:
3432        lnl, lnr = [(0, edges[0], 0)], [(0, edges[0], 0)]
3433        for i in range(bins):
3434            xc = (edges[i] + edges[i + 1]) / 2
3435            yc = fs[i]
3436            lnl.append([-yc, xc, 0])
3437            lnr.append([yc, xc, 0])
3438        lnl.append((0, edges[-1], 0))
3439        lnr.append((0, edges[-1], 0))
3440        spl = shapes.KSpline(lnl).x(x)
3441        spr = shapes.KSpline(lnr).x(x)
3442        spl.color(lc).alpha(alpha).lw(lw)
3443        spr.color(lc).alpha(alpha).lw(lw)
3444        if outline:
3445            rs.append(spl)
3446            rs.append(spr)
3447        if fill:
3448            rb = shapes.Ribbon(spl, spr, c=c, alpha=alpha).lighting("off")
3449            rs.append(rb)
3450
3451    else:
3452        lns1 = [[0, mine, 0]]
3453        for i in range(bins):
3454            lns1.append([fs[i], edges[i], 0])
3455            lns1.append([fs[i], edges[i + 1], 0])
3456        lns1.append([0, maxe, 0])
3457
3458        lns2 = [[0, mine, 0]]
3459        for i in range(bins):
3460            lns2.append([-fs[i], edges[i], 0])
3461            lns2.append([-fs[i], edges[i + 1], 0])
3462        lns2.append([0, maxe, 0])
3463
3464        if outline:
3465            rs.append(shapes.Line(lns1, c=lc, alpha=alpha, lw=lw).x(x))
3466            rs.append(shapes.Line(lns2, c=lc, alpha=alpha, lw=lw).x(x))
3467
3468        if fill:
3469            for i in range(bins):
3470                p0 = (-fs[i], edges[i], 0)
3471                p1 = (fs[i], edges[i + 1], 0)
3472                r = shapes.Rectangle(p0, p1).x(p0[0] + x)
3473                r.color(c).alpha(alpha).lighting("off")
3474                rs.append(r)
3475
3476    if centerline:
3477        cl = shapes.Line([0, mine, 0.01], [0, maxe, 0.01], c=lc, alpha=alpha, lw=2).x(x)
3478        rs.append(cl)
3479
3480    asse = Assembly(rs)
3481    asse.name = "Violin"
3482    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:
3485def whisker(data, s=0.25, c="k", lw=2, bc="blue", alpha=0.25, r=5, jitter=True, horizontal=False) -> "Assembly":
3486    """
3487    Generate a "whisker" bar from a 1-dimensional dataset.
3488
3489    Arguments:
3490        s : (float)
3491            size of the box
3492        c : (color)
3493            color of the lines
3494        lw : (float)
3495            line width
3496        bc : (color)
3497            color of the box
3498        alpha : (float)
3499            transparency of the box
3500        r : (float)
3501            point radius in pixels (use value 0 to disable)
3502        jitter : (bool)
3503            add some randomness to points to avoid overlap
3504        horizontal : (bool)
3505            set horizontal layout
3506
3507    Examples:
3508        - [whiskers.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/whiskers.py)
3509
3510            ![](https://vedo.embl.es/images/pyplot/whiskers.png)
3511    """
3512    xvals = np.zeros_like(np.asarray(data))
3513    if jitter:
3514        xjit = np.random.randn(len(xvals)) * s / 9
3515        xjit = np.clip(xjit, -s / 2.1, s / 2.1)
3516        xvals += xjit
3517
3518    dmean = np.mean(data)
3519    dq05 = np.quantile(data, 0.05)
3520    dq25 = np.quantile(data, 0.25)
3521    dq75 = np.quantile(data, 0.75)
3522    dq95 = np.quantile(data, 0.95)
3523
3524    pts = None
3525    if r:
3526        pts = shapes.Points(np.array([xvals, data]).T, c=c, r=r)
3527
3528    rec = shapes.Rectangle([-s / 2, dq25], [s / 2, dq75], c=bc, alpha=alpha)
3529    rec.properties.LightingOff()
3530    rl = shapes.Line([[-s / 2, dq25], [s / 2, dq25], [s / 2, dq75], [-s / 2, dq75]], closed=True)
3531    l1 = shapes.Line([0, dq05, 0], [0, dq25, 0], c=c, lw=lw)
3532    l2 = shapes.Line([0, dq75, 0], [0, dq95, 0], c=c, lw=lw)
3533    lm = shapes.Line([-s / 2, dmean], [s / 2, dmean])
3534    lns = merge(l1, l2, lm, rl)
3535    asse = Assembly([lns, rec, pts])
3536    if horizontal:
3537        asse.rotate_z(-90)
3538    asse.name = "Whisker"
3539    asse.info["mean"] = dmean
3540    asse.info["quantile_05"] = dq05
3541    asse.info["quantile_25"] = dq25
3542    asse.info["quantile_75"] = dq75
3543    asse.info["quantile_95"] = dq95
3544    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]:
3547def streamplot(
3548    X, Y, U, V, direction="both", max_propagation=None, lw=2, cmap="viridis", probes=()
3549) -> Union["vedo.shapes.Lines", None]:
3550    """
3551    Generate a streamline plot of a vectorial field (U,V) defined at positions (X,Y).
3552    Returns a `Mesh` object.
3553
3554    Arguments:
3555        direction : (str)
3556            either "forward", "backward" or "both"
3557        max_propagation : (float)
3558            maximum physical length of the streamline
3559        lw : (float)
3560            line width in absolute units
3561
3562    Examples:
3563        - [plot_stream.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/plot_stream.py)
3564
3565            ![](https://vedo.embl.es/images/pyplot/plot_stream.png)
3566    """
3567    n = len(X)
3568    m = len(Y[0])
3569    if n != m:
3570        print("Limitation in streamplot(): only square grids are allowed.", n, m)
3571        raise RuntimeError()
3572
3573    xmin, xmax = X[0][0], X[-1][-1]
3574    ymin, ymax = Y[0][0], Y[-1][-1]
3575
3576    field = np.sqrt(U * U + V * V)
3577
3578    vol = vedo.Volume(field, dims=(n, n, 1))
3579
3580    uf = np.ravel(U, order="F")
3581    vf = np.ravel(V, order="F")
3582    vects = np.c_[uf, vf, np.zeros_like(uf)]
3583    vol.pointdata["StreamPlotField"] = vects
3584
3585    if len(probes) == 0:
3586        probe = shapes.Grid(pos=((n - 1) / 2, (n - 1) / 2, 0), s=(n - 1, n - 1), res=(n - 1, n - 1))
3587    else:
3588        if isinstance(probes, vedo.Points):
3589            probes = probes.vertices
3590        else:
3591            probes = np.array(probes, dtype=float)
3592            if len(probes[0]) == 2:
3593                probes = np.c_[probes[:, 0], probes[:, 1], np.zeros(len(probes))]
3594        sv = [(n - 1) / (xmax - xmin), (n - 1) / (ymax - ymin), 1]
3595        probes = probes - [xmin, ymin, 0]
3596        probes = np.multiply(probes, sv)
3597        probe = vedo.Points(probes)
3598
3599    stream = vol.compute_streamlines(probe, direction=direction, max_propagation=max_propagation)
3600    if stream:
3601        stream.lw(lw).cmap(cmap).lighting("off")
3602        stream.scale([1 / (n - 1) * (xmax - xmin), 1 / (n - 1) * (ymax - ymin), 1])
3603        stream.shift(xmin, ymin)
3604    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:
3607def matrix(
3608    M,
3609    title="Matrix",
3610    xtitle="",
3611    ytitle="",
3612    xlabels=(),
3613    ylabels=(),
3614    xrotation=0,
3615    cmap="Reds",
3616    vmin=None,
3617    vmax=None,
3618    precision=2,
3619    font="Theemim",
3620    scale=0,
3621    scalarbar=True,
3622    lc="white",
3623    lw=0,
3624    c="black",
3625    alpha=1,
3626) -> "Assembly":
3627    """
3628    Generate a matrix, or a 2D color-coded plot with bin labels.
3629
3630    Returns an `Assembly` object.
3631
3632    Arguments:
3633        M : (list, numpy array)
3634            the input array to visualize
3635        title : (str)
3636            title of the plot
3637        xtitle : (str)
3638            title of the horizontal colmuns
3639        ytitle : (str)
3640            title of the vertical rows
3641        xlabels : (list)
3642            individual string labels for each column. Must be of length m
3643        ylabels : (list)
3644            individual string labels for each row. Must be of length n
3645        xrotation : (float)
3646            rotation of the horizontal labels
3647        cmap : (str)
3648            color map name
3649        vmin : (float)
3650            minimum value of the colormap range
3651        vmax : (float)
3652            maximum value of the colormap range
3653        precision : (int)
3654            number of digits for the matrix entries or bins
3655        font : (str)
3656            font name. Check [available fonts here](https://vedo.embl.es/fonts).
3657
3658        scale : (float)
3659            size of the numeric entries or bin values
3660        scalarbar : (bool)
3661            add a scalar bar to the right of the plot
3662        lc : (str)
3663            color of the line separating the bins
3664        lw : (float)
3665            Width of the line separating the bins
3666        c : (str)
3667            text color
3668        alpha : (float)
3669            plot transparency
3670
3671    Examples:
3672        - [np_matrix.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/np_matrix.py)
3673
3674            ![](https://vedo.embl.es/images/pyplot/np_matrix.png)
3675    """
3676    M = np.asarray(M)
3677    n, m = M.shape
3678    gr = shapes.Grid(res=(m, n), s=(m / (m + n) * 2, n / (m + n) * 2), c=c, alpha=alpha)
3679    gr.wireframe(False).lc(lc).lw(lw)
3680
3681    matr = np.flip(np.flip(M), axis=1).ravel(order="C")
3682    gr.cmap(cmap, matr, on="cells", vmin=vmin, vmax=vmax)
3683    sbar = None
3684    if scalarbar:
3685        gr.add_scalarbar3d(title_font=font, label_font=font)
3686        sbar = gr.scalarbar
3687    labs = None
3688    if scale != 0:
3689        labs = gr.labels(
3690            on="cells",
3691            scale=scale / max(m, n),
3692            precision=precision,
3693            font=font,
3694            justify="center",
3695            c=c,
3696        )
3697        labs.z(0.001)
3698    t = None
3699    if title:
3700        if title == "Matrix":
3701            title += " " + str(n) + "x" + str(m)
3702        t = shapes.Text3D(title, font=font, s=0.04, justify="bottom-center", c=c)
3703        t.shift(0, n / (m + n) * 1.05)
3704
3705    xlabs = None
3706    if len(xlabels) == m:
3707        xlabs = []
3708        jus = "top-center"
3709        if xrotation > 44:
3710            jus = "right-center"
3711        for i in range(m):
3712            xl = shapes.Text3D(xlabels[i], font=font, s=0.02, justify=jus, c=c).rotate_z(xrotation)
3713            xl.shift((2 * i - m + 1) / (m + n), -n / (m + n) * 1.05)
3714            xlabs.append(xl)
3715
3716    ylabs = None
3717    if len(ylabels) == n:
3718        ylabels = list(reversed(ylabels))
3719        ylabs = []
3720        for i in range(n):
3721            yl = shapes.Text3D(ylabels[i], font=font, s=0.02, justify="right-center", c=c)
3722            yl.shift(-m / (m + n) * 1.05, (2 * i - n + 1) / (m + n))
3723            ylabs.append(yl)
3724
3725    xt = None
3726    if xtitle:
3727        xt = shapes.Text3D(xtitle, font=font, s=0.035, justify="top-center", c=c)
3728        xt.shift(0, -n / (m + n) * 1.05)
3729        if xlabs is not None:
3730            y0, y1 = xlabs[0].ybounds()
3731            xt.shift(0, -(y1 - y0) - 0.55 / (m + n))
3732    yt = None
3733    if ytitle:
3734        yt = shapes.Text3D(ytitle, font=font, s=0.035, justify="bottom-center", c=c).rotate_z(90)
3735        yt.shift(-m / (m + n) * 1.05, 0)
3736        if ylabs is not None:
3737            x0, x1 = ylabs[0].xbounds()
3738            yt.shift(-(x1 - x0) - 0.55 / (m + n), 0)
3739    asse = Assembly(gr, sbar, labs, t, xt, yt, xlabs, ylabs)
3740    asse.name = "Matrix"
3741    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):
3891class DirectedGraph(Assembly):
3892    """
3893    Support for Directed Graphs.
3894    """
3895
3896    def __init__(self, **kargs):
3897        """
3898        A graph consists of a collection of nodes (without postional information)
3899        and a collection of edges connecting pairs of nodes.
3900        The task is to determine the node positions only based on their connections.
3901
3902        This class is derived from class `Assembly`, and it assembles 4 Mesh objects
3903        representing the graph, the node labels, edge labels and edge arrows.
3904
3905        Arguments:
3906            c : (color)
3907                Color of the Graph
3908            n : (int)
3909                number of the initial set of nodes
3910            layout : (int, str)
3911                layout in
3912                `['2d', 'fast2d', 'clustering2d', 'circular', 'circular3d', 'cone', 'force', 'tree']`.
3913                Each of these layouts has different available options.
3914
3915        ---------------------------------------------------------------
3916        .. note:: Options for layouts '2d', 'fast2d' and 'clustering2d'
3917
3918        Arguments:
3919            seed : (int)
3920                seed of the random number generator used to jitter point positions
3921            rest_distance : (float)
3922                manually set the resting distance
3923            nmax : (int)
3924                the maximum number of iterations to be used
3925            zrange : (list)
3926                expand 2d graph along z axis.
3927
3928        ---------------------------------------------------------------
3929        .. note:: Options for layouts 'circular', and 'circular3d':
3930
3931        Arguments:
3932            radius : (float)
3933                set the radius of the circles
3934            height : (float)
3935                set the vertical (local z) distance between the circles
3936            zrange : (float)
3937                expand 2d graph along z axis
3938
3939        ---------------------------------------------------------------
3940        .. note:: Options for layout 'cone'
3941
3942        Arguments:
3943            compactness : (float)
3944                ratio between the average width of a cone in the tree,
3945                and the height of the cone.
3946            compression : (bool)
3947                put children closer together, possibly allowing sub-trees to overlap.
3948                This is useful if the tree is actually the spanning tree of a graph.
3949            spacing : (float)
3950                space between layers of the tree
3951
3952        ---------------------------------------------------------------
3953        .. note:: Options for layout 'force'
3954
3955        Arguments:
3956            seed : (int)
3957                seed the random number generator used to jitter point positions
3958            bounds : (list)
3959                set the region in space in which to place the final graph
3960            nmax : (int)
3961                the maximum number of iterations to be used
3962            three_dimensional : (bool)
3963                allow optimization in the 3rd dimension too
3964            random_initial_points : (bool)
3965                use random positions within the graph bounds as initial points
3966
3967        Examples:
3968            - [lineage_graph.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/lineage_graph.py)
3969
3970                ![](https://vedo.embl.es/images/pyplot/graph_lineage.png)
3971
3972            - [graph_network.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/graph_network.py)
3973
3974                ![](https://vedo.embl.es/images/pyplot/graph_network.png)
3975        """
3976
3977        super().__init__()
3978
3979        self.nodes = []
3980        self.edges = []
3981
3982        self._node_labels = []  # holds strings
3983        self._edge_labels = []
3984        self.edge_orientations = []
3985        self.edge_glyph_position = 0.6
3986
3987        self.zrange = 0.0
3988
3989        self.rotX = 0
3990        self.rotY = 0
3991        self.rotZ = 0
3992
3993        self.arrow_scale = 0.15
3994        self.node_label_scale = None
3995        self.node_label_justify = "bottom-left"
3996
3997        self.edge_label_scale = None
3998
3999        self.mdg = vtki.new("MutableDirectedGraph")
4000
4001        n = kargs.pop("n", 0)
4002        for _ in range(n):
4003            self.add_node()
4004
4005        self._c = kargs.pop("c", (0.3, 0.3, 0.3))
4006
4007        self.gl = vtki.new("GraphLayout")
4008
4009        self.font = kargs.pop("font", "")
4010
4011        s = kargs.pop("layout", "2d")
4012        if isinstance(s, int):
4013            ss = ["2d", "fast2d", "clustering2d", "circular", "circular3d", "cone", "force", "tree"]
4014            s = ss[s]
4015        self.layout = s
4016
4017        if "2d" in s:
4018            if "clustering" in s:
4019                self.strategy = vtki.new("Clustering2DLayoutStrategy")
4020            elif "fast" in s:
4021                self.strategy = vtki.new("Fast2DLayoutStrategy")
4022            else:
4023                self.strategy = vtki.new("Simple2DLayoutStrategy")
4024            self.rotX = 180
4025            opt = kargs.pop("rest_distance", None)
4026            if opt is not None:
4027                self.strategy.SetRestDistance(opt)
4028            opt = kargs.pop("seed", None)
4029            if opt is not None:
4030                self.strategy.SetRandomSeed(opt)
4031            opt = kargs.pop("nmax", None)
4032            if opt is not None:
4033                self.strategy.SetMaxNumberOfIterations(opt)
4034            self.zrange = kargs.pop("zrange", 0)
4035
4036        elif "circ" in s:
4037            if "3d" in s:
4038                self.strategy = vtki.new("Simple3DCirclesStrategy")
4039                self.strategy.SetDirection(0, 0, -1)
4040                self.strategy.SetAutoHeight(True)
4041                self.strategy.SetMethod(1)
4042                self.rotX = -90
4043                opt = kargs.pop("radius", None)  # float
4044                if opt is not None:
4045                    self.strategy.SetMethod(0)
4046                    self.strategy.SetRadius(opt)  # float
4047                opt = kargs.pop("height", None)
4048                if opt is not None:
4049                    self.strategy.SetAutoHeight(False)
4050                    self.strategy.SetHeight(opt)  # float
4051            else:
4052                self.strategy = vtki.new("CircularLayoutStrategy")
4053                self.zrange = kargs.pop("zrange", 0)
4054
4055        elif "cone" in s:
4056            self.strategy = vtki.new("ConeLayoutStrategy")
4057            self.rotX = 180
4058            opt = kargs.pop("compactness", None)
4059            if opt is not None:
4060                self.strategy.SetCompactness(opt)
4061            opt = kargs.pop("compression", None)
4062            if opt is not None:
4063                self.strategy.SetCompression(opt)
4064            opt = kargs.pop("spacing", None)
4065            if opt is not None:
4066                self.strategy.SetSpacing(opt)
4067
4068        elif "force" in s:
4069            self.strategy = vtki.new("ForceDirectedLayoutStrategy")
4070            opt = kargs.pop("seed", None)
4071            if opt is not None:
4072                self.strategy.SetRandomSeed(opt)
4073            opt = kargs.pop("bounds", None)
4074            if opt is not None:
4075                self.strategy.SetAutomaticBoundsComputation(False)
4076                self.strategy.SetGraphBounds(opt)  # list
4077            opt = kargs.pop("nmax", None)
4078            if opt is not None:
4079                self.strategy.SetMaxNumberOfIterations(opt)  # int
4080            opt = kargs.pop("three_dimensional", True)
4081            if opt is not None:
4082                self.strategy.SetThreeDimensionalLayout(opt)  # bool
4083            opt = kargs.pop("random_initial_points", None)
4084            if opt is not None:
4085                self.strategy.SetRandomInitialPoints(opt)  # bool
4086
4087        elif "tree" in s:
4088            self.strategy = vtki.new("SpanTreeLayoutStrategy")
4089            self.rotX = 180
4090
4091        else:
4092            vedo.logger.error(f"Cannot understand layout {s}. Available layouts:")
4093            vedo.logger.error("[2d,fast2d,clustering2d,circular,circular3d,cone,force,tree]")
4094            raise RuntimeError()
4095
4096        self.gl.SetLayoutStrategy(self.strategy)
4097
4098        if len(kargs) > 0:
4099            vedo.logger.error(f"Cannot understand options: {kargs}")
4100
4101    def add_node(self, label="id") -> int:
4102        """Add a new node to the `Graph`."""
4103        v = self.mdg.AddVertex()  # vtk calls it vertex..
4104        self.nodes.append(v)
4105        if label == "id":
4106            label = int(v)
4107        self._node_labels.append(str(label))
4108        return v
4109
4110    def add_edge(self, v1, v2, label="") -> int:
4111        """Add a new edge between to nodes.
4112        An extra node is created automatically if needed."""
4113        nv = len(self.nodes)
4114        if v1 >= nv:
4115            for _ in range(nv, v1 + 1):
4116                self.add_node()
4117        nv = len(self.nodes)
4118        if v2 >= nv:
4119            for _ in range(nv, v2 + 1):
4120                self.add_node()
4121        e = self.mdg.AddEdge(v1, v2)
4122        self.edges.append(e)
4123        self._edge_labels.append(str(label))
4124        return e
4125
4126    def add_child(self, v, node_label="id", edge_label="") -> int:
4127        """Add a new edge to a new node as its child.
4128        The extra node is created automatically if needed."""
4129        nv = len(self.nodes)
4130        if v >= nv:
4131            for _ in range(nv, v + 1):
4132                self.add_node()
4133        child = self.mdg.AddChild(v)
4134        self.edges.append((v, child))
4135        self.nodes.append(child)
4136        if node_label == "id":
4137            node_label = int(child)
4138        self._node_labels.append(str(node_label))
4139        self._edge_labels.append(str(edge_label))
4140        return child
4141
4142    def build(self):
4143        """
4144        Build the `DirectedGraph(Assembly)`.
4145        Accessory objects are also created for labels and arrows.
4146        """
4147        self.gl.SetZRange(self.zrange)
4148        self.gl.SetInputData(self.mdg)
4149        self.gl.Update()
4150
4151        gr2poly = vtki.new("GraphToPolyData")
4152        gr2poly.EdgeGlyphOutputOn()
4153        gr2poly.SetEdgeGlyphPosition(self.edge_glyph_position)
4154        gr2poly.SetInputData(self.gl.GetOutput())
4155        gr2poly.Update()
4156
4157        dgraph = Mesh(gr2poly.GetOutput(0))
4158        # dgraph.clean() # WRONG!!! dont uncomment
4159        dgraph.flat().color(self._c).lw(2)
4160        dgraph.name = "DirectedGraph"
4161
4162        diagsz = self.diagonal_size() / 1.42
4163        if not diagsz:
4164            return None
4165
4166        dgraph.scale(1 / diagsz)
4167        if self.rotX:
4168            dgraph.rotate_x(self.rotX)
4169        if self.rotY:
4170            dgraph.rotate_y(self.rotY)
4171        if self.rotZ:
4172            dgraph.rotate_z(self.rotZ)
4173
4174        vecs = gr2poly.GetOutput(1).GetPointData().GetVectors()
4175        self.edge_orientations = utils.vtk2numpy(vecs)
4176
4177        # Use Glyph3D to repeat the glyph on all edges.
4178        arrows = None
4179        if self.arrow_scale:
4180            arrow_source = vtki.new("GlyphSource2D")
4181            arrow_source.SetGlyphTypeToEdgeArrow()
4182            arrow_source.SetScale(self.arrow_scale)
4183            arrow_source.Update()
4184            arrow_glyph = vtki.vtkGlyph3D()
4185            arrow_glyph.SetInputData(0, gr2poly.GetOutput(1))
4186            arrow_glyph.SetInputData(1, arrow_source.GetOutput())
4187            arrow_glyph.Update()
4188            arrows = Mesh(arrow_glyph.GetOutput())
4189            arrows.scale(1 / diagsz)
4190            arrows.lighting("off").color(self._c)
4191            if self.rotX:
4192                arrows.rotate_x(self.rotX)
4193            if self.rotY:
4194                arrows.rotate_y(self.rotY)
4195            if self.rotZ:
4196                arrows.rotate_z(self.rotZ)
4197            arrows.name = "DirectedGraphArrows"
4198
4199        node_labels = None
4200        if self._node_labels:
4201            node_labels = dgraph.labels(
4202                self._node_labels,
4203                scale=self.node_label_scale,
4204                precision=0,
4205                font=self.font,
4206                justify=self.node_label_justify,
4207            )
4208            node_labels.color(self._c).pickable(True)
4209            node_labels.name = "DirectedGraphNodeLabels"
4210
4211        edge_labels = None
4212        if self._edge_labels:
4213            edge_labels = dgraph.labels(
4214                self._edge_labels, on="cells", scale=self.edge_label_scale, precision=0, font=self.font
4215            )
4216            edge_labels.color(self._c).pickable(True)
4217            edge_labels.name = "DirectedGraphEdgeLabels"
4218
4219        super().__init__([dgraph, node_labels, edge_labels, arrows])
4220        self.name = "DirectedGraphAssembly"
4221        return self

Support for Directed Graphs.

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

Add a new node to the Graph.

def add_edge(self, v1, v2, label='') -> int:
4110    def add_edge(self, v1, v2, label="") -> int:
4111        """Add a new edge between to nodes.
4112        An extra node is created automatically if needed."""
4113        nv = len(self.nodes)
4114        if v1 >= nv:
4115            for _ in range(nv, v1 + 1):
4116                self.add_node()
4117        nv = len(self.nodes)
4118        if v2 >= nv:
4119            for _ in range(nv, v2 + 1):
4120                self.add_node()
4121        e = self.mdg.AddEdge(v1, v2)
4122        self.edges.append(e)
4123        self._edge_labels.append(str(label))
4124        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:
4126    def add_child(self, v, node_label="id", edge_label="") -> int:
4127        """Add a new edge to a new node as its child.
4128        The extra node is created automatically if needed."""
4129        nv = len(self.nodes)
4130        if v >= nv:
4131            for _ in range(nv, v + 1):
4132                self.add_node()
4133        child = self.mdg.AddChild(v)
4134        self.edges.append((v, child))
4135        self.nodes.append(child)
4136        if node_label == "id":
4137            node_label = int(child)
4138        self._node_labels.append(str(node_label))
4139        self._edge_labels.append(str(edge_label))
4140        return child

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

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

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