Skip to content

vedo.pyplot

pyplot

figure

Figure

Bases: Assembly

Format class for figures.

Source code in vedo/pyplot/figure.py
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
class Figure(Assembly):
    """Format class for figures."""

    def __init__(
        self, xlim, ylim, aspect=1.333, padding=(0.05, 0.05, 0.05, 0.05), **kwargs
    ):
        """
        Create an empty formatted figure for plotting.

        Args:
            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
        """

        self.verbose = True  # printing to stdout on every mouse click

        self.xlim = np.asarray(xlim)
        self.ylim = np.asarray(ylim)
        self.aspect = aspect
        self.padding = padding
        if not utils.is_sequence(self.padding):
            self.padding = [self.padding, self.padding, self.padding, self.padding]

        self.force_scaling_types = (
            shapes.Glyph,
            shapes.Line,
            shapes.Rectangle,
            shapes.DashedLine,
            shapes.Tube,
            shapes.Ribbon,
            shapes.GeoCircle,
            shapes.Arc,
            shapes.Grid,
            # shapes.Arrows, # todo
            # shapes.Arrows2D, # todo
            shapes.Brace,  # todo
        )

        options = dict(kwargs)

        self.title = options.pop("title", "")
        self.xtitle = options.pop("xtitle", " ")
        self.ytitle = options.pop("ytitle", " ")
        number_of_divisions = 6

        self.legend = None
        self.labels = []
        self.label = options.pop("label", None)
        if self.label:
            self.labels = [self.label]

        self.axopts = options.pop("axes", {})
        if isinstance(self.axopts, (bool, int, float)):
            if self.axopts:
                self.axopts = {}
        if self.axopts or isinstance(self.axopts, dict):
            number_of_divisions = self.axopts.pop(
                "number_of_divisions", number_of_divisions
            )

            self.axopts["xtitle"] = self.xtitle
            self.axopts["ytitle"] = self.ytitle

            if "xygrid" not in self.axopts:  ## modify the default
                self.axopts["xygrid"] = options.pop("grid", False)

            if "xygrid_transparent" not in self.axopts:  ## modify the default
                self.axopts["xygrid_transparent"] = True

            if "xtitle_position" not in self.axopts:  ## modify the default
                self.axopts["xtitle_position"] = 0.5
                self.axopts["xtitle_justify"] = "top-center"

            if "ytitle_position" not in self.axopts:  ## modify the default
                self.axopts["ytitle_position"] = 0.5
                self.axopts["ytitle_justify"] = "bottom-center"

            if self.label:
                if "c" in self.axopts:
                    self.label.tcolor = self.axopts["c"]

        x0, x1 = self.xlim
        y0, y1 = self.ylim
        dx = x1 - x0
        dy = y1 - y0
        x0lim, x1lim = (x0 - self.padding[0] * dx, x1 + self.padding[1] * dx)
        y0lim, y1lim = (y0 - self.padding[2] * dy, y1 + self.padding[3] * dy)
        dy = y1lim - y0lim

        self.axes = None
        if xlim[0] >= xlim[1] or ylim[0] >= ylim[1]:
            vedo.logger.warning(
                f"Null range for Figure {self.title}... returning an empty Assembly."
            )
            super().__init__()
            self.yscale = 0
            return

        if aspect == "equal":
            self.aspect = dx / dy  # so that yscale becomes 1

        self.yscale = dx / dy / self.aspect

        y0lim *= self.yscale
        y1lim *= self.yscale

        self.x0lim = x0lim
        self.x1lim = x1lim
        self.y0lim = y0lim
        self.y1lim = y1lim

        self.ztolerance = options.pop("ztolerance", None)
        if self.ztolerance is None:
            self.ztolerance = dx / 5000

        ############## create axes
        if self.axopts:
            axes_opts = self.axopts
            if self.axopts is True or self.axopts == 1:
                axes_opts = {}

            tp, ts = utils.make_ticks(
                y0lim / self.yscale, y1lim / self.yscale, number_of_divisions
            )
            labs = []
            for i in range(1, len(tp) - 1):
                ynew = utils.lin_interpolate(tp[i], [0, 1], [y0lim, y1lim])
                labs.append([ynew, ts[i]])

            if self.title:
                axes_opts["htitle"] = self.title
            axes_opts["y_values_and_labels"] = labs
            axes_opts["xrange"] = (x0lim, x1lim)
            axes_opts["yrange"] = (y0lim, y1lim)
            axes_opts["zrange"] = (0, 0)
            axes_opts["y_use_bounds"] = True

            if "c" not in axes_opts and "ac" in options:
                axes_opts["c"] = options["ac"]

            self.axes = addons.Axes(**axes_opts)

        super().__init__([self.axes])
        self.name = "Figure"

        vedo.set_last_figure(self if settings.remember_last_figure_format else None)

    ##################################################################
    def _repr_html_(self):
        """
        HTML representation of the Figure object for Jupyter Notebooks.

        Returns:
            HTML text with the image and some properties.
        """
        import io
        import base64
        from PIL import Image

        library_name = "vedo.pyplot.Figure"
        help_url = "https://vedo.embl.es/docs/vedo/pyplot.html#Figure"

        arr = self.thumbnail(zoom=1.1)

        im = Image.fromarray(arr)
        buffered = io.BytesIO()
        im.save(buffered, format="PNG", quality=100)
        encoded = base64.b64encode(buffered.getvalue()).decode("utf-8")
        url = "data:image/png;base64," + encoded
        image = f"<img src='{url}'></img>"

        bounds = "<br/>".join(
            [
                vedo.utils.precision(min_x, 4)
                + " ... "
                + vedo.utils.precision(max_x, 4)
                for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2])
            ]
        )

        help_text = ""
        if self.name:
            help_text += f"<b> {self.name}: &nbsp&nbsp</b>"
        help_text += (
            '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>"
        )
        if self.filename:
            dots = ""
            if len(self.filename) > 30:
                dots = "..."
            help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>"

        _all = [
            "<table>",
            "<tr>",
            "<td>",
            image,
            "</td>",
            "<td style='text-align: center; vertical-align: center;'><br/>",
            help_text,
            "<table>",
            "<tr><td><b> nr. of parts </b></td><td>"
            + str(self.actor.GetNumberOfPaths())
            + "</td></tr>",
            "<tr><td><b> position </b></td><td>"
            + str(self.actor.GetPosition())
            + "</td></tr>",
            "<tr><td><b> x-limits </b></td><td>"
            + utils.precision(self.xlim, 4)
            + "</td></tr>",
            "<tr><td><b> y-limits </b></td><td>"
            + utils.precision(self.ylim, 4)
            + "</td></tr>",
            "<tr><td><b> world bounds </b> <br/> (x/y/z) </td><td>"
            + str(bounds)
            + "</td></tr>",
            "</table>",
            "</table>",
        ]
        return "\n".join(_all)

    def __add__(self, *obj):
        # just to avoid confusion, supersede Assembly.__add__
        return self.__iadd__(*obj)

    def __iadd__(self, *obj):
        if len(obj) == 1 and isinstance(obj[0], Figure):
            return self._check_unpack_and_insert(obj[0])

        obj = utils.flatten(obj)
        return self.insert(*obj)

    def _check_unpack_and_insert(self, fig: Figure) -> Self:

        if fig.label:
            self.labels.append(fig.label)

        if abs(self.yscale - fig.yscale) > 0.0001:
            vedo.logger.error(
                "Adding incompatible Figure: Y-scales are different."
            )
            vedo.logger.error(f"First figure yscale: {self.yscale}")
            vedo.logger.error(f"Second figure yscale: {fig.yscale}")

            vedo.logger.error("One or more of these parameters can be the cause:")
            if list(self.xlim) != list(fig.xlim):
                vedo.logger.error(
                    f"xlim mismatch: first figure {self.xlim}, second figure {fig.xlim}"
                )
            if list(self.ylim) != list(fig.ylim):
                vedo.logger.error(
                    f"ylim mismatch: first figure {self.ylim}, second figure {fig.ylim}"
                )
            if list(self.padding) != list(fig.padding):
                vedo.logger.error(
                    "padding mismatch: "
                    f"first figure {self.padding}, second figure {fig.padding}"
                )
            if self.aspect != fig.aspect:
                vedo.logger.error(
                    f"aspect mismatch: first figure {self.aspect}, second figure {fig.aspect}"
                )

            vedo.logger.info(
                "Consider using fig2 = histogram(..., like=fig1)"
            )
            vedo.logger.info("Or fig += histogram(..., like=fig)")
            return self

        offset = self.zbounds()[1] + self.ztolerance

        for ele in fig.unpack():
            if "Axes" in ele.name:
                continue
            ele.z(offset)
            self.insert(ele, rescale=False)

        return self

    def insert(self, *objs, rescale=True, as3d=True, adjusted=False, cut=True) -> 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.

        Args:
            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.
        """
        for a in objs:
            if a in self.objects:
                # should not add twice the same object in plot
                continue

            if isinstance(a, vedo.Points):  # hacky way to identify Points
                if a.ncells == a.npoints:
                    poly = a.dataset
                    if poly.GetNumberOfPolys() == 0 and poly.GetNumberOfLines() == 0:
                        as3d = False
                        rescale = True

            if isinstance(a, (shapes.Arrow, shapes.Arrow2D)):
                # discard input Arrow and substitute it with a brand new one
                # (because scaling would fatally distort the shape)

                py = a.base[1]
                a.top[1] = (a.top[1] - py) * self.yscale + py
                b = shapes.Arrow2D(a.base, a.top, s=a.s, fill=a.fill).z(a.z())

                prop = a.properties
                prop.LightingOff()
                b.actor.SetProperty(prop)
                b.properties = prop
                b.y(py * self.yscale)
                a = b

            # elif isinstance(a, shapes.Rectangle) and a.radius is not None:
            #     # discard input Rectangle and substitute it with a brand new one
            #     # (because scaling would fatally distort the shape of the corners)
            #     py = a.corner1[1]
            #     rx1,ry1,rz1 = a.corner1
            #     rx2,ry2,rz2 = a.corner2
            #     ry2 = (ry2-py) * self.yscale + py
            #     b = shapes.Rectangle([rx1,0,rz1], [rx2,ry2,rz2], radius=a.radius).z(a.z())
            #     b.SetProperty(a.properties)
            #     b.y(py / self.yscale)
            #     a = b

            else:
                if rescale:
                    if not isinstance(a, Figure):
                        if as3d and not isinstance(a, self.force_scaling_types):
                            if adjusted:
                                scl = np.min([1, self.yscale])
                            else:
                                scl = self.yscale

                            a.scale(scl)

                        else:
                            a.scale([1, self.yscale, 1])

                    # shift it in y
                    a.y(a.y() * self.yscale)

            if cut:
                try:
                    bx0, bx1, by0, by1, _, _ = a.bounds()
                    if self.y0lim > by0:
                        a.cut_with_plane([0, self.y0lim, 0], [0, 1, 0])
                    if self.y1lim < by1:
                        a.cut_with_plane([0, self.y1lim, 0], [0, -1, 0])
                    if self.x0lim > bx0:
                        a.cut_with_plane([self.x0lim, 0, 0], [1, 0, 0])
                    if self.x1lim < bx1:
                        a.cut_with_plane([self.x1lim, 0, 0], [-1, 0, 0])
                except:
                    # print("insert(): cannot cut", [a])
                    pass

            self.actor.AddPart(a.actor)
            self.objects.append(a)

        return self

    def add_label(self, text: str, c=None, marker="", mc="black") -> Self:
        """
        Manually add en entry label to the legend.

        Args:
            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
        """
        newlabel = LabelData()
        newlabel.text = text.replace("\n", " ")
        newlabel.tcolor = c
        newlabel.marker = marker
        newlabel.mcolor = mc
        self.labels.append(newlabel)
        return self

    def add_legend(
        self,
        pos="top-right",
        relative=True,
        font=None,
        s=1,
        c=None,
        vspace=1.75,
        padding=0.1,
        radius=0,
        alpha=0.8,
        bc="k7",
        lw=1,
        lc="k4",
        z=0,
    ) -> Self:
        """
        Add existing labels to form a legend box.
        Labels have been previously filled with eg: `plot(..., label="text")`

        Args:
            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](https://vedo.embl.es/fonts).
            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)
        """
        sx = self.x1lim - self.x0lim
        s = s * sx / 55  # so that input can be about 1

        ds = 0
        texts = []
        mks = []
        for i, t in enumerate(self.labels):
            label = self.labels[i]
            t = label.text

            if label.tcolor is not None:
                c = label.tcolor

            tx = vedo.shapes.Text3D(t, s=s, c=c, justify="center-left", font=font)
            y0, y1 = tx.ybounds()
            ds = max(y1 - y0, ds)
            texts.append(tx)

            mk = label.marker
            if isinstance(mk, vedo.Points):
                mk = mk.clone(deep=False).lighting("off")
                cm = mk.center_of_mass()
                ty0, ty1 = tx.ybounds()
                oby0, oby1 = mk.ybounds()
                mk.shift(-cm)
                mk.SetOrigin(cm)
                mk.scale((ty1 - ty0) / (oby1 - oby0))
                mk.scale([1.1, 1.1, 0.01])
            elif mk == "-":
                mk = vedo.shapes.Marker(mk, s=s * 2)
                mk.color(label.mcolor)
            else:
                mk = vedo.shapes.Marker(mk, s=s)
                mk.color(label.mcolor)
            mks.append(mk)

        for i, tx in enumerate(texts):
            tx.shift(0, -(i + 0) * ds * vspace)

        for i, mk in enumerate(mks):
            mk.shift(-ds * 1.75, -(i + 0) * ds * vspace, 0)

        acts = texts + mks

        aleg = Assembly(acts)  # .show(axes=1).close()
        x0, x1, y0, y1, _, _ = aleg.actor.GetBounds()

        if alpha:
            dx = x1 - x0
            dy = y1 - y0

            if not utils.is_sequence(padding):
                padding = [padding] * 4
            padding = min(padding)
            padding = min(padding * dx, padding * dy)
            if len(self.labels) == 1:
                padding *= 4
            x0 -= padding
            x1 += padding
            y0 -= padding
            y1 += padding

            box = shapes.Rectangle([x0, y0], [x1, y1], radius=radius, c=bc, alpha=alpha)
            box.shift(0, 0, -dy / 100).pickable(False)
            if lc:
                box.lc(lc).lw(lw)
            aleg.actor.AddPart(box.actor)
            aleg.objects.append(box)

        xlim = self.xlim
        ylim = self.ylim
        if isinstance(pos, str):
            px, py = 0.0, 0.0
            rx, ry = (xlim[1] + xlim[0]) / 2, (ylim[1] + ylim[0]) / 2
            shx, shy = 0.0, 0.0
            if "top" in pos:
                if "cent" in pos:
                    px, py = rx, ylim[1]
                    shx, shy = (x0 + x1) / 2, y1
                elif "left" in pos:
                    px, py = xlim[0], ylim[1]
                    shx, shy = x0, y1
                else:  # "right"
                    px, py = xlim[1], ylim[1]
                    shx, shy = x1, y1
            elif "bot" in pos:
                if "left" in pos:
                    px, py = xlim[0], ylim[0]
                    shx, shy = x0, y0
                elif "right" in pos:
                    px, py = xlim[1], ylim[0]
                    shx, shy = x1, y0
                else:  # "cent"
                    px, py = rx, ylim[0]
                    shx, shy = (x0 + x1) / 2, y0
            elif "cent" in pos:
                if "left" in pos:
                    px, py = xlim[0], ry
                    shx, shy = x0, (y0 + y1) / 2
                elif "right" in pos:
                    px, py = xlim[1], ry
                    shx, shy = x1, (y0 + y1) / 2
            else:
                vedo.logger.error(f"in add_legend(), cannot understand {pos}")
                raise RuntimeError

        else:
            if relative:
                rx, ry = pos[0], pos[1]
                px = (xlim[1] - xlim[0]) * rx + xlim[0]
                py = (ylim[1] - ylim[0]) * ry + ylim[0]
                z *= xlim[1] - xlim[0]
            else:
                px, py = pos[0], pos[1]
            shx, shy = x0, y1

        zpos = aleg.pos()[2]
        aleg.pos(px - shx, py * self.yscale - shy, zpos + sx / 50 + z)

        self.insert(aleg, rescale=False, cut=False)
        self.legend = aleg
        aleg.name = "Legend"
        return self

add_label(text, c=None, marker='', mc='black')

Manually add en entry label to the legend.

Parameters:

Name Type Description Default
text str

text string for the label.

required
c str

color of the text

None
marker

(str), Mesh a marker char or a Mesh object to be used as marker

required
mc str

color for the marker

'black'
Source code in vedo/pyplot/figure.py
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
def add_label(self, text: str, c=None, marker="", mc="black") -> Self:
    """
    Manually add en entry label to the legend.

    Args:
        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
    """
    newlabel = LabelData()
    newlabel.text = text.replace("\n", " ")
    newlabel.tcolor = c
    newlabel.marker = marker
    newlabel.mcolor = mc
    self.labels.append(newlabel)
    return self

add_legend(pos='top-right', relative=True, font=None, s=1, c=None, vspace=1.75, padding=0.1, radius=0, alpha=0.8, bc='k7', lw=1, lc='k4', z=0)

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

Parameters:

Name Type Description Default
pos (str, list)

A string or 2D coordinates. The default is "top-right".

'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.

True
font (str, int)

font name or number. Check available fonts here.

None
s float

global size of the legend

1
c str

color of the text

None
vspace float

vertical spacing of lines

1.75
padding float

padding of the box as a fraction of the text size

0.1
radius float

border radius of the box

0
alpha float

opacity of the box. Values below 1 may cause poor rendering because of antialiasing. Use alpha = 0 to remove the box.

0.8
bc str

box color

'k7'
lw int

border line width of the box in pixel units

1
lc int

border line color of the box

'k4'
z float

set the zorder as z position (useful to avoid overlap)

0
Source code in vedo/pyplot/figure.py
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
def add_legend(
    self,
    pos="top-right",
    relative=True,
    font=None,
    s=1,
    c=None,
    vspace=1.75,
    padding=0.1,
    radius=0,
    alpha=0.8,
    bc="k7",
    lw=1,
    lc="k4",
    z=0,
) -> Self:
    """
    Add existing labels to form a legend box.
    Labels have been previously filled with eg: `plot(..., label="text")`

    Args:
        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](https://vedo.embl.es/fonts).
        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)
    """
    sx = self.x1lim - self.x0lim
    s = s * sx / 55  # so that input can be about 1

    ds = 0
    texts = []
    mks = []
    for i, t in enumerate(self.labels):
        label = self.labels[i]
        t = label.text

        if label.tcolor is not None:
            c = label.tcolor

        tx = vedo.shapes.Text3D(t, s=s, c=c, justify="center-left", font=font)
        y0, y1 = tx.ybounds()
        ds = max(y1 - y0, ds)
        texts.append(tx)

        mk = label.marker
        if isinstance(mk, vedo.Points):
            mk = mk.clone(deep=False).lighting("off")
            cm = mk.center_of_mass()
            ty0, ty1 = tx.ybounds()
            oby0, oby1 = mk.ybounds()
            mk.shift(-cm)
            mk.SetOrigin(cm)
            mk.scale((ty1 - ty0) / (oby1 - oby0))
            mk.scale([1.1, 1.1, 0.01])
        elif mk == "-":
            mk = vedo.shapes.Marker(mk, s=s * 2)
            mk.color(label.mcolor)
        else:
            mk = vedo.shapes.Marker(mk, s=s)
            mk.color(label.mcolor)
        mks.append(mk)

    for i, tx in enumerate(texts):
        tx.shift(0, -(i + 0) * ds * vspace)

    for i, mk in enumerate(mks):
        mk.shift(-ds * 1.75, -(i + 0) * ds * vspace, 0)

    acts = texts + mks

    aleg = Assembly(acts)  # .show(axes=1).close()
    x0, x1, y0, y1, _, _ = aleg.actor.GetBounds()

    if alpha:
        dx = x1 - x0
        dy = y1 - y0

        if not utils.is_sequence(padding):
            padding = [padding] * 4
        padding = min(padding)
        padding = min(padding * dx, padding * dy)
        if len(self.labels) == 1:
            padding *= 4
        x0 -= padding
        x1 += padding
        y0 -= padding
        y1 += padding

        box = shapes.Rectangle([x0, y0], [x1, y1], radius=radius, c=bc, alpha=alpha)
        box.shift(0, 0, -dy / 100).pickable(False)
        if lc:
            box.lc(lc).lw(lw)
        aleg.actor.AddPart(box.actor)
        aleg.objects.append(box)

    xlim = self.xlim
    ylim = self.ylim
    if isinstance(pos, str):
        px, py = 0.0, 0.0
        rx, ry = (xlim[1] + xlim[0]) / 2, (ylim[1] + ylim[0]) / 2
        shx, shy = 0.0, 0.0
        if "top" in pos:
            if "cent" in pos:
                px, py = rx, ylim[1]
                shx, shy = (x0 + x1) / 2, y1
            elif "left" in pos:
                px, py = xlim[0], ylim[1]
                shx, shy = x0, y1
            else:  # "right"
                px, py = xlim[1], ylim[1]
                shx, shy = x1, y1
        elif "bot" in pos:
            if "left" in pos:
                px, py = xlim[0], ylim[0]
                shx, shy = x0, y0
            elif "right" in pos:
                px, py = xlim[1], ylim[0]
                shx, shy = x1, y0
            else:  # "cent"
                px, py = rx, ylim[0]
                shx, shy = (x0 + x1) / 2, y0
        elif "cent" in pos:
            if "left" in pos:
                px, py = xlim[0], ry
                shx, shy = x0, (y0 + y1) / 2
            elif "right" in pos:
                px, py = xlim[1], ry
                shx, shy = x1, (y0 + y1) / 2
        else:
            vedo.logger.error(f"in add_legend(), cannot understand {pos}")
            raise RuntimeError

    else:
        if relative:
            rx, ry = pos[0], pos[1]
            px = (xlim[1] - xlim[0]) * rx + xlim[0]
            py = (ylim[1] - ylim[0]) * ry + ylim[0]
            z *= xlim[1] - xlim[0]
        else:
            px, py = pos[0], pos[1]
        shx, shy = x0, y1

    zpos = aleg.pos()[2]
    aleg.pos(px - shx, py * self.yscale - shy, zpos + sx / 50 + z)

    self.insert(aleg, rescale=False, cut=False)
    self.legend = aleg
    aleg.name = "Legend"
    return self

insert(*objs, rescale=True, as3d=True, adjusted=False, cut=True)

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.

Parameters:

Name Type Description Default
rescale bool

rescale the y axis position while inserting the object.

True
as3d bool

if True keep the aspect ratio of the 3d object, otherwise stretch it in y.

True
adjusted bool

adjust the scaling according to the shortest axis

False
cut bool

cut off the parts of the object which go beyond the axes frame.

True
Source code in vedo/pyplot/figure.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
def insert(self, *objs, rescale=True, as3d=True, adjusted=False, cut=True) -> 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.

    Args:
        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.
    """
    for a in objs:
        if a in self.objects:
            # should not add twice the same object in plot
            continue

        if isinstance(a, vedo.Points):  # hacky way to identify Points
            if a.ncells == a.npoints:
                poly = a.dataset
                if poly.GetNumberOfPolys() == 0 and poly.GetNumberOfLines() == 0:
                    as3d = False
                    rescale = True

        if isinstance(a, (shapes.Arrow, shapes.Arrow2D)):
            # discard input Arrow and substitute it with a brand new one
            # (because scaling would fatally distort the shape)

            py = a.base[1]
            a.top[1] = (a.top[1] - py) * self.yscale + py
            b = shapes.Arrow2D(a.base, a.top, s=a.s, fill=a.fill).z(a.z())

            prop = a.properties
            prop.LightingOff()
            b.actor.SetProperty(prop)
            b.properties = prop
            b.y(py * self.yscale)
            a = b

        # elif isinstance(a, shapes.Rectangle) and a.radius is not None:
        #     # discard input Rectangle and substitute it with a brand new one
        #     # (because scaling would fatally distort the shape of the corners)
        #     py = a.corner1[1]
        #     rx1,ry1,rz1 = a.corner1
        #     rx2,ry2,rz2 = a.corner2
        #     ry2 = (ry2-py) * self.yscale + py
        #     b = shapes.Rectangle([rx1,0,rz1], [rx2,ry2,rz2], radius=a.radius).z(a.z())
        #     b.SetProperty(a.properties)
        #     b.y(py / self.yscale)
        #     a = b

        else:
            if rescale:
                if not isinstance(a, Figure):
                    if as3d and not isinstance(a, self.force_scaling_types):
                        if adjusted:
                            scl = np.min([1, self.yscale])
                        else:
                            scl = self.yscale

                        a.scale(scl)

                    else:
                        a.scale([1, self.yscale, 1])

                # shift it in y
                a.y(a.y() * self.yscale)

        if cut:
            try:
                bx0, bx1, by0, by1, _, _ = a.bounds()
                if self.y0lim > by0:
                    a.cut_with_plane([0, self.y0lim, 0], [0, 1, 0])
                if self.y1lim < by1:
                    a.cut_with_plane([0, self.y1lim, 0], [0, -1, 0])
                if self.x0lim > bx0:
                    a.cut_with_plane([self.x0lim, 0, 0], [1, 0, 0])
                if self.x1lim < bx1:
                    a.cut_with_plane([self.x1lim, 0, 0], [-1, 0, 0])
            except:
                # print("insert(): cannot cut", [a])
                pass

        self.actor.AddPart(a.actor)
        self.objects.append(a)

    return self

