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