LabelData

Helper internal class to hold label information.

Source code in vedo/pyplot/figure.py
26
27
28
29
30
31
32
33
34
class LabelData:
    """Helper internal class to hold label information."""

    def __init__(self):
        """Helper internal class to hold label information."""
        self.text = "dataset"
        self.tcolor = "black"
        self.marker = "s"
        self.mcolor = "black"

charts

Histogram1D

Bases: Figure

1D histogramming.

Source code in vedo/pyplot/charts.py
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
class Histogram1D(Figure):
    "1D histogramming."

    def __init__(
        self,
        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,
        # Figure and axes options:
        like=None,
        xlim=None,
        ylim=(0, None),
        aspect=1.333,
        padding=(0.0, 0.0, 0.0, 0.05),
        title="",
        xtitle=" ",
        ytitle=" ",
        ac="k",
        grid=False,
        ztolerance=None,
        label="",
        **fig_kwargs,
    ):
        """
        Creates a `Histogram1D(Figure)` object.

        Args:
            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:
            - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
            - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
            - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
            - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)

            ![](https://vedo.embl.es/images/pyplot/histo_1D.png)
        """

        if max_entries and data.shape[0] > max_entries:
            data = np.random.choice(data, int(max_entries))

        # purge NaN from data
        data = np.asarray(data).ravel()
        data = data[np.logical_not(np.isnan(data))]

        # if data.dtype is integer try to center bins by default
        if like is None and bins is None and np.issubdtype(data.dtype, np.integer):
            if xlim is None and ylim == (0, None):
                x1, x0 = data.max(), data.min()
                if 0 < x1 - x0 <= 100:
                    bins = x1 - x0 + 1
                    xlim = (x0 - 0.5, x1 + 0.5)

        if like is None and vedo.current_last_figure() is not None:
            if xlim is None and ylim == (0, None):
                like = vedo.current_last_figure()

        if like is not None:
            xlim = like.xlim
            ylim = like.ylim
            aspect = like.aspect
            padding = like.padding
            if bins is None:
                bins = like.bins
        if bins is None:
            bins = 20

        if utils.is_sequence(xlim):
            # deal with user passing eg [x0, None]
            _x0, _x1 = xlim
            if _x0 is None:
                _x0 = data.min()
            if _x1 is None:
                _x1 = data.max()
            xlim = [_x0, _x1]

        fs, edges = np.histogram(data, bins=bins, weights=weights, range=xlim)
        binsize = edges[1] - edges[0]
        ntot = data.shape[0]

        fig_kwargs["title"] = title
        fig_kwargs["xtitle"] = xtitle
        fig_kwargs["ytitle"] = ytitle
        fig_kwargs["ac"] = ac
        fig_kwargs["ztolerance"] = ztolerance
        fig_kwargs["grid"] = grid

        unscaled_errors = np.sqrt(fs)
        if density:
            scaled_errors = unscaled_errors / (ntot * binsize)
            fs = fs / (ntot * binsize)
            if ytitle == " ":
                ytitle = f"counts / ({ntot} x {utils.precision(binsize, 3)})"
                fig_kwargs["ytitle"] = ytitle
        elif logscale:
            se_up = np.log10(fs + unscaled_errors / 2 + 1)
            se_dw = np.log10(fs - unscaled_errors / 2 + 1)
            scaled_errors = np.c_[se_up, se_dw]
            fs = np.log10(fs + 1)
            if ytitle == " ":
                ytitle = "log_10 (counts+1)"
                fig_kwargs["ytitle"] = ytitle

        x0, x1 = np.min(edges), np.max(edges)
        y0, y1 = ylim[0], np.max(fs)

        _errors = []
        if errors:
            if density:
                y1 += max(scaled_errors) / 2
                _errors = scaled_errors
            elif logscale:
                y1 = max(scaled_errors[:, 0])
                _errors = scaled_errors
            else:
                y1 += max(unscaled_errors) / 2
                _errors = unscaled_errors

        if like is None:
            ylim = list(ylim)
            if xlim is None:
                xlim = [x0, x1]
            if ylim[1] is None:
                ylim[1] = y1
            if ylim[0] != 0:
                ylim[0] = y0

        self.title = title
        self.xtitle = xtitle
        self.ytitle = ytitle
        self.entries = ntot
        self.frequencies = fs
        self.errors = _errors
        self.edges = edges
        self.centers = (edges[0:-1] + edges[1:]) / 2
        self.mean = data.mean()
        self.mode = self.centers[np.argmax(fs)]
        self.std = data.std()
        self.bins = edges  # internally used by "like"

        ############################### stats legend as htitle
        addstats = False
        if not title:
            if "axes" not in fig_kwargs:
                addstats = True
                axes_opts = {}
                fig_kwargs["axes"] = axes_opts
            elif fig_kwargs["axes"] is False:
                pass
            else:
                axes_opts = fig_kwargs["axes"]
                if "htitle" not in axes_opts:
                    addstats = True

        if addstats:
            htitle = f"Entries:~~{int(self.entries)}  "
            htitle += f"Mean:~~{utils.precision(self.mean, 4)}  "
            htitle += f"STD:~~{utils.precision(self.std, 4)}  "

            axes_opts["htitle"] = htitle
            axes_opts["htitle_justify"] = "bottom-left"
            axes_opts["htitle_size"] = 0.016
            # axes_opts["htitle_offset"] = [-0.49, 0.01, 0]

        if mc is None:
            mc = lc
        if ma is None:
            ma = alpha

        if label:
            nlab = LabelData()
            nlab.text = label
            nlab.tcolor = ac
            nlab.marker = marker
            nlab.mcolor = mc
            if not marker:
                nlab.marker = "s"
                nlab.mcolor = c
            fig_kwargs["label"] = nlab

        ############################################### Figure init
        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)

        if not self.yscale:
            return

        if utils.is_sequence(bins):
            myedges = np.array(bins)
            bins = len(bins) - 1
        else:
            myedges = edges

        bin_centers = []
        for i in range(bins):
            x = (myedges[i] + myedges[i + 1]) / 2
            bin_centers.append([x, fs[i], 0])

        rs = []
        maxheigth = 0
        if not fill and not outline and not errors and not marker:
            outline = True  # otherwise it's empty..

        if fill:  #####################
            if outline:
                gap = 0

            for i in range(bins):
                F = fs[i]
                if not F:
                    continue
                p0 = (myedges[i] + gap * binsize, 0, 0)
                p1 = (myedges[i + 1] - gap * binsize, F, 0)

                if radius:
                    if gap:
                        rds = np.array([0, 0, radius, radius])
                    else:
                        rd1 = 0 if i < bins - 1 and fs[i + 1] >= F else radius / 2
                        rd2 = 0 if i > 0 and fs[i - 1] >= F else radius / 2
                        rds = np.array([0, 0, rd1, rd2])
                    p1_yscaled = [p1[0], p1[1] * self.yscale, 0]
                    r = shapes.Rectangle(p0, p1_yscaled, radius=rds * binsize, res=6)
                    r.scale([1, 1 / self.yscale, 1])
                    r.radius = (
                        None  # so it doesnt get recreated and rescaled by insert()
                    )
                else:
                    r = shapes.Rectangle(p0, p1)

                if texture:
                    r.texture(texture)
                    c = "w"

                r.actor.PickableOff()
                maxheigth = max(maxheigth, p1[1])
                if c in colors.cmaps_names:
                    col = colors.color_map(
                        (p0[0] + p1[0]) / 2, c, myedges[0], myedges[-1]
                    )
                else:
                    col = c
                r.color(col).alpha(alpha).lighting("off")
                r.z(self.ztolerance)
                rs.append(r)

        if outline:  #####################
            lns = [[myedges[0], 0, 0]]
            for i in range(bins):
                lns.append([myedges[i], fs[i], 0])
                lns.append([myedges[i + 1], fs[i], 0])
                maxheigth = max(maxheigth, fs[i])
            lns.append([myedges[-1], 0, 0])
            outl = shapes.Line(lns, c=lc, alpha=alpha, lw=lw)
            outl.z(self.ztolerance * 2)
            rs.append(outl)

        if errors:  #####################
            for i in range(bins):
                x = self.centers[i]
                f = fs[i]
                if not f:
                    continue
                err = _errors[i]
                if utils.is_sequence(err):
                    el = shapes.Line(
                        [x, err[0], 0], [x, err[1], 0], c=lc, alpha=alpha, lw=lw
                    )
                else:
                    el = shapes.Line(
                        [x, f - err / 2, 0],
                        [x, f + err / 2, 0],
                        c=lc,
                        alpha=alpha,
                        lw=lw,
                    )
                el.z(self.ztolerance * 3)
                rs.append(el)

        if marker:  #####################
            # remove empty bins (we dont want a marker there)
            bin_centers = np.array(bin_centers)
            bin_centers = bin_centers[bin_centers[:, 1] > 0]

            if utils.is_sequence(ms):  ### variable point size
                mk = shapes.Marker(marker, s=1)
                mk.scale([1, 1 / self.yscale, 1])
                msv = np.zeros_like(bin_centers)
                msv[:, 0] = ms
                marked = shapes.Glyph(
                    bin_centers,
                    mk,
                    c=mc,
                    orientation_array=msv,
                    scale_by_vector_size=True,
                )
            else:  ### fixed point size
                if ms is None:
                    ms = (xlim[1] - xlim[0]) / 100.0
                else:
                    ms = (xlim[1] - xlim[0]) / 100.0 * ms

                if utils.is_sequence(mc):
                    mk = shapes.Marker(marker, s=ms)
                    mk.scale([1, 1 / self.yscale, 1])
                    msv = np.zeros_like(bin_centers)
                    msv[:, 0] = 1
                    marked = shapes.Glyph(
                        bin_centers,
                        mk,
                        c=mc,
                        orientation_array=msv,
                        scale_by_vector_size=True,
                    )
                else:
                    mk = shapes.Marker(marker, s=ms)
                    mk.scale([1, 1 / self.yscale, 1])
                    marked = shapes.Glyph(bin_centers, mk, c=mc)

            marked.alpha(ma)
            marked.z(self.ztolerance * 4)
            rs.append(marked)

        self.insert(*rs, as3d=False)
        self.name = "Histogram1D"

    def print(self, **kwargs) -> None:
        """Print infos about this histogram"""
        txt = (
            f"{self.name}  {self.title}\n"
            f"    xtitle  = '{self.xtitle}'\n"
            f"    ytitle  = '{self.ytitle}'\n"
            f"    entries = {self.entries}\n"
            f"    mean    = {self.mean}\n"
            f"    std     = {self.std}"
        )
        colors.printc(txt, **kwargs)

print(**kwargs)

Print infos about this histogram

Source code in vedo/pyplot/charts.py
405
406
407
408
409
410
411
412
413
414
415
def print(self, **kwargs) -> None:
    """Print infos about this histogram"""
    txt = (
        f"{self.name}  {self.title}\n"
        f"    xtitle  = '{self.xtitle}'\n"
        f"    ytitle  = '{self.ytitle}'\n"
        f"    entries = {self.entries}\n"
        f"    mean    = {self.mean}\n"
        f"    std     = {self.std}"
    )
    colors.printc(txt, **kwargs)

Histogram2D

Bases: Figure

2D histogramming.

Source code in vedo/pyplot/charts.py
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
class Histogram2D(Figure):
    """2D histogramming."""

    def __init__(
        self,
        xvalues,
        yvalues=None,
        bins=25,
        weights=None,
        cmap="cividis",
        alpha=1,
        gap=0,
        scalarbar=True,
        # Figure and axes options:
        like=None,
        xlim=None,
        ylim=(None, None),
        zlim=(None, None),
        aspect=1,
        title="",
        xtitle=" ",
        ytitle=" ",
        ztitle="",
        ac="k",
        **fig_kwargs,
    ):
        """
        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.

        Args:
            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:
            - [histo_2d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_a.py)
            - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py)

            ![](https://vedo.embl.es/images/pyplot/histo_2D.png)
        """
        xvalues = np.asarray(xvalues)
        if yvalues is None:
            # assume [(x1,y1), (x2,y2) ...] format
            yvalues = xvalues[:, 1]
            xvalues = xvalues[:, 0]
        else:
            yvalues = np.asarray(yvalues)

        padding = [0, 0, 0, 0]

        if like is None and vedo.current_last_figure() is not None:
            if xlim is None and ylim == (None, None) and zlim == (None, None):
                like = vedo.current_last_figure()

        if like is not None:
            xlim = like.xlim
            ylim = like.ylim
            aspect = like.aspect
            padding = like.padding
            if bins is None:
                bins = like.bins
        if bins is None:
            bins = 20

        if isinstance(bins, int):
            bins = (bins, bins)

        if utils.is_sequence(xlim):
            # deal with user passing eg [x0, None]
            _x0, _x1 = xlim
            if _x0 is None:
                _x0 = xvalues.min()
            if _x1 is None:
                _x1 = xvalues.max()
            xlim = [_x0, _x1]

        if utils.is_sequence(ylim):
            # deal with user passing eg [x0, None]
            _y0, _y1 = ylim
            if _y0 is None:
                _y0 = yvalues.min()
            if _y1 is None:
                _y1 = yvalues.max()
            ylim = [_y0, _y1]

        H, xedges, yedges = np.histogram2d(
            xvalues, yvalues, weights=weights, bins=bins, range=(xlim, ylim)
        )

        xlim = np.min(xedges), np.max(xedges)
        ylim = np.min(yedges), np.max(yedges)
        dx, dy = xlim[1] - xlim[0], ylim[1] - ylim[0]

        fig_kwargs["title"] = title
        fig_kwargs["xtitle"] = xtitle
        fig_kwargs["ytitle"] = ytitle
        fig_kwargs["ac"] = ac

        self.entries = len(xvalues)
        self.frequencies = H
        self.edges = (xedges, yedges)
        self.mean = (xvalues.mean(), yvalues.mean())
        self.std = (xvalues.std(), yvalues.std())
        self.bins = bins  # internally used by "like"

        ############################### stats legend as htitle
        addstats = False
        if not title:
            if "axes" not in fig_kwargs:
                addstats = True
                axes_opts = {}
                fig_kwargs["axes"] = axes_opts
            elif fig_kwargs["axes"] is False:
                pass
            else:
                axes_opts = fig_kwargs["axes"]
                if "htitle" not in fig_kwargs["axes"]:
                    addstats = True

        if addstats:
            htitle = f"Entries:~~{int(self.entries)}  "
            htitle += f"Mean:~~{utils.precision(self.mean, 3)}  "
            htitle += f"STD:~~{utils.precision(self.std, 3)}  "
            axes_opts["htitle"] = htitle
            axes_opts["htitle_justify"] = "bottom-left"
            axes_opts["htitle_size"] = 0.0175

        ############################################### Figure init
        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)

        if self.yscale:
            ##################### the grid
            acts = []
            g = shapes.Grid(
                pos=[(xlim[0] + xlim[1]) / 2, (ylim[0] + ylim[1]) / 2, 0],
                s=(dx, dy),
                res=bins[:2],
            )
            g.alpha(alpha).lw(0).wireframe(False).flat().lighting("off")
            g.cmap(cmap, np.ravel(H.T), on="cells", vmin=zlim[0], vmax=zlim[1])
            if gap:
                g.shrink(abs(1 - gap))

            if scalarbar:
                sc = g.add_scalarbar3d(ztitle, c=ac).scalarbar

                # print(" g.actor.GetBounds()[0]", g.actor.GetBounds()[:2])
                # print("sc.actor.GetBounds()[0]",sc.actor.GetBounds()[:2])
                delta = sc.actor.GetBounds()[0] - g.actor.GetBounds()[1]

                sc_size = sc.actor.GetBounds()[1] - sc.actor.GetBounds()[0]

                sc.actor.SetOrigin(sc.actor.GetBounds()[0], 0, 0)
                sc.scale([self.yscale, 1, 1])  ## prescale trick
                sc.shift(-delta + 0.25 * sc_size * self.yscale)

                acts.append(sc)
            acts.append(g)

            self.insert(*acts, as3d=False)
            self.name = "Histogram2D"

PlotBars

Bases: Figure

Creates a PlotBars(Figure) object.

Source code in vedo/pyplot/charts.py
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
class PlotBars(Figure):
    """Creates a `PlotBars(Figure)` object."""

    def __init__(
        self,
        data,
        errors=False,
        logscale=False,
        fill=True,
        gap=0.02,
        radius=0.05,
        c="olivedrab",
        alpha=1,
        texture="",
        outline=False,
        lw=2,
        lc="k",
        # Figure and axes options:
        like=None,
        xlim=(None, None),
        ylim=(0, None),
        aspect=1.333,
        padding=(0.025, 0.025, 0, 0.05),
        #
        title="",
        xtitle=" ",
        ytitle=" ",
        ac="k",
        grid=False,
        ztolerance=None,
        **fig_kwargs,
    ):
        """
        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.

        Args:
            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:
            - [plot_bars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_bars.py)

               ![](https://vedo.embl.es/images/pyplot/plot_bars.png)
        """
        ndata = len(data)
        if ndata == 4:
            counts, xlabs, cols, edges = data
        elif ndata == 3:
            counts, xlabs, cols = data
            edges = np.array(range(len(counts) + 1)) + 0.5
        elif ndata == 2:
            counts, xlabs = data
            edges = np.array(range(len(counts) + 1)) + 0.5
            cols = [c] * len(counts)
        else:
            m = "barplot error: data must be given as [counts, labels, colors, edges] not\n"
            vedo.logger.error(f"{m}{data}\n     bin edges and colors are optional.")
            raise RuntimeError()

        # sanity checks
        assert len(counts) == len(xlabs)
        assert len(counts) == len(cols)
        assert len(counts) == len(edges) - 1

        counts = np.asarray(counts)
        edges = np.asarray(edges)

        if logscale:
            counts = np.log10(counts + 1)
            if ytitle == " ":
                ytitle = "log_10 (counts+1)"

        if like is None and vedo.current_last_figure() is not None:
            if xlim == (None, None) and ylim == (0, None):
                like = vedo.current_last_figure()

        if like is not None:
            xlim = like.xlim
            ylim = like.ylim
            aspect = like.aspect
            padding = like.padding

        if utils.is_sequence(xlim):
            # deal with user passing eg [x0, None]
            _x0, _x1 = xlim
            if _x0 is None:
                _x0 = np.min(edges)
            if _x1 is None:
                _x1 = np.max(edges)
            xlim = [_x0, _x1]

        x0, x1 = np.min(edges), np.max(edges)
        y0, y1 = ylim[0], np.max(counts)

        if like is None:
            ylim = list(ylim)
            if xlim is None:
                xlim = [x0, x1]
            if ylim[1] is None:
                ylim[1] = y1
            if ylim[0] != 0:
                ylim[0] = y0

        fig_kwargs["title"] = title
        fig_kwargs["xtitle"] = xtitle
        fig_kwargs["ytitle"] = ytitle
        fig_kwargs["ac"] = ac
        fig_kwargs["ztolerance"] = ztolerance
        fig_kwargs["grid"] = grid

        centers = (edges[0:-1] + edges[1:]) / 2
        binsizes = (centers - edges[0:-1]) * 2

        if "axes" not in fig_kwargs:
            fig_kwargs["axes"] = {}

        _xlabs = []
        for center, xlb in zip(centers, xlabs):
            _xlabs.append([center, str(xlb)])
        fig_kwargs["axes"]["x_values_and_labels"] = _xlabs

        ############################################### Figure
        self.statslegend = ""
        self.edges = edges
        self.centers = centers
        self.bins = edges  # internal used by "like"
        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)
        if not self.yscale:
            return

        rs = []
        maxheigth = 0
        if fill:  #####################
            if outline:
                gap = 0

            for i in range(len(centers)):
                binsize = binsizes[i]
                p0 = (edges[i] + gap * binsize, 0, 0)
                p1 = (edges[i + 1] - gap * binsize, counts[i], 0)

                if radius:
                    rds = np.array([0, 0, radius, radius])
                    p1_yscaled = [p1[0], p1[1] * self.yscale, 0]
                    r = shapes.Rectangle(p0, p1_yscaled, radius=rds * binsize, res=6)
                    r.scale([1, 1 / self.yscale, 1])
                    r.radius = (
                        None  # so it doesnt get recreated and rescaled by insert()
                    )
                else:
                    r = shapes.Rectangle(p0, p1)

                if texture:
                    r.texture(texture)
                    c = "w"

                r.actor.PickableOff()
                maxheigth = max(maxheigth, p1[1])
                if c in colors.cmaps_names:
                    col = colors.color_map((p0[0] + p1[0]) / 2, c, edges[0], edges[-1])
                else:
                    col = cols[i]
                r.color(col).alpha(alpha).lighting("off")
                r.name = f"bar_{i}"
                r.z(self.ztolerance)
                rs.append(r)

        elif outline:  #####################
            lns = [[edges[0], 0, 0]]
            for i in range(len(centers)):
                lns.append([edges[i], counts[i], 0])
                lns.append([edges[i + 1], counts[i], 0])
                maxheigth = max(maxheigth, counts[i])
            lns.append([edges[-1], 0, 0])
            outl = shapes.Line(lns, c=lc, alpha=alpha, lw=lw).z(self.ztolerance)
            outl.name = f"bar_outline_{i}"
            rs.append(outl)

        if errors:  #####################
            for x, f in centers:
                err = np.sqrt(f)
                el = shapes.Line(
                    [x, f - err / 2, 0], [x, f + err / 2, 0], c=lc, alpha=alpha, lw=lw
                )
                el.z(self.ztolerance * 2)
                rs.append(el)

        self.insert(*rs, as3d=False)
        self.name = "PlotBars"

PlotXY

Bases: Figure

Creates a PlotXY(Figure) object.

Source code in vedo/pyplot/charts.py
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
class PlotXY(Figure):
    """Creates a `PlotXY(Figure)` object."""

    def __init__(
        self,
        #
        data,
        xerrors=None,
        yerrors=None,
        #
        lw=2,
        lc=None,
        la=1,
        dashed=False,
        splined=False,
        #
        elw=2,  # error line width
        ec=None,  # error line or band color
        error_band=False,  # errors in x are ignored
        #
        marker="",
        ms=None,
        mc=None,
        ma=None,
        # Figure and axes options:
        like=None,
        xlim=None,
        ylim=(None, None),
        aspect=1.333,
        padding=0.05,
        #
        title="",
        xtitle=" ",
        ytitle=" ",
        ac="k",
        grid=True,
        ztolerance=None,
        label="",
        **fig_kwargs,
    ):
        """
        Args:
            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).

        Examples:
            ```python
            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()
            ```
            ![](https://vedo.embl.es/images/feats/plotxy.png)

        Examples:
            - [plot_errbars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errbars.py)
            - [plot_errband.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errband.py)
            - [plot_pip.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_pip.py)

                ![](https://vedo.embl.es/images/pyplot/plot_pip.png)

            - [scatter1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter1.py)
            - [scatter2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter2.py)

                ![](https://vedo.embl.es/images/pyplot/scatter2.png)
        """
        line = False
        if lw > 0:
            line = True
        if marker == "" and not line and not splined:
            marker = "o"

        if like is None and vedo.current_last_figure() is not None:
            if xlim is None and ylim == (None, None):
                like = vedo.current_last_figure()

        if like is not None:
            xlim = like.xlim
            ylim = like.ylim
            aspect = like.aspect
            padding = like.padding

        if utils.is_sequence(xlim):
            # deal with user passing eg [x0, None]
            _x0, _x1 = xlim
            if _x0 is None:
                _x0 = data.min()
            if _x1 is None:
                _x1 = data.max()
            xlim = [_x0, _x1]

        # purge NaN from data
        data = data[~np.isnan(data).any(axis=1), :]

        fig_kwargs["title"] = title
        fig_kwargs["xtitle"] = xtitle
        fig_kwargs["ytitle"] = ytitle
        fig_kwargs["ac"] = ac
        fig_kwargs["ztolerance"] = ztolerance
        fig_kwargs["grid"] = grid

        x0, y0 = np.min(data, axis=0)
        x1, y1 = np.max(data, axis=0)
        if xerrors is not None and not error_band:
            x0 = min(data[:, 0] - xerrors)
            x1 = max(data[:, 0] + xerrors)
        if yerrors is not None:
            y0 = min(data[:, 1] - yerrors)
            y1 = max(data[:, 1] + yerrors)

        if like is None:
            if xlim is None:
                xlim = (None, None)
            xlim = list(xlim)
            if xlim[0] is None:
                xlim[0] = x0
            if xlim[1] is None:
                xlim[1] = x1
            ylim = list(ylim)
            if ylim[0] is None:
                ylim[0] = y0
            if ylim[1] is None:
                ylim[1] = y1

        self.entries = len(data)
        self.mean = data.mean()
        self.std = data.std()

        self.ztolerance = 0

        ######### the PlotXY marker
        # fall back solutions logic for colors
        if "c" in fig_kwargs:
            if mc is None:
                mc = fig_kwargs["c"]
            if lc is None:
                lc = fig_kwargs["c"]
            if ec is None:
                ec = fig_kwargs["c"]
        if lc is None:
            lc = "k"
        if mc is None:
            mc = lc
        if ma is None:
            ma = la
        if ec is None:
            if mc is None:
                ec = lc
            else:
                ec = mc

        if label:
            nlab = LabelData()
            nlab.text = label
            nlab.tcolor = ac
            nlab.marker = marker
            if line and marker == "":
                nlab.marker = "-"
            nlab.mcolor = mc
            fig_kwargs["label"] = nlab

        ############################################### Figure init
        super().__init__(xlim, ylim, aspect, padding, **fig_kwargs)

        if not self.yscale:
            return

        acts = []

        ######### the PlotXY Line or Spline
        if dashed:
            l = shapes.DashedLine(data, c=lc, alpha=la, lw=lw)
            acts.append(l)
        elif splined:
            l = shapes.KSpline(data).lw(lw).c(lc).alpha(la)
            acts.append(l)
        elif line:
            l = shapes.Line(data, c=lc, alpha=la).lw(lw)
            acts.append(l)

        if marker:
            pts = np.c_[data, np.zeros(len(data))]

            if utils.is_sequence(ms):
                ### variable point size
                mk = shapes.Marker(marker, s=1)
                mk.scale([1, 1 / self.yscale, 1])
                msv = np.zeros_like(pts)
                msv[:, 0] = ms
                marked = shapes.Glyph(
                    pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
                )
            else:
                ### fixed point size
                if ms is None:
                    ms = (xlim[1] - xlim[0]) / 100.0

                if utils.is_sequence(mc):
                    fig_kwargs["marker_color"] = None  # for labels
                    mk = shapes.Marker(marker, s=ms)
                    mk.scale([1, 1 / self.yscale, 1])
                    msv = np.zeros_like(pts)
                    msv[:, 0] = 1
                    marked = shapes.Glyph(
                        pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True
                    )
                else:
                    mk = shapes.Marker(marker, s=ms)
                    mk.scale([1, 1 / self.yscale, 1])
                    marked = shapes.Glyph(pts, mk, c=mc)

            marked.name = "Marker"
            marked.alpha(ma)
            marked.z(3 * self.ztolerance)
            acts.append(marked)

        ######### the PlotXY marker errors
        ztol = self.ztolerance

        if error_band:
            yerrors = np.abs(yerrors)
            du = np.array(data)
            dd = np.array(data)
            du[:, 1] += yerrors
            dd[:, 1] -= yerrors
            if splined:
                res = len(data) * 20
                band1 = shapes.KSpline(du, res=res)
                band2 = shapes.KSpline(dd, res=res)
                band = shapes.Ribbon(band1, band2, res=(res, 2))
            else:
                dd = list(reversed(dd.tolist()))
                band = shapes.Line(du.tolist() + dd, closed=True)
                band.triangulate().lw(0)
            if ec is None:
                band.c(lc)
            else:
                band.c(ec)
            band.lighting("off").alpha(la).z(ztol / 20)
            acts.append(band)

        else:
            ## xerrors
            if xerrors is not None:
                if len(xerrors) == len(data):
                    errs = []
                    for i, val in enumerate(data):
                        xval, yval = val
                        xerr = xerrors[i] / 2
                        el = shapes.Line(
                            (xval - xerr, yval, ztol), (xval + xerr, yval, ztol)
                        )
                        el.lw(elw)
                        errs.append(el)
                    mxerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol)
                    acts.append(mxerrs)
                else:
                    vedo.logger.error(
                        "in PlotXY(xerrors=...): mismatch in array length"
                    )

            ## yerrors
            if yerrors is not None:
                if len(yerrors) == len(data):
                    errs = []
                    for i, val in enumerate(data):
                        xval, yval = val
                        yerr = yerrors[i]
                        el = shapes.Line(
                            (xval, yval - yerr, ztol), (xval, yval + yerr, ztol)
                        )
                        el.lw(elw)
                        errs.append(el)
                    myerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol)
                    acts.append(myerrs)
                else:
                    vedo.logger.error(
                        "in PlotXY(yerrors=...): mismatch in array length"
                    )

        self.insert(*acts, as3d=False)
        self.name = "PlotXY"

functions

fit(points, deg=1, niter=0, nstd=3, xerrors=None, yerrors=None, vrange=None, res=250, lw=3, c='red4')

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.

Parameters:

Name Type Description Default
deg int

degree of the polynomial to be fitted

1
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

0
nstd float

nr. of standard deviation to use for error calculation

3
xerrors list

array of the same length of points with the errors on x

None
yerrors list

array of the same length of points with the errors on y

None
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)

None
res int

resolution of the output fitted line and error lines

250

Examples:

Source code in vedo/pyplot/functions.py
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
def fit(
    points,
    deg=1,
    niter=0,
    nstd=3,
    xerrors=None,
    yerrors=None,
    vrange=None,
    res=250,
    lw=3,
    c="red4",
) -> vedo.shapes.Line:
    """
    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.

    Args:
        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:
        - [fit_polynomial1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fit_polynomial1.py)

        ![](https://vedo.embl.es/images/pyplot/fitPolynomial1.png)
    """
    if isinstance(points, vedo.pointcloud.Points):
        points = points.coordinates
    points = np.asarray(points)
    if len(points) == 2:  # assume user is passing [x,y]
        points = np.c_[points[0], points[1]]
    x = points[:, 0]
    y = points[:, 1]  # ignore z

    n = len(x)
    ndof = n - deg - 1
    if vrange is not None:
        x0, x1 = vrange
    else:
        x0, x1 = np.min(x), np.max(x)
        if xerrors is not None:
            x0 -= xerrors[0] / 2
            x1 += xerrors[-1] / 2

    tol = (x1 - x0) / 10000
    xr = np.linspace(x0, x1, res)

    # project x errs on y
    if xerrors is not None:
        xerrors = np.asarray(xerrors)
        if yerrors is not None:
            yerrors = np.asarray(yerrors)
            w = 1.0 / yerrors
            coeffs = np.polyfit(x, y, deg, w=w, rcond=None)
        else:
            coeffs = np.polyfit(x, y, deg, rcond=None)
        # update yerrors, 1 bootstrap iteration is enough
        p1d = np.poly1d(coeffs)
        der = (p1d(x + tol) - p1d(x)) / tol
        yerrors = np.sqrt(yerrors * yerrors + np.power(der * xerrors, 2))

    if yerrors is not None:
        yerrors = np.asarray(yerrors)
        w = 1.0 / yerrors
        coeffs, V = np.polyfit(x, y, deg, w=w, rcond=None, cov=True)
    else:
        w = 1
        coeffs, V = np.polyfit(x, y, deg, rcond=None, cov=True)

    p1d = np.poly1d(coeffs)
    theor = p1d(xr)
    fitl = shapes.Line(np.c_[xr, theor], lw=lw, c=c).z(tol * 2)
    fitl.coefficients = coeffs
    fitl.covariance_matrix = V
    residuals2_sum = np.sum(np.power(p1d(x) - y, 2)) / ndof
    sigma = np.sqrt(residuals2_sum)
    fitl.reduced_chi2 = np.sum(np.power((p1d(x) - y) * w, 2)) / ndof
    fitl.ndof = ndof
    fitl.data_sigma = sigma  # worked out from data using chi2=1 hypo
    fitl.name = "LinearPolynomialFit"

    if not niter:
        fitl.coefficient_errors = np.sqrt(np.diag(V))
        return fitl  ################################

    if yerrors is not None:
        sigma = yerrors
    else:
        w = None
        fitl.reduced_chi2 = 1

    Theors, all_coeffs = [], []
    for i in range(niter):
        noise = np.random.randn(n) * sigma
        coeffs = np.polyfit(x, y + noise, deg, w=w, rcond=None)
        all_coeffs.append(coeffs)
        P1d = np.poly1d(coeffs)
        Theor = P1d(xr)
        Theors.append(Theor)
    # all_coeffs = np.array(all_coeffs)
    fitl.monte_carlo_coefficients = np.array(all_coeffs)

    stds = np.std(Theors, axis=0)
    fitl.coefficient_errors = np.std(all_coeffs, axis=0)

    # check distributions on the fly
    # for i in range(deg+1):
    #     histogram(all_coeffs[:,i],title='par'+str(i)).show(new=1)
    # histogram(all_coeffs[:,0], all_coeffs[:,1],
    #           xtitle='param0', ytitle='param1',scalarbar=1).show(new=1)
    # histogram(all_coeffs[:,1], all_coeffs[:,2],
    #           xtitle='param1', ytitle='param2').show(new=1)
    # histogram(all_coeffs[:,0], all_coeffs[:,2],
    #           xtitle='param0', ytitle='param2').show(new=1)

    error_lines = []
    for i in [nstd, -nstd]:
        pp = np.c_[xr, theor + stds * i]
        el = shapes.Line(pp, lw=1, alpha=0.2, c="k").z(tol)
        error_lines.append(el)
        el.name = "ErrorLine for sigma=" + str(i)

    fitl.error_lines = error_lines
    l1 = error_lines[0].coordinates.tolist()
    cband = l1 + list(reversed(error_lines[1].coordinates.tolist())) + [l1[0]]
    fitl.error_band = shapes.Line(cband).triangulate().lw(0).c("k", 0.15)
    fitl.error_band.name = "PolynomialFitErrorBand"
    return fitl

histogram(*args, **kwargs)

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.


.. note:: default mode, for 1D arrays

Creates a Histogram1D(Figure) object.

Parameters:

Name Type Description Default
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).

required
bins int

number of bins

required
vrange list

restrict the range of the histogram

required
density bool

normalize the area to 1 by dividing by the nr of entries and bin size

required
logscale bool

use logscale on y-axis

required
fill bool

fill bars with solid color c

required
gap float

leave a small space btw bars

required
radius float

border radius of the top of the histogram bar. Default value is 0.1.

required
texture str

url or path to an image to be used as texture for the bin

required
outline bool

show outline of the bins

required
errors bool

show error bars

required
xtitle str

title for the x-axis, can also be set using axes=dict(xtitle="my x axis")

required
ytitle str

title for the y-axis, can also be set using axes=dict(ytitle="my y axis")

required
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.

required
aspect float

the desired aspect ratio of the histogram. Default is 4/3.

required
grid bool

show the background grid for the axes, can also be set using axes=dict(xygrid=True)

required
ztolerance float

a tolerance factor to superimpose objects (along the z-axis).

required

Examples:


.. note:: default mode, for 2D arrays

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

Parameters:

Name Type Description Default
bins list

binning as (nx, ny)

required
weights list

array of weights to assign to each entry

required
cmap (str, lookuptable)

color map name or look up table

required
alpha float

opacity of the histogram

required
gap float

separation between adjacent bins as a fraction for their size. Set gap=-1 to generate a quad surface.

required
scalarbar bool

add a scalarbar to right of the histogram

required
like Figure

grab and use the same format of the given Figure (for superimposing)

required
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.

required
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.

required
aspect float

the desired aspect ratio of the figure.

required
title str

title of the plot to appear on top. If left blank some statistics will be shown.

required
xtitle str

x axis title

required
ytitle str

y axis title

required
ztitle str

title for the scalar bar

required
ac str

axes color, additional keyword for Axes can also be added using e.g. axes=dict(xygrid=True)

required

Examples:


.. note:: mode="3d"

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

Parameters:

Name Type Description Default
xtitle str

x axis title

required
bins int

nr of bins for the smaller range in x or y

required
vrange list

range in x and y in format [(xmin,xmax), (ymin,ymax)]

required
norm float

sets a scaling factor for the z axis (frequency axis)

required
fill bool

draw solid hexagons

required
cmap str

color map name for elevation

required
gap float

keep a internal empty gap between bins [0,1]

required
zscale float

rescale the (already normalized) zaxis for visual convenience

required

Examples:


.. note:: mode="hexbin"

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

Parameters:

Name Type Description Default
xtitle str

x axis title

required
bins int

nr of bins for the smaller range in x or y

required
vrange list

range in x and y in format [(xmin,xmax), (ymin,ymax)]

required
norm float

sets a scaling factor for the z axis (frequency axis)

required
fill bool

draw solid hexagons

required
cmap str

color map name for elevation

required

Examples:


.. note:: mode="polar"

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

Parameters:

Name Type Description Default
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).

required
title str

histogram title

required
tsize float

title size

required
bins int

number of bins in phi

required
r1 float

inner radius

required
r2 float

outer radius

required
phigap float

gap angle btw 2 radial bars, in degrees

required
rgap float

gap factor along radius of numeric angle labels

required
lpos float

label gap factor along radius

required
lsize float

label size

required
c color

color of the histogram bars, can be a list of length bins

required
bc color

color of the frame and labels

required
alpha float

opacity of the frame

required
cmap str

color map name

required
deg bool

input array is in degrees

required
vmin float

minimum value of the radial axis

required
vmax float

maximum value of the radial axis

required
labels list

list of labels, must be of length bins

required
show_disc bool

show the outer ring axis

required
nrays int

draw this number of axis rays (continuous and dashed)

required
show_lines bool

show lines to the origin

required
show_angles bool

show angular values

required
show_errors bool

show error bars

required

Examples:


.. note:: mode="spheric"

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

Parameters:

Name Type Description Default
rmax float

maximum radial elevation of bin

required
res int

sphere resolution

required
cmap str

color map name

required
lw int

line width of the bin edges

required

Examples:

Source code in vedo/pyplot/functions.py
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
def histogram(*args, **kwargs):
    """
    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.

    -------------------------------------------------------------------------
    .. note:: default mode, for 1D arrays

    Creates a `Histogram1D(Figure)` object.

    Args:
        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:
        - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
        - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
        - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
        - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)

        ![](https://vedo.embl.es/images/pyplot/histo_1D.png)


    -------------------------------------------------------------------------
    .. note:: default mode, for 2D arrays

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

    Args:
        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:
        - [histo_2d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_a.py)
        - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py)

        ![](https://vedo.embl.es/images/pyplot/histo_2D.png)


    -------------------------------------------------------------------------
    .. note:: mode="3d"

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

    Args:
        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:
        - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py)


    -------------------------------------------------------------------------
    .. note:: mode="hexbin"

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

    Args:
        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:
        - [histo_hexagonal.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_hexagonal.py)

        ![](https://vedo.embl.es/images/pyplot/histo_hexagonal.png)


    -------------------------------------------------------------------------
    .. note:: mode="polar"

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

    Args:
        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:
        - [histo_polar.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_polar.py)

        ![](https://vedo.embl.es/images/pyplot/histo_polar.png)


    -------------------------------------------------------------------------
    .. note:: mode="spheric"

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

    Args:
        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:
        - [histo_spheric.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_spheric.py)

        ![](https://vedo.embl.es/images/pyplot/histo_spheric.png)
    """
    mode = kwargs.pop("mode", "")
    if len(args) == 2:  # x, y
        if "spher" in mode:
            return _histogram_spheric(args[0], args[1], **kwargs)

        if "hex" in mode:
            return _histogram_hex_bin(args[0], args[1], **kwargs)

        if "3d" in mode.lower():
            return _histogram_quad_bin(args[0], args[1], **kwargs)

        return Histogram2D(args[0], args[1], **kwargs)

    elif len(args) == 1:
        if isinstance(args[0], vedo.Volume):
            data = args[0].pointdata[0]
        elif isinstance(args[0], vedo.Points):
            pd0 = args[0].pointdata[0]
            if pd0 is not None:
                data = pd0.ravel()
            else:
                data = args[0].celldata[0].ravel()
        else:
            try:
                if "pandas" in str(type(args[0])):
                    if "xtitle" not in kwargs:
                        kwargs.update({"xtitle": args[0].name.replace("_", "_ ")})
            except:
                pass
            data = np.asarray(args[0])

        if "spher" in mode:
            return _histogram_spheric(args[0][:, 0], args[0][:, 1], **kwargs)

        if data.ndim == 1:
            if "polar" in mode:
                return _histogram_polar(data, **kwargs)
            return Histogram1D(data, **kwargs)

        if "hex" in mode:
            return _histogram_hex_bin(args[0][:, 0], args[0][:, 1], **kwargs)

        if "3d" in mode.lower():
            return _histogram_quad_bin(args[0][:, 0], args[0][:, 1], **kwargs)

        return Histogram2D(args[0], **kwargs)

    vedo.logger.error(f"in histogram(): could not understand input {args[0]}")
    return None

plot(*args, **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.

Parameters:

Name Type Description Default
xerrors bool

show error bars associated to each point in x

required
yerrors bool

show error bars associated to each point in y

required
lw int

width of the line connecting points in pixel units. Set it to 0 to remove the line.

required
lc str

line color

required
la float

line "alpha", opacity of the line

required
dashed bool

draw a dashed line instead of a continuous line

required
splined bool

spline the line joining the point as a countinous curve

required
elw int

width of error bar lines in units of pixels

required
ec color

color of error bar, by default the same as marker color

required
error_band bool

represent errors on y as a filled error band. Use ec keyword to modify its color.

required
marker (str, int)

use a marker for the data points

required
ms float

marker size

required
mc color

color of the marker

required
ma float

opacity of the marker

required
xlim list

set limits to the range for the x variable

required
ylim list

set limits to the range for the y variable

required
aspect float

Desired aspect ratio. If None, it is automatically calculated to get a reasonable aspect ratio. Scaling factor is saved in Figure.yscale

required
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.

required
title str

title to appear on the top of the frame, like a header.

required
xtitle str

title for the x-axis, can also be set using axes=dict(xtitle="my x axis")

required
ytitle str

title for the y-axis, can also be set using axes=dict(ytitle="my y axis")

required
ac str

axes color

required
grid bool

show the background grid for the axes, can also be set using axes=dict(xygrid=True)

required
ztolerance float

a tolerance factor to superimpose objects (along the z-axis).

required

Examples:

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:


.. note:: 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.

Parameters:

Name Type Description Default
errors bool

show error bars

required
logscale bool

use logscale on y-axis

required
fill bool

fill bars with solid color c

required
gap float

leave a small space btw bars

required
radius float

border radius of the top of the histogram bar. Default value is 0.1.

required
texture str

url or path to an image to be used as texture for the bin

required
outline bool

show outline of the bins

required
xtitle str

title for the x-axis, can also be set using axes=dict(xtitle="my x axis")

required
ytitle str

title for the y-axis, can also be set using axes=dict(ytitle="my y axis")

required
ac str

axes color

required
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.

required
aspect float

the desired aspect ratio of the figure. Default is 4/3.

required
grid bool

show the background grid for the axes, can also be set using axes=dict(xygrid=True)

required

Examples:


.. note:: 2D functions

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

Parameters:

Name Type Description Default
x float

x range of values

required
y float

y range of values

required
zlimits float

limit the z range of the independent variable

required
zlevels int

will draw the specified number of z-levels contour lines

required
show_nan bool

show where the function does not exist as red points

required
bins list

number of bins in x and y

required

Examples:


.. note:: mode="complex"

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

Parameters:

Name Type Description Default
cmap str

diverging color map (white means imag(z)=0)

required
lw float

line with of the binning

required
bins list

binning in x and y

required

Examples:


.. note:: 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.

Parameters:

Name Type Description Default
title str

plot title

required
tsize float

title size

required
bins int

number of bins in phi

required
r1 float

inner radius

required
r2 float

outer radius

required
lsize float

label size

required
c color

color of the line

required
ac color

color of the frame and labels

required
alpha float

opacity of the frame

required
ps int

point size in pixels, if ps=0 no point is drawn

required
lw int

line width in pixels, if lw=0 no line is drawn

required
deg bool

input array is in degrees

required
vmax float

normalize radius to this maximum value

required
fill bool

fill convex area with solid color

required
splined bool

interpolate the set of input points

required
show_disc bool

draw the outer ring axis

required
nrays int

draw this number of axis rays (continuous and dashed)

required
show_lines bool

draw lines to the origin

required
show_angles bool

draw angle values

required

Examples:


.. note:: 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).

Parameters:

Name Type Description Default
rfunc

function handle to a user defined function rho(theta, phi).

required
normalize bool

scale surface to fit inside the unit sphere

required
res int

grid resolution of the unit sphere

required
scalarbar bool

add a 3D scalarbar to the plot for radius

required
c color

color of the unit sphere

required
alpha float

opacity of the unit sphere

required
cmap str

color map for the surface

required

Examples:

Source code in vedo/pyplot/functions.py
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
def plot(*args, **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.

    Args:
        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).

    Examples:
        ```python
        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()
        ```
        <img src="https://user-images.githubusercontent.com/32848391/74363882-c3638300-4dcb-11ea-8a78-eb492ad9711f.png" width="600">

    Examples:
        - [plot_errbars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errbars.py)
        - [plot_errband.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errband.py)
        - [plot_pip.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_pip.py)

            ![](https://vedo.embl.es/images/pyplot/plot_pip.png)

        - [scatter1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter1.py)
        - [scatter2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter2.py)



    -------------------------------------------------------------------------
    .. note:: 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.

    Args:
        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:
        - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py)
        - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py)
        - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py)
        - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py)

        ![](https://vedo.embl.es/images/pyplot/histo_1D.png)


    ----------------------------------------------------------------------
    .. note:: 2D functions

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

    Args:
        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:
        - [plot_fxy1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy1.py)

            ![](https://vedo.embl.es/images/pyplot/plot_fxy.png)

        - [plot_fxy2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy2.py)


    --------------------------------------------------------------------
    .. note:: mode="complex"

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

    Args:
        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:
        - [plot_fxy.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy.py)

            ![](https://user-images.githubusercontent.com/32848391/73392962-1709a300-42db-11ea-9278-30c9d6e5eeaa.png)


    --------------------------------------------------------------------
    .. note:: 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.

    Args:
        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:
        - [histo_polar.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_polar.py)

            ![](https://user-images.githubusercontent.com/32848391/64992590-7fc82400-d8d4-11e9-9c10-795f4756a73f.png)


    --------------------------------------------------------------------
    .. note:: 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)`.

    Args:
        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:
        - [plot_spheric.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_spheric.py)

            ![](https://vedo.embl.es/images/pyplot/plot_spheric.png)
    """
    mode = kwargs.pop("mode", "")
    if "spher" in mode:
        return _plot_spheric(args[0], **kwargs)

    if "bar" in mode:
        return PlotBars(args[0], **kwargs)

    if isinstance(args[0], str) or "function" in str(type(args[0])):
        if "complex" in mode:
            return _plot_fz(args[0], **kwargs)
        return _plot_fxy(args[0], **kwargs)

    # grab the matplotlib-like options
    optidx = None
    for i, a in enumerate(args):
        if i > 0 and isinstance(a, str):
            optidx = i
            break
    if optidx:
        opts = args[optidx].replace(" ", "")
        if "--" in opts:
            opts = opts.replace("--", "")
            kwargs["dashed"] = True
        elif "-" in opts:
            opts = opts.replace("-", "")
        else:
            kwargs["lw"] = 0

        symbs = [
            ".",
            "o",
            "O",
            "0",
            "p",
            "*",
            "h",
            "D",
            "d",
            "v",
            "^",
            ">",
            "<",
            "s",
            "x",
            "a",
        ]

        allcols = list(colors.colors.keys()) + list(colors.color_nicks.keys())
        for cc in allcols:
            if cc == "o":
                continue
            if cc in opts:
                opts = opts.replace(cc, "")
                kwargs["lc"] = cc
                kwargs["mc"] = cc
                break

        for ss in symbs:
            if ss in opts:
                opts = opts.replace(ss, "", 1)
                kwargs["marker"] = ss
                break

        opts.replace(" ", "")
        if opts:
            vedo.logger.error(f"in plot(), could not understand option(s): {opts}")

    if optidx == 1 or optidx is None:
        if utils.is_sequence(args[0][0]) and len(args[0][0]) > 1:
            # print('------------- case 1', 'plot([(x,y),..])')
            data = np.asarray(args[0])  # (x,y)
            x = np.asarray(data[:, 0])
            y = np.asarray(data[:, 1])

        elif len(args) == 1 or optidx == 1:
            # print('------------- case 2', 'plot(x)')
            if "pandas" in str(type(args[0])):
                if "ytitle" not in kwargs:
                    kwargs.update({"ytitle": args[0].name.replace("_", "_ ")})
            x = np.linspace(0, len(args[0]), num=len(args[0]))
            y = np.asarray(args[0]).ravel()

        elif utils.is_sequence(args[1]):
            # print('------------- case 3', 'plot(allx,ally)',str(type(args[0])))
            if "pandas" in str(type(args[0])):
                if "xtitle" not in kwargs:
                    kwargs.update({"xtitle": args[0].name.replace("_", "_ ")})
            if "pandas" in str(type(args[1])):
                if "ytitle" not in kwargs:
                    kwargs.update({"ytitle": args[1].name.replace("_", "_ ")})
            x = np.asarray(args[0]).ravel()
            y = np.asarray(args[1]).ravel()

        elif utils.is_sequence(args[0]) and utils.is_sequence(args[0][0]):
            # print('------------- case 4', 'plot([allx,ally])')
            x = np.asarray(args[0][0]).ravel()
            y = np.asarray(args[0][1]).ravel()

    elif optidx == 2:
        # print('------------- case 5', 'plot(x,y)')
        x = np.asarray(args[0]).ravel()
        y = np.asarray(args[1]).ravel()

    else:
        vedo.logger.error(f"plot(): Could not understand input arguments {args}")
        return None

    if "polar" in mode:
        return _plot_polar(np.c_[x, y], **kwargs)

    return PlotXY(np.c_[x, y], **kwargs)

streamplot(X, Y, U, V, direction='both', max_propagation=None, lw=2, cmap='viridis', probes=())

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

Parameters:

Name Type Description Default
direction str

either "forward", "backward" or "both"

'both'
max_propagation float

maximum physical length of the streamline

None
lw float

line width in absolute units

2

Examples:

Source code in vedo/pyplot/functions.py
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
def streamplot(
    X, Y, U, V, direction="both", max_propagation=None, lw=2, cmap="viridis", probes=()
) -> vedo.shapes.Lines | None:
    """
    Generate a streamline plot of a vectorial field (U,V) defined at positions (X,Y).
    Returns a `Mesh` object.

    Args:
        direction (str):
            either "forward", "backward" or "both"
        max_propagation (float):
            maximum physical length of the streamline
        lw (float):
            line width in absolute units

    Examples:
        - [plot_stream.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_stream.py)

            ![](https://vedo.embl.es/images/pyplot/plot_stream.png)
    """
    n = len(X)
    m = len(Y[0])
    if n != m:
        print("Limitation in streamplot(): only square grids are allowed.", n, m)
        raise RuntimeError()

    xmin, xmax = X[0][0], X[-1][-1]
    ymin, ymax = Y[0][0], Y[-1][-1]

    field = np.sqrt(U * U + V * V)

    vol = vedo.Volume(field, dims=(n, n, 1))

    uf = np.ravel(U, order="F")
    vf = np.ravel(V, order="F")
    vects = np.c_[uf, vf, np.zeros_like(uf)]
    vol.pointdata["StreamPlotField"] = vects

    if len(probes) == 0:
        probe = shapes.Grid(
            pos=((n - 1) / 2, (n - 1) / 2, 0), s=(n - 1, n - 1), res=(n - 1, n - 1)
        )
    else:
        if isinstance(probes, vedo.Points):
            probes = probes.coordinates
        else:
            probes = np.array(probes, dtype=float)
            if len(probes[0]) == 2:
                probes = np.c_[probes[:, 0], probes[:, 1], np.zeros(len(probes))]
        sv = [(n - 1) / (xmax - xmin), (n - 1) / (ymax - ymin), 1]
        probes = probes - [xmin, ymin, 0]
        probes = np.multiply(probes, sv)
        probe = vedo.Points(probes)

    stream = vol.compute_streamlines(
        probe, direction=direction, max_propagation=max_propagation
    )
    if stream:
        stream.lw(lw).cmap(cmap).lighting("off")
        stream.scale([1 / (n - 1) * (xmax - xmin), 1 / (n - 1) * (ymax - ymin), 1])
        stream.shift(xmin, ymin)
    return stream

stats

CornerHistogram(values, bins=20, vrange=None, minbin=0, logscale=False, title='', c='g', bg='k', alpha=1, pos='bottom-left', s=0.175, lines=True, dots=False, nmax=None)

Build a histogram from a list of values in n bins. The resulting object is a 2D actor.

Use vrange to restrict the range of the histogram.

Use nmax to limit the sampling to this max nr of entries

Use pos to assign its position: - 1, topleft, - 2, topright, - 3, bottomleft, - 4, bottomright, - (x, y), as fraction of the rendering window

Source code in vedo/pyplot/stats.py
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
def CornerHistogram(
    values,
    bins=20,
    vrange=None,
    minbin=0,
    logscale=False,
    title="",
    c="g",
    bg="k",
    alpha=1,
    pos="bottom-left",
    s=0.175,
    lines=True,
    dots=False,
    nmax=None,
):
    """
    Build a histogram from a list of values in n bins.
    The resulting object is a 2D actor.

    Use `vrange` to restrict the range of the histogram.

    Use `nmax` to limit the sampling to this max nr of entries

    Use `pos` to assign its position:
        - 1, topleft,
        - 2, topright,
        - 3, bottomleft,
        - 4, bottomright,
        - (x, y), as fraction of the rendering window
    """
    if hasattr(values, "dataset"):
        values = utils.vtk2numpy(values.dataset.GetPointData().GetScalars())

    n = values.shape[0]
    if nmax and nmax < n:
        # subsample:
        idxs = np.linspace(0, n, num=int(nmax), endpoint=False).astype(int)
        values = values[idxs]

    fs, edges = np.histogram(values, bins=bins, range=vrange)

    if minbin:
        fs = fs[minbin:-1]
    if logscale:
        fs = np.log10(fs + 1)
    pts = []
    for i in range(len(fs)):
        pts.append([(edges[i] + edges[i + 1]) / 2, fs[i]])

    cplot = CornerPlot(pts, pos, s, title, c, bg, lines, dots)
    cplot.SetNumberOfYLabels(2)
    cplot.SetNumberOfXLabels(3)
    tprop = vtki.vtkTextProperty()
    tprop.SetColor(colors.get_color(bg))
    tprop.SetFontFamily(vtki.VTK_FONT_FILE)
    tprop.SetFontFile(utils.get_font_path("Calco"))
    tprop.SetOpacity(alpha)
    cplot.SetAxisTitleTextProperty(tprop)
    cplot.GetProperty().SetOpacity(alpha)
    cplot.GetXAxisActor2D().SetLabelTextProperty(tprop)
    cplot.GetXAxisActor2D().SetTitleTextProperty(tprop)
    cplot.GetXAxisActor2D().SetFontFactor(0.55)
    cplot.GetYAxisActor2D().SetLabelFactor(0.0)
    cplot.GetYAxisActor2D().LabelVisibilityOff()
    return cplot

CornerPlot(points, pos=1, s=0.2, title='', c='b', bg='k', lines=True, dots=True)

Return a vtkXYPlotActor that is a plot of x versus y, where points is a list of (x,y) points.

Assign position following this convention:

- 1, topleft,
- 2, topright,
- 3, bottomleft,
- 4, bottomright.
Source code in vedo/pyplot/stats.py
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
def CornerPlot(points, pos=1, s=0.2, title="", c="b", bg="k", lines=True, dots=True):
    """
    Return a `vtkXYPlotActor` that is a plot of `x` versus `y`,
    where `points` is a list of `(x,y)` points.

    Assign position following this convention:

        - 1, topleft,
        - 2, topright,
        - 3, bottomleft,
        - 4, bottomright.
    """
    if len(points) == 2:  # passing [allx, ally]
        points = np.stack((points[0], points[1]), axis=1)

    c = colors.get_color(c)  # allow different codings
    array_x = vtki.vtkFloatArray()
    array_y = vtki.vtkFloatArray()
    array_x.SetNumberOfTuples(len(points))
    array_y.SetNumberOfTuples(len(points))
    for i, p in enumerate(points):
        array_x.InsertValue(i, p[0])
        array_y.InsertValue(i, p[1])
    field = vtki.vtkFieldData()
    field.AddArray(array_x)
    field.AddArray(array_y)
    data = vtki.vtkDataObject()
    data.SetFieldData(field)

    xyplot = vtki.new("XYPlotActor")
    xyplot.AddDataObjectInput(data)
    xyplot.SetDataObjectXComponent(0, 0)
    xyplot.SetDataObjectYComponent(0, 1)
    xyplot.SetXValuesToValue()
    xyplot.SetAdjustXLabels(0)
    xyplot.SetAdjustYLabels(0)
    xyplot.SetNumberOfXLabels(3)

    xyplot.GetProperty().SetPointSize(5)
    xyplot.GetProperty().SetLineWidth(2)
    xyplot.GetProperty().SetColor(colors.get_color(bg))
    xyplot.SetPlotColor(0, c[0], c[1], c[2])

    xyplot.SetXTitle(title)
    xyplot.SetYTitle("")
    xyplot.ExchangeAxesOff()
    xyplot.SetPlotPoints(dots)

    if not lines:
        xyplot.PlotLinesOff()

    if isinstance(pos, str):
        spos = 2
        if "top" in pos:
            if "left" in pos:
                spos = 1
            elif "right" in pos:
                spos = 2
        elif "bottom" in pos:
            if "left" in pos:
                spos = 3
            elif "right" in pos:
                spos = 4
        pos = spos
    if pos == 1:
        xyplot.GetPositionCoordinate().SetValue(0.0, 0.8, 0)
    elif pos == 2:
        xyplot.GetPositionCoordinate().SetValue(0.76, 0.8, 0)
    elif pos == 3:
        xyplot.GetPositionCoordinate().SetValue(0.0, 0.0, 0)
    elif pos == 4:
        xyplot.GetPositionCoordinate().SetValue(0.76, 0.0, 0)
    else:
        xyplot.GetPositionCoordinate().SetValue(pos[0], pos[1], 0)

    xyplot.GetPosition2Coordinate().SetValue(s, s, 0)
    return xyplot

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)

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

Returns an Assembly object.

Parameters:

Name Type Description Default
M list, numpy array

the input array to visualize

required
title str

title of the plot

'Matrix'
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

0
cmap str

color map name

'Reds'
vmin float

minimum value of the colormap range

None
vmax float

maximum value of the colormap range

None
precision int

number of digits for the matrix entries or bins

2
font str

font name. Check available fonts here.

'Theemim'
scale float

size of the numeric entries or bin values

0
scalarbar bool

add a scalar bar to the right of the plot

True
lc str

color of the line separating the bins

'white'
lw float

Width of the line separating the bins

0
c str

text color

'black'
alpha float

plot transparency

1

Examples:

Source code in vedo/pyplot/stats.py
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
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,
) -> Assembly:
    """
    Generate a matrix, or a 2D color-coded plot with bin labels.

    Returns an `Assembly` object.

    Args:
        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](https://vedo.embl.es/fonts).

        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:
        - [np_matrix.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/np_matrix.py)

            ![](https://vedo.embl.es/images/pyplot/np_matrix.png)
    """
    M = np.asarray(M)
    n, m = M.shape
    gr = shapes.Grid(res=(m, n), s=(m / (m + n) * 2, n / (m + n) * 2), c=c, alpha=alpha)
    gr.wireframe(False).lc(lc).lw(lw)

    matr = np.flip(np.flip(M), axis=1).ravel(order="C")
    gr.cmap(cmap, matr, on="cells", vmin=vmin, vmax=vmax)
    sbar = None
    if scalarbar:
        gr.add_scalarbar3d(title_font=font, label_font=font)
        sbar = gr.scalarbar
    labs = None
    if scale != 0:
        gr.compute_normals(points=False)
        labs = gr.labels(
            on="cells",
            scale=scale / max(m, n),
            precision=precision,
            font=font,
            justify="center",
            c=c,
        )
        labs.z(0.001)
    t = None
    if title:
        if title == "Matrix":
            title += " " + str(n) + "x" + str(m)
        t = shapes.Text3D(title, font=font, s=0.04, justify="bottom-center", c=c)
        t.shift(0, n / (m + n) * 1.05)

    xlabs = None
    if len(xlabels) == m:
        xlabs = []
        jus = "top-center"
        if xrotation > 44:
            jus = "right-center"
        for i in range(m):
            xl = shapes.Text3D(
                xlabels[i], font=font, s=0.02, justify=jus, c=c
            ).rotate_z(xrotation)
            xl.shift((2 * i - m + 1) / (m + n), -n / (m + n) * 1.05)
            xlabs.append(xl)

    ylabs = None
    if len(ylabels) == n:
        ylabels = list(reversed(ylabels))
        ylabs = []
        for i in range(n):
            yl = shapes.Text3D(
                ylabels[i], font=font, s=0.02, justify="right-center", c=c
            )
            yl.shift(-m / (m + n) * 1.05, (2 * i - n + 1) / (m + n))
            ylabs.append(yl)

    xt = None
    if xtitle:
        xt = shapes.Text3D(xtitle, font=font, s=0.035, justify="top-center", c=c)
        xt.shift(0, -n / (m + n) * 1.05)
        if xlabs is not None:
            y0, y1 = xlabs[0].ybounds()
            xt.shift(0, -(y1 - y0) - 0.55 / (m + n))
    yt = None
    if ytitle:
        yt = shapes.Text3D(
            ytitle, font=font, s=0.035, justify="bottom-center", c=c
        ).rotate_z(90)
        yt.shift(-m / (m + n) * 1.05, 0)
        if ylabs is not None:
            x0, x1 = ylabs[0].xbounds()
            yt.shift(-(x1 - x0) - 0.55 / (m + n), 0)
    asse = Assembly(gr, sbar, labs, t, xt, yt, xlabs, ylabs)
    asse.name = "Matrix"
    return asse

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)

Donut plot or pie chart.

Parameters:

Name Type Description Default
title str

plot title

''
tsize float

title size

0.3
r1

(float) inner radius

required
r2 float

outer radius, starting from r1

1
phigap float

gap angle btw 2 radial bars, in degrees

0
lpos float

label gap factor along radius

0.8
lsize float

label size

0.15
c color

color of the plot slices

None
bc color

color of the disc frame

'k'
alpha float

opacity of the disc frame

1
labels list

list of labels

()
show_disc bool

show the outer ring axis

False

Examples:

Source code in vedo/pyplot/stats.py
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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,
) -> Assembly:
    """
    Donut plot or pie chart.

    Args:
        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:
        - [donut.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/donut.py)

            ![](https://vedo.embl.es/images/pyplot/donut.png)
    """
    fractions = np.array(fractions, dtype=float)
    angles = np.add.accumulate(2 * np.pi * fractions)
    angles[-1] = 2 * np.pi
    if angles[-2] > 2 * np.pi:
        print("Error in donut(): fractions must sum to 1.")
        raise RuntimeError

    cols = []
    for i, th in enumerate(np.linspace(0, 2 * np.pi, 360, endpoint=False)):
        for ia, a in enumerate(angles):
            if th < a:
                cols.append(c[ia])
                break
    labs = []
    if labels:
        angles = np.concatenate([[0], angles])
        labs = [""] * 360
        for i in range(len(labels)):
            a = (angles[i + 1] + angles[i]) / 2
            j = int(a / np.pi * 180)
            labs[j] = labels[i]

    data = np.linspace(0, 2 * np.pi, 360, endpoint=False) + 0.005
    dn = _histogram_polar(
        data,
        title=title,
        bins=360,
        r1=r1,
        r2=r2,
        phigap=phigap,
        lpos=lpos,
        lsize=lsize,
        tsize=tsize,
        c=cols,
        bc=bc,
        alpha=alpha,
        vmin=0,
        vmax=1,
        labels=labs,
        show_disc=show_disc,
        show_lines=0,
        show_angles=0,
        show_errors=0,
    )
    dn.name = "Donut"
    return dn

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)

Violin style histogram.

Parameters:

Name Type Description Default
bins int

number of bins

10
vlim list

input value limits. Crop values outside range

None
x float

x-position of the violin axis

0
width float

width factor of the normalized distribution

3
splined bool

spline the outline

True
fill bool

fill violin with solid color

True
outline bool

add the distribution outline

True
centerline bool

add the vertical centerline at x

True
lc color

line color

'darkorchid'

Examples:

Source code in vedo/pyplot/stats.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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,
) -> Assembly:
    """
    Violin style histogram.

    Args:
        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:
        - [histo_violin.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_violin.py)

            ![](https://vedo.embl.es/images/pyplot/histo_violin.png)
    """
    fs, edges = np.histogram(values, bins=bins, range=vlim)
    mine, maxe = np.min(edges), np.max(edges)
    fs = fs.astype(float) / len(values) * width

    rs = []

    if splined:
        lnl, lnr = [(0, edges[0], 0)], [(0, edges[0], 0)]
        for i in range(bins):
            xc = (edges[i] + edges[i + 1]) / 2
            yc = fs[i]
            lnl.append([-yc, xc, 0])
            lnr.append([yc, xc, 0])
        lnl.append((0, edges[-1], 0))
        lnr.append((0, edges[-1], 0))
        spl = shapes.KSpline(lnl).x(x)
        spr = shapes.KSpline(lnr).x(x)
        spl.color(lc).alpha(alpha).lw(lw)
        spr.color(lc).alpha(alpha).lw(lw)
        if outline:
            rs.append(spl)
            rs.append(spr)
        if fill:
            rb = shapes.Ribbon(spl, spr, c=c, alpha=alpha).lighting("off")
            rs.append(rb)

    else:
        lns1 = [[0, mine, 0]]
        for i in range(bins):
            lns1.append([fs[i], edges[i], 0])
            lns1.append([fs[i], edges[i + 1], 0])
        lns1.append([0, maxe, 0])

        lns2 = [[0, mine, 0]]
        for i in range(bins):
            lns2.append([-fs[i], edges[i], 0])
            lns2.append([-fs[i], edges[i + 1], 0])
        lns2.append([0, maxe, 0])

        if outline:
            rs.append(shapes.Line(lns1, c=lc, alpha=alpha, lw=lw).x(x))
            rs.append(shapes.Line(lns2, c=lc, alpha=alpha, lw=lw).x(x))

        if fill:
            for i in range(bins):
                p0 = (-fs[i], edges[i], 0)
                p1 = (fs[i], edges[i + 1], 0)
                r = shapes.Rectangle(p0, p1).x(p0[0] + x)
                r.color(c).alpha(alpha).lighting("off")
                rs.append(r)

    if centerline:
        cl = shapes.Line([0, mine, 0.01], [0, maxe, 0.01], c=lc, alpha=alpha, lw=2).x(x)
        rs.append(cl)

    asse = Assembly(rs)
    asse.name = "Violin"
    return asse

whisker(data, s=0.25, c='k', lw=2, bc='blue', alpha=0.25, r=5, jitter=True, horizontal=False)

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

Parameters:

Name Type Description Default
s float

size of the box

0.25
c color

color of the lines

'k'
lw float

line width

2
bc color

color of the box

'blue'
alpha float

transparency of the box

0.25
r float

point radius in pixels (use value 0 to disable)

5
jitter bool

add some randomness to points to avoid overlap

True
horizontal bool

set horizontal layout

False

Examples:

Source code in vedo/pyplot/stats.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
def whisker(
    data, s=0.25, c="k", lw=2, bc="blue", alpha=0.25, r=5, jitter=True, horizontal=False
) -> Assembly:
    """
    Generate a "whisker" bar from a 1-dimensional dataset.

    Args:
        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:
        - [whiskers.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/whiskers.py)

            ![](https://vedo.embl.es/images/pyplot/whiskers.png)
    """
    xvals = np.zeros_like(np.asarray(data))
    if jitter:
        xjit = np.random.randn(len(xvals)) * s / 9
        xjit = np.clip(xjit, -s / 2.1, s / 2.1)
        xvals += xjit

    dmean = np.mean(data)
    dq05 = np.quantile(data, 0.05)
    dq25 = np.quantile(data, 0.25)
    dq75 = np.quantile(data, 0.75)
    dq95 = np.quantile(data, 0.95)

    pts = None
    if r:
        pts = Points(np.array([xvals, data]).T, c=c, r=r)

    rec = shapes.Rectangle([-s / 2, dq25], [s / 2, dq75], c=bc, alpha=alpha)
    rec.properties.LightingOff()
    rl = shapes.Line(
        [[-s / 2, dq25], [s / 2, dq25], [s / 2, dq75], [-s / 2, dq75]], closed=True
    )
    l1 = shapes.Line([0, dq05, 0], [0, dq25, 0], c=c, lw=lw)
    l2 = shapes.Line([0, dq75, 0], [0, dq95, 0], c=c, lw=lw)
    lm = shapes.Line([-s / 2, dmean], [s / 2, dmean])
    lns = merge(l1, l2, lm, rl)
    asse = Assembly([lns, rec, pts])
    if horizontal:
        asse.rotate_z(-90)
    asse.name = "Whisker"
    asse.info["mean"] = dmean
    asse.info["quantile_05"] = dq05
    asse.info["quantile_25"] = dq25
    asse.info["quantile_75"] = dq75
    asse.info["quantile_95"] = dq95
    return asse

graph

DirectedGraph

Bases: Assembly

Support for Directed Graphs.

Source code in vedo/pyplot/graph.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
class DirectedGraph(Assembly):
    """
    Support for Directed Graphs.
    """

    def __init__(self, **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.

        Args:
            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.

        ---------------------------------------------------------------
        .. note:: Options for layouts '2d', 'fast2d' and 'clustering2d'

        Args:
            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.

        ---------------------------------------------------------------
        .. note:: Options for layouts 'circular', and 'circular3d':

        Args:
            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

        ---------------------------------------------------------------
        .. note:: Options for layout 'cone'

        Args:
            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

        ---------------------------------------------------------------
        .. note:: Options for layout 'force'

        Args:
            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:
            - [lineage_graph.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/graph_lineage.py)

                ![](https://vedo.embl.es/images/pyplot/graph_lineage.png)

            - [graph_network.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/graph_network.py)

                ![](https://vedo.embl.es/images/pyplot/graph_network.png)
        """

        super().__init__()

        self.nodes = []
        self.edges = []

        self._node_labels = []  # holds strings
        self._edge_labels = []
        self.edge_orientations = []
        self.edge_glyph_position = 0.6

        self.zrange = 0.0

        self.rotX = 0
        self.rotY = 0
        self.rotZ = 0

        self.arrow_scale = 0.15
        self.node_label_scale = None
        self.node_label_justify = "bottom-left"

        self.edge_label_scale = None

        self.mdg = vtki.new("MutableDirectedGraph")

        n = kargs.pop("n", 0)
        for _ in range(n):
            self.add_node()

        self._c = kargs.pop("c", (0.3, 0.3, 0.3))

        self.gl = vtki.new("GraphLayout")

        self.font = kargs.pop("font", "")

        s = kargs.pop("layout", "2d")
        if isinstance(s, int):
            ss = [
                "2d",
                "fast2d",
                "clustering2d",
                "circular",
                "circular3d",
                "cone",
                "force",
                "tree",
            ]
            s = ss[s]
        self.layout = s

        if "2d" in s:
            if "clustering" in s:
                self.strategy = vtki.new("Clustering2DLayoutStrategy")
            elif "fast" in s:
                self.strategy = vtki.new("Fast2DLayoutStrategy")
            else:
                self.strategy = vtki.new("Simple2DLayoutStrategy")
            self.rotX = 180
            opt = kargs.pop("rest_distance", None)
            if opt is not None:
                self.strategy.SetRestDistance(opt)
            opt = kargs.pop("seed", None)
            if opt is not None:
                self.strategy.SetRandomSeed(opt)
            opt = kargs.pop("nmax", None)
            if opt is not None:
                self.strategy.SetMaxNumberOfIterations(opt)
            self.zrange = kargs.pop("zrange", 0)

        elif "circ" in s:
            if "3d" in s:
                self.strategy = vtki.new("Simple3DCirclesStrategy")
                self.strategy.SetDirection(0, 0, -1)
                self.strategy.SetAutoHeight(True)
                self.strategy.SetMethod(1)
                self.rotX = -90
                opt = kargs.pop("radius", None)  # float
                if opt is not None:
                    self.strategy.SetMethod(0)
                    self.strategy.SetRadius(opt)  # float
                opt = kargs.pop("height", None)
                if opt is not None:
                    self.strategy.SetAutoHeight(False)
                    self.strategy.SetHeight(opt)  # float
            else:
                self.strategy = vtki.new("CircularLayoutStrategy")
                self.zrange = kargs.pop("zrange", 0)

        elif "cone" in s:
            self.strategy = vtki.new("ConeLayoutStrategy")
            self.rotX = 180
            opt = kargs.pop("compactness", None)
            if opt is not None:
                self.strategy.SetCompactness(opt)
            opt = kargs.pop("compression", None)
            if opt is not None:
                self.strategy.SetCompression(opt)
            opt = kargs.pop("spacing", None)
            if opt is not None:
                self.strategy.SetSpacing(opt)

        elif "force" in s:
            self.strategy = vtki.new("ForceDirectedLayoutStrategy")
            opt = kargs.pop("seed", None)
            if opt is not None:
                self.strategy.SetRandomSeed(opt)
            opt = kargs.pop("bounds", None)
            if opt is not None:
                self.strategy.SetAutomaticBoundsComputation(False)
                self.strategy.SetGraphBounds(opt)  # list
            opt = kargs.pop("nmax", None)
            if opt is not None:
                self.strategy.SetMaxNumberOfIterations(opt)  # int
            opt = kargs.pop("three_dimensional", True)
            if opt is not None:
                self.strategy.SetThreeDimensionalLayout(opt)  # bool
            opt = kargs.pop("random_initial_points", None)
            if opt is not None:
                self.strategy.SetRandomInitialPoints(opt)  # bool

        elif "tree" in s:
            self.strategy = vtki.new("SpanTreeLayoutStrategy")
            self.rotX = 180

        else:
            vedo.logger.error(f"Cannot understand layout {s}. Available layouts:")
            vedo.logger.error(
                "[2d,fast2d,clustering2d,circular,circular3d,cone,force,tree]"
            )
            raise RuntimeError()

        self.gl.SetLayoutStrategy(self.strategy)

        if len(kargs) > 0:
            vedo.logger.error(f"Cannot understand options: {kargs}")

    def add_node(self, label="id") -> int:
        """Add a new node to the `Graph`."""
        v = self.mdg.AddVertex()  # vtk calls it vertex..
        self.nodes.append(v)
        if label == "id":
            label = int(v)
        self._node_labels.append(str(label))
        return v

    def add_edge(self, v1, v2, label="") -> int:
        """Add a new edge between to nodes.
        An extra node is created automatically if needed."""
        nv = len(self.nodes)
        if v1 >= nv:
            for _ in range(nv, v1 + 1):
                self.add_node()
        nv = len(self.nodes)
        if v2 >= nv:
            for _ in range(nv, v2 + 1):
                self.add_node()
        e = self.mdg.AddEdge(v1, v2)
        self.edges.append(e)
        self._edge_labels.append(str(label))
        return e

    def add_child(self, v, node_label="id", edge_label="") -> int:
        """Add a new edge to a new node as its child.
        The extra node is created automatically if needed."""
        nv = len(self.nodes)
        if v >= nv:
            for _ in range(nv, v + 1):
                self.add_node()
        child = self.mdg.AddChild(v)
        self.edges.append((v, child))
        self.nodes.append(child)
        if node_label == "id":
            node_label = int(child)
        self._node_labels.append(str(node_label))
        self._edge_labels.append(str(edge_label))
        return child

    def build(self):
        """
        Build the `DirectedGraph(Assembly)`.
        Accessory objects are also created for labels and arrows.
        """
        self.gl.SetZRange(self.zrange)
        self.gl.SetInputData(self.mdg)
        self.gl.Update()

        gr2poly = vtki.new("GraphToPolyData")
        gr2poly.EdgeGlyphOutputOn()
        gr2poly.SetEdgeGlyphPosition(self.edge_glyph_position)
        gr2poly.SetInputData(self.gl.GetOutput())
        gr2poly.Update()

        dgraph = Mesh(gr2poly.GetOutput(0))
        # dgraph.clean() # WRONG!!! dont uncomment
        dgraph.flat().color(self._c).lw(2)
        dgraph.name = "DirectedGraph"

        diagsz = self.diagonal_size() / 1.42
        if not diagsz:
            return None

        dgraph.scale(1 / diagsz)
        if self.rotX:
            dgraph.rotate_x(self.rotX)
        if self.rotY:
            dgraph.rotate_y(self.rotY)
        if self.rotZ:
            dgraph.rotate_z(self.rotZ)

        vecs = gr2poly.GetOutput(1).GetPointData().GetVectors()
        self.edge_orientations = utils.vtk2numpy(vecs)

        # Use Glyph3D to repeat the glyph on all edges.
        arrows = None
        if self.arrow_scale:
            arrow_source = vtki.new("GlyphSource2D")
            arrow_source.SetGlyphTypeToEdgeArrow()
            arrow_source.SetScale(self.arrow_scale)
            arrow_source.Update()
            arrow_glyph = vtki.vtkGlyph3D()
            arrow_glyph.SetInputData(0, gr2poly.GetOutput(1))
            arrow_glyph.SetInputData(1, arrow_source.GetOutput())
            arrow_glyph.Update()
            arrows = Mesh(arrow_glyph.GetOutput())
            arrows.scale(1 / diagsz)
            arrows.lighting("off").color(self._c)
            if self.rotX:
                arrows.rotate_x(self.rotX)
            if self.rotY:
                arrows.rotate_y(self.rotY)
            if self.rotZ:
                arrows.rotate_z(self.rotZ)
            arrows.name = "DirectedGraphArrows"

        node_labels = None
        if self._node_labels:
            node_labels = dgraph.labels(
                self._node_labels,
                scale=self.node_label_scale,
                precision=0,
                font=self.font,
                justify=self.node_label_justify,
            )
            node_labels.color(self._c).pickable(True)
            node_labels.name = "DirectedGraphNodeLabels"

        edge_labels = None
        if self._edge_labels:
            edge_labels = dgraph.labels(
                self._edge_labels,
                on="cells",
                scale=self.edge_label_scale,
                precision=0,
                font=self.font,
            )
            edge_labels.color(self._c).pickable(True)
            edge_labels.name = "DirectedGraphEdgeLabels"

        super().__init__([dgraph, node_labels, edge_labels, arrows])
        self.name = "DirectedGraphAssembly"
        return self

add_child(v, node_label='id', edge_label='')

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

Source code in vedo/pyplot/graph.py
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
def add_child(self, v, node_label="id", edge_label="") -> int:
    """Add a new edge to a new node as its child.
    The extra node is created automatically if needed."""
    nv = len(self.nodes)
    if v >= nv:
        for _ in range(nv, v + 1):
            self.add_node()
    child = self.mdg.AddChild(v)
    self.edges.append((v, child))
    self.nodes.append(child)
    if node_label == "id":
        node_label = int(child)
    self._node_labels.append(str(node_label))
    self._edge_labels.append(str(edge_label))
    return child

add_edge(v1, v2, label='')

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

Source code in vedo/pyplot/graph.py
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
def add_edge(self, v1, v2, label="") -> int:
    """Add a new edge between to nodes.
    An extra node is created automatically if needed."""
    nv = len(self.nodes)
    if v1 >= nv:
        for _ in range(nv, v1 + 1):
            self.add_node()
    nv = len(self.nodes)
    if v2 >= nv:
        for _ in range(nv, v2 + 1):
            self.add_node()
    e = self.mdg.AddEdge(v1, v2)
    self.edges.append(e)
    self._edge_labels.append(str(label))
    return e

add_node(label='id')

Add a new node to the Graph.

Source code in vedo/pyplot/graph.py
247
248
249
250
251
252
253
254
def add_node(self, label="id") -> int:
    """Add a new node to the `Graph`."""
    v = self.mdg.AddVertex()  # vtk calls it vertex..
    self.nodes.append(v)
    if label == "id":
        label = int(v)
    self._node_labels.append(str(label))
    return v

build()

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

Source code in vedo/pyplot/graph.py
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
def build(self):
    """
    Build the `DirectedGraph(Assembly)`.
    Accessory objects are also created for labels and arrows.
    """
    self.gl.SetZRange(self.zrange)
    self.gl.SetInputData(self.mdg)
    self.gl.Update()

    gr2poly = vtki.new("GraphToPolyData")
    gr2poly.EdgeGlyphOutputOn()
    gr2poly.SetEdgeGlyphPosition(self.edge_glyph_position)
    gr2poly.SetInputData(self.gl.GetOutput())
    gr2poly.Update()

    dgraph = Mesh(gr2poly.GetOutput(0))
    # dgraph.clean() # WRONG!!! dont uncomment
    dgraph.flat().color(self._c).lw(2)
    dgraph.name = "DirectedGraph"

    diagsz = self.diagonal_size() / 1.42
    if not diagsz:
        return None

    dgraph.scale(1 / diagsz)
    if self.rotX:
        dgraph.rotate_x(self.rotX)
    if self.rotY:
        dgraph.rotate_y(self.rotY)
    if self.rotZ:
        dgraph.rotate_z(self.rotZ)

    vecs = gr2poly.GetOutput(1).GetPointData().GetVectors()
    self.edge_orientations = utils.vtk2numpy(vecs)

    # Use Glyph3D to repeat the glyph on all edges.
    arrows = None
    if self.arrow_scale:
        arrow_source = vtki.new("GlyphSource2D")
        arrow_source.SetGlyphTypeToEdgeArrow()
        arrow_source.SetScale(self.arrow_scale)
        arrow_source.Update()
        arrow_glyph = vtki.vtkGlyph3D()
        arrow_glyph.SetInputData(0, gr2poly.GetOutput(1))
        arrow_glyph.SetInputData(1, arrow_source.GetOutput())
        arrow_glyph.Update()
        arrows = Mesh(arrow_glyph.GetOutput())
        arrows.scale(1 / diagsz)
        arrows.lighting("off").color(self._c)
        if self.rotX:
            arrows.rotate_x(self.rotX)
        if self.rotY:
            arrows.rotate_y(self.rotY)
        if self.rotZ:
            arrows.rotate_z(self.rotZ)
        arrows.name = "DirectedGraphArrows"

    node_labels = None
    if self._node_labels:
        node_labels = dgraph.labels(
            self._node_labels,
            scale=self.node_label_scale,
            precision=0,
            font=self.font,
            justify=self.node_label_justify,
        )
        node_labels.color(self._c).pickable(True)
        node_labels.name = "DirectedGraphNodeLabels"

    edge_labels = None
    if self._edge_labels:
        edge_labels = dgraph.labels(
            self._edge_labels,
            on="cells",
            scale=self.edge_label_scale,
            precision=0,
            font=self.font,
        )
        edge_labels.color(self._c).pickable(True)
        edge_labels.name = "DirectedGraphEdgeLabels"

    super().__init__([dgraph, node_labels, edge_labels, arrows])
    self.name = "DirectedGraphAssembly"
    return self