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 data = data[~np.isnan(data).any(axis=1), :] 1573 1574 fig_kwargs["title"] = title 1575 fig_kwargs["xtitle"] = xtitle 1576 fig_kwargs["ytitle"] = ytitle 1577 fig_kwargs["ac"] = ac 1578 fig_kwargs["ztolerance"] = ztolerance 1579 fig_kwargs["grid"] = grid 1580 1581 x0, y0 = np.min(data, axis=0) 1582 x1, y1 = np.max(data, axis=0) 1583 if xerrors is not None and not error_band: 1584 x0 = min(data[:, 0] - xerrors) 1585 x1 = max(data[:, 0] + xerrors) 1586 if yerrors is not None: 1587 y0 = min(data[:, 1] - yerrors) 1588 y1 = max(data[:, 1] + yerrors) 1589 1590 if like is None: 1591 if xlim is None: 1592 xlim = (None, None) 1593 xlim = list(xlim) 1594 if xlim[0] is None: 1595 xlim[0] = x0 1596 if xlim[1] is None: 1597 xlim[1] = x1 1598 ylim = list(ylim) 1599 if ylim[0] is None: 1600 ylim[0] = y0 1601 if ylim[1] is None: 1602 ylim[1] = y1 1603 1604 self.entries = len(data) 1605 self.mean = data.mean() 1606 self.std = data.std() 1607 1608 self.ztolerance = 0 1609 1610 ######### the PlotXY marker 1611 # fall back solutions logic for colors 1612 if "c" in fig_kwargs: 1613 if mc is None: 1614 mc = fig_kwargs["c"] 1615 if lc is None: 1616 lc = fig_kwargs["c"] 1617 if ec is None: 1618 ec = fig_kwargs["c"] 1619 if lc is None: 1620 lc = "k" 1621 if mc is None: 1622 mc = lc 1623 if ma is None: 1624 ma = la 1625 if ec is None: 1626 if mc is None: 1627 ec = lc 1628 else: 1629 ec = mc 1630 1631 if label: 1632 nlab = LabelData() 1633 nlab.text = label 1634 nlab.tcolor = ac 1635 nlab.marker = marker 1636 if line and marker == "": 1637 nlab.marker = "-" 1638 nlab.mcolor = mc 1639 fig_kwargs["label"] = nlab 1640 1641 ############################################### Figure init 1642 super().__init__(xlim, ylim, aspect, padding, **fig_kwargs) 1643 1644 if not self.yscale: 1645 return 1646 1647 acts = [] 1648 1649 ######### the PlotXY Line or Spline 1650 if dashed: 1651 l = shapes.DashedLine(data, c=lc, alpha=la, lw=lw) 1652 acts.append(l) 1653 elif splined: 1654 l = shapes.KSpline(data).lw(lw).c(lc).alpha(la) 1655 acts.append(l) 1656 elif line: 1657 l = shapes.Line(data, c=lc, alpha=la).lw(lw) 1658 acts.append(l) 1659 1660 if marker: 1661 1662 pts = np.c_[data, np.zeros(len(data))] 1663 1664 if utils.is_sequence(ms): 1665 ### variable point size 1666 mk = shapes.Marker(marker, s=1) 1667 mk.scale([1, 1 / self.yscale, 1]) 1668 msv = np.zeros_like(pts) 1669 msv[:, 0] = ms 1670 marked = shapes.Glyph( 1671 pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True 1672 ) 1673 else: 1674 ### fixed point size 1675 if ms is None: 1676 ms = (xlim[1] - xlim[0]) / 100.0 1677 1678 if utils.is_sequence(mc): 1679 fig_kwargs["marker_color"] = None # for labels 1680 mk = shapes.Marker(marker, s=ms) 1681 mk.scale([1, 1 / self.yscale, 1]) 1682 msv = np.zeros_like(pts) 1683 msv[:, 0] = 1 1684 marked = shapes.Glyph( 1685 pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True 1686 ) 1687 else: 1688 mk = shapes.Marker(marker, s=ms) 1689 mk.scale([1, 1 / self.yscale, 1]) 1690 marked = shapes.Glyph(pts, mk, c=mc) 1691 1692 marked.name = "Marker" 1693 marked.alpha(ma) 1694 marked.z(3 * self.ztolerance) 1695 acts.append(marked) 1696 1697 ######### the PlotXY marker errors 1698 ztol = self.ztolerance 1699 1700 if error_band: 1701 yerrors = np.abs(yerrors) 1702 du = np.array(data) 1703 dd = np.array(data) 1704 du[:, 1] += yerrors 1705 dd[:, 1] -= yerrors 1706 if splined: 1707 res = len(data) * 20 1708 band1 = shapes.KSpline(du, res=res) 1709 band2 = shapes.KSpline(dd, res=res) 1710 band = shapes.Ribbon(band1, band2, res=(res, 2)) 1711 else: 1712 dd = list(reversed(dd.tolist())) 1713 band = shapes.Line(du.tolist() + dd, closed=True) 1714 band.triangulate().lw(0) 1715 if ec is None: 1716 band.c(lc) 1717 else: 1718 band.c(ec) 1719 band.lighting("off").alpha(la).z(ztol / 20) 1720 acts.append(band) 1721 1722 else: 1723 1724 ## xerrors 1725 if xerrors is not None: 1726 if len(xerrors) == len(data): 1727 errs = [] 1728 for i, val in enumerate(data): 1729 xval, yval = val 1730 xerr = xerrors[i] / 2 1731 el = shapes.Line((xval - xerr, yval, ztol), (xval + xerr, yval, ztol)) 1732 el.lw(elw) 1733 errs.append(el) 1734 mxerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol) 1735 acts.append(mxerrs) 1736 else: 1737 vedo.logger.error("in PlotXY(xerrors=...): mismatch in array length") 1738 1739 ## yerrors 1740 if yerrors is not None: 1741 if len(yerrors) == len(data): 1742 errs = [] 1743 for i, val in enumerate(data): 1744 xval, yval = val 1745 yerr = yerrors[i] 1746 el = shapes.Line((xval, yval - yerr, ztol), (xval, yval + yerr, ztol)) 1747 el.lw(elw) 1748 errs.append(el) 1749 myerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol) 1750 acts.append(myerrs) 1751 else: 1752 vedo.logger.error("in PlotXY(yerrors=...): mismatch in array length") 1753 1754 self.insert(*acts, as3d=False) 1755 self.name = "PlotXY" 1756 1757 1758def plot(*args, **kwargs): 1759 """ 1760 Draw a 2D line plot, or scatter plot, of variable x vs variable y. 1761 Input format can be either `[allx], [allx, ally] or [(x1,y1), (x2,y2), ...]` 1762 1763 Use `like=...` if you want to use the same format of a previously 1764 created Figure (useful when superimposing Figures) to make sure 1765 they are compatible and comparable. If they are not compatible 1766 you will receive an error message. 1767 1768 Arguments: 1769 xerrors : (bool) 1770 show error bars associated to each point in x 1771 yerrors : (bool) 1772 show error bars associated to each point in y 1773 lw : (int) 1774 width of the line connecting points in pixel units. 1775 Set it to 0 to remove the line. 1776 lc : (str) 1777 line color 1778 la : (float) 1779 line "alpha", opacity of the line 1780 dashed : (bool) 1781 draw a dashed line instead of a continuous line 1782 splined : (bool) 1783 spline the line joining the point as a countinous curve 1784 elw : (int) 1785 width of error bar lines in units of pixels 1786 ec : (color) 1787 color of error bar, by default the same as marker color 1788 error_band : (bool) 1789 represent errors on y as a filled error band. 1790 Use `ec` keyword to modify its color. 1791 marker : (str, int) 1792 use a marker for the data points 1793 ms : (float) 1794 marker size 1795 mc : (color) 1796 color of the marker 1797 ma : (float) 1798 opacity of the marker 1799 xlim : (list) 1800 set limits to the range for the x variable 1801 ylim : (list) 1802 set limits to the range for the y variable 1803 aspect : (float) 1804 Desired aspect ratio. 1805 If None, it is automatically calculated to get a reasonable aspect ratio. 1806 Scaling factor is saved in Figure.yscale 1807 padding : (float, list) 1808 keep a padding space from the axes (as a fraction of the axis size). 1809 This can be a list of four numbers. 1810 title : (str) 1811 title to appear on the top of the frame, like a header. 1812 xtitle : (str) 1813 title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")` 1814 ytitle : (str) 1815 title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")` 1816 ac : (str) 1817 axes color 1818 grid : (bool) 1819 show the background grid for the axes, can also be set using `axes=dict(xygrid=True)` 1820 ztolerance : (float) 1821 a tolerance factor to superimpose objects (along the z-axis). 1822 1823 Example: 1824 ```python 1825 import numpy as np 1826 from vedo.pyplot import plot 1827 from vedo import settings 1828 settings.remember_last_figure_format = True ############# 1829 x = np.linspace(0, 6.28, num=50) 1830 fig = plot(np.sin(x), 'r-') 1831 fig+= plot(np.cos(x), 'bo-') # no need to specify like=... 1832 fig.show().close() 1833 ``` 1834 <img src="https://user-images.githubusercontent.com/32848391/74363882-c3638300-4dcb-11ea-8a78-eb492ad9711f.png" width="600"> 1835 1836 Examples: 1837 - [plot_errbars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errbars.py) 1838 - [plot_errband.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errband.py) 1839 - [plot_pip.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_pip.py) 1840 1841 ![](https://vedo.embl.es/images/pyplot/plot_pip.png) 1842 1843 - [scatter1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter1.py) 1844 - [scatter2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter2.py) 1845 1846 1847 1848 ------------------------------------------------------------------------- 1849 .. note:: mode="bar" 1850 1851 Creates a `PlotBars(Figure)` object. 1852 1853 Input must be in format `[counts, labels, colors, edges]`. 1854 Either or both `edges` and `colors` are optional and can be omitted. 1855 1856 Arguments: 1857 errors : (bool) 1858 show error bars 1859 logscale : (bool) 1860 use logscale on y-axis 1861 fill : (bool) 1862 fill bars with solid color `c` 1863 gap : (float) 1864 leave a small space btw bars 1865 radius : (float) 1866 border radius of the top of the histogram bar. Default value is 0.1. 1867 texture : (str) 1868 url or path to an image to be used as texture for the bin 1869 outline : (bool) 1870 show outline of the bins 1871 xtitle : (str) 1872 title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")` 1873 ytitle : (str) 1874 title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")` 1875 ac : (str) 1876 axes color 1877 padding : (float, list) 1878 keep a padding space from the axes (as a fraction of the axis size). 1879 This can be a list of four numbers. 1880 aspect : (float) 1881 the desired aspect ratio of the figure. Default is 4/3. 1882 grid : (bool) 1883 show the background grid for the axes, can also be set using `axes=dict(xygrid=True)` 1884 1885 Examples: 1886 - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py) 1887 - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py) 1888 - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py) 1889 - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py) 1890 1891 ![](https://vedo.embl.es/images/pyplot/histo_1D.png) 1892 1893 1894 ---------------------------------------------------------------------- 1895 .. note:: 2D functions 1896 1897 If input is an external function or a formula, draw the surface 1898 representing the function `f(x,y)`. 1899 1900 Arguments: 1901 x : (float) 1902 x range of values 1903 y : (float) 1904 y range of values 1905 zlimits : (float) 1906 limit the z range of the independent variable 1907 zlevels : (int) 1908 will draw the specified number of z-levels contour lines 1909 show_nan : (bool) 1910 show where the function does not exist as red points 1911 bins : (list) 1912 number of bins in x and y 1913 1914 Examples: 1915 - [plot_fxy1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy1.py) 1916 1917 ![](https://vedo.embl.es/images/pyplot/plot_fxy.png) 1918 1919 - [plot_fxy2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy2.py) 1920 1921 1922 -------------------------------------------------------------------- 1923 .. note:: mode="complex" 1924 1925 If `mode='complex'` draw the real value of the function and color map the imaginary part. 1926 1927 Arguments: 1928 cmap : (str) 1929 diverging color map (white means `imag(z)=0`) 1930 lw : (float) 1931 line with of the binning 1932 bins : (list) 1933 binning in x and y 1934 1935 Examples: 1936 - [plot_fxy.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy.py) 1937 1938 ![](https://user-images.githubusercontent.com/32848391/73392962-1709a300-42db-11ea-9278-30c9d6e5eeaa.png) 1939 1940 1941 -------------------------------------------------------------------- 1942 .. note:: mode="polar" 1943 1944 If `mode='polar'` input arrays are interpreted as a list of polar angles and radii. 1945 Build a polar (radar) plot by joining the set of points in polar coordinates. 1946 1947 Arguments: 1948 title : (str) 1949 plot title 1950 tsize : (float) 1951 title size 1952 bins : (int) 1953 number of bins in phi 1954 r1 : (float) 1955 inner radius 1956 r2 : (float) 1957 outer radius 1958 lsize : (float) 1959 label size 1960 c : (color) 1961 color of the line 1962 ac : (color) 1963 color of the frame and labels 1964 alpha : (float) 1965 opacity of the frame 1966 ps : (int) 1967 point size in pixels, if ps=0 no point is drawn 1968 lw : (int) 1969 line width in pixels, if lw=0 no line is drawn 1970 deg : (bool) 1971 input array is in degrees 1972 vmax : (float) 1973 normalize radius to this maximum value 1974 fill : (bool) 1975 fill convex area with solid color 1976 splined : (bool) 1977 interpolate the set of input points 1978 show_disc : (bool) 1979 draw the outer ring axis 1980 nrays : (int) 1981 draw this number of axis rays (continuous and dashed) 1982 show_lines : (bool) 1983 draw lines to the origin 1984 show_angles : (bool) 1985 draw angle values 1986 1987 Examples: 1988 - [histo_polar.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_polar.py) 1989 1990 ![](https://user-images.githubusercontent.com/32848391/64992590-7fc82400-d8d4-11e9-9c10-795f4756a73f.png) 1991 1992 1993 -------------------------------------------------------------------- 1994 .. note:: mode="spheric" 1995 1996 If `mode='spheric'` input must be an external function rho(theta, phi). 1997 A surface is created in spherical coordinates. 1998 1999 Return an `Figure(Assembly)` of 2 objects: the unit 2000 sphere (in wireframe representation) and the surface `rho(theta, phi)`. 2001 2002 Arguments: 2003 rfunc : function 2004 handle to a user defined function `rho(theta, phi)`. 2005 normalize : (bool) 2006 scale surface to fit inside the unit sphere 2007 res : (int) 2008 grid resolution of the unit sphere 2009 scalarbar : (bool) 2010 add a 3D scalarbar to the plot for radius 2011 c : (color) 2012 color of the unit sphere 2013 alpha : (float) 2014 opacity of the unit sphere 2015 cmap : (str) 2016 color map for the surface 2017 2018 Examples: 2019 - [plot_spheric.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_spheric.py) 2020 2021 ![](https://vedo.embl.es/images/pyplot/plot_spheric.png) 2022 """ 2023 mode = kwargs.pop("mode", "") 2024 if "spher" in mode: 2025 return _plot_spheric(args[0], **kwargs) 2026 2027 if "bar" in mode: 2028 return PlotBars(args[0], **kwargs) 2029 2030 if isinstance(args[0], str) or "function" in str(type(args[0])): 2031 if "complex" in mode: 2032 return _plot_fz(args[0], **kwargs) 2033 return _plot_fxy(args[0], **kwargs) 2034 2035 # grab the matplotlib-like options 2036 optidx = None 2037 for i, a in enumerate(args): 2038 if i > 0 and isinstance(a, str): 2039 optidx = i 2040 break 2041 if optidx: 2042 opts = args[optidx].replace(" ", "") 2043 if "--" in opts: 2044 opts = opts.replace("--", "") 2045 kwargs["dashed"] = True 2046 elif "-" in opts: 2047 opts = opts.replace("-", "") 2048 else: 2049 kwargs["lw"] = 0 2050 2051 symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"] 2052 2053 allcols = list(colors.colors.keys()) + list(colors.color_nicks.keys()) 2054 for cc in allcols: 2055 if cc == "o": 2056 continue 2057 if cc in opts: 2058 opts = opts.replace(cc, "") 2059 kwargs["lc"] = cc 2060 kwargs["mc"] = cc 2061 break 2062 2063 for ss in symbs: 2064 if ss in opts: 2065 opts = opts.replace(ss, "", 1) 2066 kwargs["marker"] = ss 2067 break 2068 2069 opts.replace(" ", "") 2070 if opts: 2071 vedo.logger.error(f"in plot(), could not understand option(s): {opts}") 2072 2073 if optidx == 1 or optidx is None: 2074 if utils.is_sequence(args[0][0]) and len(args[0][0]) > 1: 2075 # print('------------- case 1', 'plot([(x,y),..])') 2076 data = np.asarray(args[0]) # (x,y) 2077 x = np.asarray(data[:, 0]) 2078 y = np.asarray(data[:, 1]) 2079 2080 elif len(args) == 1 or optidx == 1: 2081 # print('------------- case 2', 'plot(x)') 2082 if "pandas" in str(type(args[0])): 2083 if "ytitle" not in kwargs: 2084 kwargs.update({"ytitle": args[0].name.replace("_", "_ ")}) 2085 x = np.linspace(0, len(args[0]), num=len(args[0])) 2086 y = np.asarray(args[0]).ravel() 2087 2088 elif utils.is_sequence(args[1]): 2089 # print('------------- case 3', 'plot(allx,ally)',str(type(args[0]))) 2090 if "pandas" in str(type(args[0])): 2091 if "xtitle" not in kwargs: 2092 kwargs.update({"xtitle": args[0].name.replace("_", "_ ")}) 2093 if "pandas" in str(type(args[1])): 2094 if "ytitle" not in kwargs: 2095 kwargs.update({"ytitle": args[1].name.replace("_", "_ ")}) 2096 x = np.asarray(args[0]).ravel() 2097 y = np.asarray(args[1]).ravel() 2098 2099 elif utils.is_sequence(args[0]) and utils.is_sequence(args[0][0]): 2100 # print('------------- case 4', 'plot([allx,ally])') 2101 x = np.asarray(args[0][0]).ravel() 2102 y = np.asarray(args[0][1]).ravel() 2103 2104 elif optidx == 2: 2105 # print('------------- case 5', 'plot(x,y)') 2106 x = np.asarray(args[0]).ravel() 2107 y = np.asarray(args[1]).ravel() 2108 2109 else: 2110 vedo.logger.error(f"plot(): Could not understand input arguments {args}") 2111 return None 2112 2113 if "polar" in mode: 2114 return _plot_polar(np.c_[x, y], **kwargs) 2115 2116 return PlotXY(np.c_[x, y], **kwargs) 2117 2118 2119def histogram(*args, **kwargs): 2120 """ 2121 Histogramming for 1D and 2D data arrays. 2122 2123 This is meant as a convenience function that creates the appropriate object 2124 based on the shape of the provided input data. 2125 2126 Use keyword `like=...` if you want to use the same format of a previously 2127 created Figure (useful when superimposing Figures) to make sure 2128 they are compatible and comparable. If they are not compatible 2129 you will receive an error message. 2130 2131 ------------------------------------------------------------------------- 2132 .. note:: default mode, for 1D arrays 2133 2134 Creates a `Histogram1D(Figure)` object. 2135 2136 Arguments: 2137 weights : (list) 2138 An array of weights, of the same shape as `data`. Each value in `data` 2139 only contributes its associated weight towards the bin count (instead of 1). 2140 bins : (int) 2141 number of bins 2142 vrange : (list) 2143 restrict the range of the histogram 2144 density : (bool) 2145 normalize the area to 1 by dividing by the nr of entries and bin size 2146 logscale : (bool) 2147 use logscale on y-axis 2148 fill : (bool) 2149 fill bars with solid color `c` 2150 gap : (float) 2151 leave a small space btw bars 2152 radius : (float) 2153 border radius of the top of the histogram bar. Default value is 0.1. 2154 texture : (str) 2155 url or path to an image to be used as texture for the bin 2156 outline : (bool) 2157 show outline of the bins 2158 errors : (bool) 2159 show error bars 2160 xtitle : (str) 2161 title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")` 2162 ytitle : (str) 2163 title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")` 2164 padding : (float, list) 2165 keep a padding space from the axes (as a fraction of the axis size). 2166 This can be a list of four numbers. 2167 aspect : (float) 2168 the desired aspect ratio of the histogram. Default is 4/3. 2169 grid : (bool) 2170 show the background grid for the axes, can also be set using `axes=dict(xygrid=True)` 2171 ztolerance : (float) 2172 a tolerance factor to superimpose objects (along the z-axis). 2173 2174 Examples: 2175 - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py) 2176 - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py) 2177 - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py) 2178 - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py) 2179 2180 ![](https://vedo.embl.es/images/pyplot/histo_1D.png) 2181 2182 2183 ------------------------------------------------------------------------- 2184 .. note:: default mode, for 2D arrays 2185 2186 Input data formats `[(x1,x2,..), (y1,y2,..)] or [(x1,y1), (x2,y2),..]` 2187 are both valid. 2188 2189 Arguments: 2190 bins : (list) 2191 binning as (nx, ny) 2192 weights : (list) 2193 array of weights to assign to each entry 2194 cmap : (str, lookuptable) 2195 color map name or look up table 2196 alpha : (float) 2197 opacity of the histogram 2198 gap : (float) 2199 separation between adjacent bins as a fraction for their size. 2200 Set gap=-1 to generate a quad surface. 2201 scalarbar : (bool) 2202 add a scalarbar to right of the histogram 2203 like : (Figure) 2204 grab and use the same format of the given Figure (for superimposing) 2205 xlim : (list) 2206 [x0, x1] range of interest. If left to None will automatically 2207 choose the minimum or the maximum of the data range. 2208 Data outside the range are completely ignored. 2209 ylim : (list) 2210 [y0, y1] range of interest. If left to None will automatically 2211 choose the minimum or the maximum of the data range. 2212 Data outside the range are completely ignored. 2213 aspect : (float) 2214 the desired aspect ratio of the figure. 2215 title : (str) 2216 title of the plot to appear on top. 2217 If left blank some statistics will be shown. 2218 xtitle : (str) 2219 x axis title 2220 ytitle : (str) 2221 y axis title 2222 ztitle : (str) 2223 title for the scalar bar 2224 ac : (str) 2225 axes color, additional keyword for Axes can also be added 2226 using e.g. `axes=dict(xygrid=True)` 2227 2228 Examples: 2229 - [histo_2d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_a.py) 2230 - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py) 2231 2232 ![](https://vedo.embl.es/images/pyplot/histo_2D.png) 2233 2234 2235 ------------------------------------------------------------------------- 2236 .. note:: mode="3d" 2237 2238 If `mode='3d'`, build a 2D histogram as 3D bars from a list of x and y values. 2239 2240 Arguments: 2241 xtitle : (str) 2242 x axis title 2243 bins : (int) 2244 nr of bins for the smaller range in x or y 2245 vrange : (list) 2246 range in x and y in format `[(xmin,xmax), (ymin,ymax)]` 2247 norm : (float) 2248 sets a scaling factor for the z axis (frequency axis) 2249 fill : (bool) 2250 draw solid hexagons 2251 cmap : (str) 2252 color map name for elevation 2253 gap : (float) 2254 keep a internal empty gap between bins [0,1] 2255 zscale : (float) 2256 rescale the (already normalized) zaxis for visual convenience 2257 2258 Examples: 2259 - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/histo_2d_b.py) 2260 2261 2262 ------------------------------------------------------------------------- 2263 .. note:: mode="hexbin" 2264 2265 If `mode='hexbin'`, build a hexagonal histogram from a list of x and y values. 2266 2267 Arguments: 2268 xtitle : (str) 2269 x axis title 2270 bins : (int) 2271 nr of bins for the smaller range in x or y 2272 vrange : (list) 2273 range in x and y in format `[(xmin,xmax), (ymin,ymax)]` 2274 norm : (float) 2275 sets a scaling factor for the z axis (frequency axis) 2276 fill : (bool) 2277 draw solid hexagons 2278 cmap : (str) 2279 color map name for elevation 2280 2281 Examples: 2282 - [histo_hexagonal.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_hexagonal.py) 2283 2284 ![](https://vedo.embl.es/images/pyplot/histo_hexagonal.png) 2285 2286 2287 ------------------------------------------------------------------------- 2288 .. note:: mode="polar" 2289 2290 If `mode='polar'` assume input is polar coordinate system (rho, theta): 2291 2292 Arguments: 2293 weights : (list) 2294 Array of weights, of the same shape as the input. 2295 Each value only contributes its associated weight towards the bin count (instead of 1). 2296 title : (str) 2297 histogram title 2298 tsize : (float) 2299 title size 2300 bins : (int) 2301 number of bins in phi 2302 r1 : (float) 2303 inner radius 2304 r2 : (float) 2305 outer radius 2306 phigap : (float) 2307 gap angle btw 2 radial bars, in degrees 2308 rgap : (float) 2309 gap factor along radius of numeric angle labels 2310 lpos : (float) 2311 label gap factor along radius 2312 lsize : (float) 2313 label size 2314 c : (color) 2315 color of the histogram bars, can be a list of length `bins` 2316 bc : (color) 2317 color of the frame and labels 2318 alpha : (float) 2319 opacity of the frame 2320 cmap : (str) 2321 color map name 2322 deg : (bool) 2323 input array is in degrees 2324 vmin : (float) 2325 minimum value of the radial axis 2326 vmax : (float) 2327 maximum value of the radial axis 2328 labels : (list) 2329 list of labels, must be of length `bins` 2330 show_disc : (bool) 2331 show the outer ring axis 2332 nrays : (int) 2333 draw this number of axis rays (continuous and dashed) 2334 show_lines : (bool) 2335 show lines to the origin 2336 show_angles : (bool) 2337 show angular values 2338 show_errors : (bool) 2339 show error bars 2340 2341 Examples: 2342 - [histo_polar.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_polar.py) 2343 2344 ![](https://vedo.embl.es/images/pyplot/histo_polar.png) 2345 2346 2347 ------------------------------------------------------------------------- 2348 .. note:: mode="spheric" 2349 2350 If `mode='spheric'`, build a histogram from list of theta and phi values. 2351 2352 Arguments: 2353 rmax : (float) 2354 maximum radial elevation of bin 2355 res : (int) 2356 sphere resolution 2357 cmap : (str) 2358 color map name 2359 lw : (int) 2360 line width of the bin edges 2361 2362 Examples: 2363 - [histo_spheric.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_spheric.py) 2364 2365 ![](https://vedo.embl.es/images/pyplot/histo_spheric.png) 2366 """ 2367 mode = kwargs.pop("mode", "") 2368 if len(args) == 2: # x, y 2369 2370 if "spher" in mode: 2371 return _histogram_spheric(args[0], args[1], **kwargs) 2372 2373 if "hex" in mode: 2374 return _histogram_hex_bin(args[0], args[1], **kwargs) 2375 2376 if "3d" in mode.lower(): 2377 return _histogram_quad_bin(args[0], args[1], **kwargs) 2378 2379 return Histogram2D(args[0], args[1], **kwargs) 2380 2381 elif len(args) == 1: 2382 2383 if isinstance(args[0], vedo.Volume): 2384 data = args[0].pointdata[0] 2385 elif isinstance(args[0], vedo.Points): 2386 pd0 = args[0].pointdata[0] 2387 if pd0 is not None: 2388 data = pd0.ravel() 2389 else: 2390 data = args[0].celldata[0].ravel() 2391 else: 2392 try: 2393 if "pandas" in str(type(args[0])): 2394 if "xtitle" not in kwargs: 2395 kwargs.update({"xtitle": args[0].name.replace("_", "_ ")}) 2396 except: 2397 pass 2398 data = np.asarray(args[0]) 2399 2400 if "spher" in mode: 2401 return _histogram_spheric(args[0][:, 0], args[0][:, 1], **kwargs) 2402 2403 if data.ndim == 1: 2404 if "polar" in mode: 2405 return _histogram_polar(data, **kwargs) 2406 return Histogram1D(data, **kwargs) 2407 2408 if "hex" in mode: 2409 return _histogram_hex_bin(args[0][:, 0], args[0][:, 1], **kwargs) 2410 2411 if "3d" in mode.lower(): 2412 return _histogram_quad_bin(args[0][:, 0], args[0][:, 1], **kwargs) 2413 2414 return Histogram2D(args[0], **kwargs) 2415 2416 vedo.logger.error(f"in histogram(): could not understand input {args[0]}") 2417 return None 2418 2419 2420def fit( 2421 points, deg=1, niter=0, nstd=3, xerrors=None, yerrors=None, vrange=None, res=250, lw=3, c="red4" 2422) -> "vedo.shapes.Line": 2423 """ 2424 Polynomial fitting with parameter error and error bands calculation. 2425 Errors bars in both x and y are supported. 2426 2427 Returns a `vedo.shapes.Line` object. 2428 2429 Additional information about the fitting output can be accessed with: 2430 2431 `fitd = fit(pts)` 2432 2433 - `fitd.coefficients` will contain the coefficients of the polynomial fit 2434 - `fitd.coefficient_errors`, errors on the fitting coefficients 2435 - `fitd.monte_carlo_coefficients`, fitting coefficient set from MC generation 2436 - `fitd.covariance_matrix`, covariance matrix as a numpy array 2437 - `fitd.reduced_chi2`, reduced chi-square of the fitting 2438 - `fitd.ndof`, number of degrees of freedom 2439 - `fitd.data_sigma`, mean data dispersion from the central fit assuming `Chi2=1` 2440 - `fitd.error_lines`, a `vedo.shapes.Line` object for the upper and lower error band 2441 - `fitd.error_band`, the `vedo.mesh.Mesh` object representing the error band 2442 2443 Errors on x and y can be specified. If left to `None` an estimate is made from 2444 the statistical spread of the dataset itself. Errors are always assumed gaussian. 2445 2446 Arguments: 2447 deg : (int) 2448 degree of the polynomial to be fitted 2449 niter : (int) 2450 number of monte-carlo iterations to compute error bands. 2451 If set to 0, return the simple least-squares fit with naive error estimation 2452 on coefficients only. A reasonable non-zero value to set is about 500, in 2453 this case *error_lines*, *error_band* and the other class attributes are filled 2454 nstd : (float) 2455 nr. of standard deviation to use for error calculation 2456 xerrors : (list) 2457 array of the same length of points with the errors on x 2458 yerrors : (list) 2459 array of the same length of points with the errors on y 2460 vrange : (list) 2461 specify the domain range of the fitting line 2462 (only affects visualization, but can be used to extrapolate the fit 2463 outside the data range) 2464 res : (int) 2465 resolution of the output fitted line and error lines 2466 2467 Examples: 2468 - [fit_polynomial1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fit_polynomial1.py) 2469 2470 ![](https://vedo.embl.es/images/pyplot/fitPolynomial1.png) 2471 """ 2472 if isinstance(points, vedo.pointcloud.Points): 2473 points = points.vertices 2474 points = np.asarray(points) 2475 if len(points) == 2: # assume user is passing [x,y] 2476 points = np.c_[points[0], points[1]] 2477 x = points[:, 0] 2478 y = points[:, 1] # ignore z 2479 2480 n = len(x) 2481 ndof = n - deg - 1 2482 if vrange is not None: 2483 x0, x1 = vrange 2484 else: 2485 x0, x1 = np.min(x), np.max(x) 2486 if xerrors is not None: 2487 x0 -= xerrors[0] / 2 2488 x1 += xerrors[-1] / 2 2489 2490 tol = (x1 - x0) / 10000 2491 xr = np.linspace(x0, x1, res) 2492 2493 # project x errs on y 2494 if xerrors is not None: 2495 xerrors = np.asarray(xerrors) 2496 if yerrors is not None: 2497 yerrors = np.asarray(yerrors) 2498 w = 1.0 / yerrors 2499 coeffs = np.polyfit(x, y, deg, w=w, rcond=None) 2500 else: 2501 coeffs = np.polyfit(x, y, deg, rcond=None) 2502 # update yerrors, 1 bootstrap iteration is enough 2503 p1d = np.poly1d(coeffs) 2504 der = (p1d(x + tol) - p1d(x)) / tol 2505 yerrors = np.sqrt(yerrors * yerrors + np.power(der * xerrors, 2)) 2506 2507 if yerrors is not None: 2508 yerrors = np.asarray(yerrors) 2509 w = 1.0 / yerrors 2510 coeffs, V = np.polyfit(x, y, deg, w=w, rcond=None, cov=True) 2511 else: 2512 w = 1 2513 coeffs, V = np.polyfit(x, y, deg, rcond=None, cov=True) 2514 2515 p1d = np.poly1d(coeffs) 2516 theor = p1d(xr) 2517 fitl = shapes.Line(np.c_[xr, theor], lw=lw, c=c).z(tol * 2) 2518 fitl.coefficients = coeffs 2519 fitl.covariance_matrix = V 2520 residuals2_sum = np.sum(np.power(p1d(x) - y, 2)) / ndof 2521 sigma = np.sqrt(residuals2_sum) 2522 fitl.reduced_chi2 = np.sum(np.power((p1d(x) - y) * w, 2)) / ndof 2523 fitl.ndof = ndof 2524 fitl.data_sigma = sigma # worked out from data using chi2=1 hypo 2525 fitl.name = "LinearPolynomialFit" 2526 2527 if not niter: 2528 fitl.coefficient_errors = np.sqrt(np.diag(V)) 2529 return fitl ################################ 2530 2531 if yerrors is not None: 2532 sigma = yerrors 2533 else: 2534 w = None 2535 fitl.reduced_chi2 = 1 2536 2537 Theors, all_coeffs = [], [] 2538 for i in range(niter): 2539 noise = np.random.randn(n) * sigma 2540 coeffs = np.polyfit(x, y + noise, deg, w=w, rcond=None) 2541 all_coeffs.append(coeffs) 2542 P1d = np.poly1d(coeffs) 2543 Theor = P1d(xr) 2544 Theors.append(Theor) 2545 # all_coeffs = np.array(all_coeffs) 2546 fitl.monte_carlo_coefficients = np.array(all_coeffs) 2547 2548 stds = np.std(Theors, axis=0) 2549 fitl.coefficient_errors = np.std(all_coeffs, axis=0) 2550 2551 # check distributions on the fly 2552 # for i in range(deg+1): 2553 # histogram(all_coeffs[:,i],title='par'+str(i)).show(new=1) 2554 # histogram(all_coeffs[:,0], all_coeffs[:,1], 2555 # xtitle='param0', ytitle='param1',scalarbar=1).show(new=1) 2556 # histogram(all_coeffs[:,1], all_coeffs[:,2], 2557 # xtitle='param1', ytitle='param2').show(new=1) 2558 # histogram(all_coeffs[:,0], all_coeffs[:,2], 2559 # xtitle='param0', ytitle='param2').show(new=1) 2560 2561 error_lines = [] 2562 for i in [nstd, -nstd]: 2563 pp = np.c_[xr, theor + stds * i] 2564 el = shapes.Line(pp, lw=1, alpha=0.2, c="k").z(tol) 2565 error_lines.append(el) 2566 el.name = "ErrorLine for sigma=" + str(i) 2567 2568 fitl.error_lines = error_lines 2569 l1 = error_lines[0].vertices.tolist() 2570 cband = l1 + list(reversed(error_lines[1].vertices.tolist())) + [l1[0]] 2571 fitl.error_band = shapes.Line(cband).triangulate().lw(0).c("k", 0.15) 2572 fitl.error_band.name = "PolynomialFitErrorBand" 2573 return fitl 2574 2575 2576def _plot_fxy( 2577 z, 2578 xlim=(0, 3), 2579 ylim=(0, 3), 2580 zlim=(None, None), 2581 show_nan=True, 2582 zlevels=10, 2583 c=None, 2584 bc="aqua", 2585 alpha=1, 2586 texture="", 2587 bins=(100, 100), 2588 axes=True, 2589): 2590 import warnings 2591 2592 if c is not None: 2593 texture = None # disable 2594 2595 ps = vtki.new("PlaneSource") 2596 ps.SetResolution(bins[0], bins[1]) 2597 ps.SetNormal([0, 0, 1]) 2598 ps.Update() 2599 poly = ps.GetOutput() 2600 dx = xlim[1] - xlim[0] 2601 dy = ylim[1] - ylim[0] 2602 todel, nans = [], [] 2603 2604 for i in range(poly.GetNumberOfPoints()): 2605 px, py, _ = poly.GetPoint(i) 2606 xv = (px + 0.5) * dx + xlim[0] 2607 yv = (py + 0.5) * dy + ylim[0] 2608 try: 2609 with warnings.catch_warnings(): 2610 warnings.simplefilter("ignore") 2611 zv = z(xv, yv) 2612 if np.isnan(zv) or np.isinf(zv) or np.iscomplex(zv): 2613 zv = 0 2614 todel.append(i) 2615 nans.append([xv, yv, 0]) 2616 except: 2617 zv = 0 2618 todel.append(i) 2619 nans.append([xv, yv, 0]) 2620 poly.GetPoints().SetPoint(i, [xv, yv, zv]) 2621 2622 if todel: 2623 cellIds = vtki.vtkIdList() 2624 poly.BuildLinks() 2625 for i in todel: 2626 poly.GetPointCells(i, cellIds) 2627 for j in range(cellIds.GetNumberOfIds()): 2628 poly.DeleteCell(cellIds.GetId(j)) # flag cell 2629 poly.RemoveDeletedCells() 2630 cl = vtki.new("CleanPolyData") 2631 cl.SetInputData(poly) 2632 cl.Update() 2633 poly = cl.GetOutput() 2634 2635 if not poly.GetNumberOfPoints(): 2636 vedo.logger.error("function is not real in the domain") 2637 return None 2638 2639 if zlim[0]: 2640 poly = Mesh(poly).cut_with_plane((0, 0, zlim[0]), (0, 0, 1)).dataset 2641 if zlim[1]: 2642 poly = Mesh(poly).cut_with_plane((0, 0, zlim[1]), (0, 0, -1)).dataset 2643 2644 cmap = "" 2645 if c in colors.cmaps_names: 2646 cmap = c 2647 c = None 2648 bc = None 2649 2650 mesh = Mesh(poly, c, alpha).compute_normals().lighting("plastic") 2651 2652 if cmap: 2653 mesh.compute_elevation().cmap(cmap) 2654 if bc: 2655 mesh.bc(bc) 2656 if texture: 2657 mesh.texture(texture) 2658 2659 acts = [mesh] 2660 if zlevels: 2661 elevation = vtki.new("ElevationFilter") 2662 elevation.SetInputData(poly) 2663 bounds = poly.GetBounds() 2664 elevation.SetLowPoint(0, 0, bounds[4]) 2665 elevation.SetHighPoint(0, 0, bounds[5]) 2666 elevation.Update() 2667 bcf = vtki.new("BandedPolyDataContourFilter") 2668 bcf.SetInputData(elevation.GetOutput()) 2669 bcf.SetScalarModeToValue() 2670 bcf.GenerateContourEdgesOn() 2671 bcf.GenerateValues(zlevels, elevation.GetScalarRange()) 2672 bcf.Update() 2673 zpoly = bcf.GetContourEdgesOutput() 2674 zbandsact = Mesh(zpoly, "k", alpha).lw(1).lighting("off") 2675 zbandsact.mapper.SetResolveCoincidentTopologyToPolygonOffset() 2676 acts.append(zbandsact) 2677 2678 if show_nan and todel: 2679 bb = mesh.bounds() 2680 if bb[4] <= 0 and bb[5] >= 0: 2681 zm = 0.0 2682 else: 2683 zm = (bb[4] + bb[5]) / 2 2684 nans = np.array(nans) + [0, 0, zm] 2685 nansact = shapes.Points(nans, r=2, c="red5", alpha=alpha) 2686 nansact.properties.RenderPointsAsSpheresOff() 2687 acts.append(nansact) 2688 2689 if isinstance(axes, dict): 2690 axs = addons.Axes(mesh, **axes) 2691 acts.append(axs) 2692 elif axes: 2693 axs = addons.Axes(mesh) 2694 acts.append(axs) 2695 2696 assem = Assembly(acts) 2697 assem.name = "PlotFxy" 2698 return assem 2699 2700 2701def _plot_fz( 2702 z, 2703 x=(-1, 1), 2704 y=(-1, 1), 2705 zlimits=(None, None), 2706 cmap="PiYG", 2707 alpha=1, 2708 lw=0.1, 2709 bins=(75, 75), 2710 axes=True, 2711): 2712 ps = vtki.new("PlaneSource") 2713 ps.SetResolution(bins[0], bins[1]) 2714 ps.SetNormal([0, 0, 1]) 2715 ps.Update() 2716 poly = ps.GetOutput() 2717 dx = x[1] - x[0] 2718 dy = y[1] - y[0] 2719 2720 arrImg = [] 2721 for i in range(poly.GetNumberOfPoints()): 2722 px, py, _ = poly.GetPoint(i) 2723 xv = (px + 0.5) * dx + x[0] 2724 yv = (py + 0.5) * dy + y[0] 2725 try: 2726 zv = z(complex(xv), complex(yv)) 2727 except: 2728 zv = 0 2729 poly.GetPoints().SetPoint(i, [xv, yv, np.real(zv)]) 2730 arrImg.append(np.imag(zv)) 2731 2732 mesh = Mesh(poly, alpha).lighting("plastic") 2733 v = max(abs(np.min(arrImg)), abs(np.max(arrImg))) 2734 mesh.cmap(cmap, arrImg, vmin=-v, vmax=v) 2735 mesh.compute_normals().lw(lw) 2736 2737 if zlimits[0]: 2738 mesh.cut_with_plane((0, 0, zlimits[0]), (0, 0, 1)) 2739 if zlimits[1]: 2740 mesh.cut_with_plane((0, 0, zlimits[1]), (0, 0, -1)) 2741 2742 acts = [mesh] 2743 if axes: 2744 axs = addons.Axes(mesh, ztitle="Real part") 2745 acts.append(axs) 2746 asse = Assembly(acts) 2747 asse.name = "PlotFz" 2748 if isinstance(z, str): 2749 asse.name += " " + z 2750 return asse 2751 2752 2753def _plot_polar( 2754 rphi, 2755 title="", 2756 tsize=0.1, 2757 lsize=0.05, 2758 r1=0, 2759 r2=1, 2760 c="blue", 2761 bc="k", 2762 alpha=1, 2763 ps=5, 2764 lw=3, 2765 deg=False, 2766 vmax=None, 2767 fill=False, 2768 splined=False, 2769 nrays=8, 2770 show_disc=True, 2771 show_lines=True, 2772 show_angles=True, 2773): 2774 if len(rphi) == 2: 2775 rphi = np.stack((rphi[0], rphi[1]), axis=1) 2776 2777 rphi = np.array(rphi, dtype=float) 2778 thetas = rphi[:, 0] 2779 radii = rphi[:, 1] 2780 2781 k = 180 / np.pi 2782 if deg: 2783 thetas = np.array(thetas, dtype=float) / k 2784 2785 vals = [] 2786 for v in thetas: # normalize range 2787 t = np.arctan2(np.sin(v), np.cos(v)) 2788 if t < 0: 2789 t += 2 * np.pi 2790 vals.append(t) 2791 thetas = np.array(vals, dtype=float) 2792 2793 if vmax is None: 2794 vmax = np.max(radii) 2795 2796 angles = [] 2797 points = [] 2798 for t, r in zip(thetas, radii): 2799 r = r / vmax * r2 + r1 2800 ct, st = np.cos(t), np.sin(t) 2801 points.append([r * ct, r * st, 0]) 2802 p0 = points[0] 2803 points.append(p0) 2804 2805 r2e = r1 + r2 2806 lines = None 2807 if splined: 2808 lines = shapes.KSpline(points, closed=True) 2809 lines.c(c).lw(lw).alpha(alpha) 2810 elif lw: 2811 lines = shapes.Line(points) 2812 lines.c(c).lw(lw).alpha(alpha) 2813 2814 points.pop() 2815 2816 ptsact = None 2817 if ps: 2818 ptsact = shapes.Points(points, r=ps, c=c, alpha=alpha) 2819 2820 filling = None 2821 if fill and lw: 2822 faces = [] 2823 coords = [[0, 0, 0]] + lines.vertices.tolist() 2824 for i in range(1, lines.npoints): 2825 faces.append([0, i, i + 1]) 2826 filling = Mesh([coords, faces]).c(c).alpha(alpha) 2827 2828 back = None 2829 back2 = None 2830 if show_disc: 2831 back = shapes.Disc(r1=r2e, r2=r2e * 1.01, c=bc, res=(1, 360)) 2832 back.z(-0.01).lighting("off").alpha(alpha) 2833 back2 = shapes.Disc(r1=r2e / 2, r2=r2e / 2 * 1.005, c=bc, res=(1, 360)) 2834 back2.z(-0.01).lighting("off").alpha(alpha) 2835 2836 ti = None 2837 if title: 2838 ti = shapes.Text3D(title, (0, 0, 0), s=tsize, depth=0, justify="top-center") 2839 ti.pos(0, -r2e * 1.15, 0.01) 2840 2841 rays = [] 2842 if show_disc: 2843 rgap = 0.05 2844 for t in np.linspace(0, 2 * np.pi, num=nrays, endpoint=False): 2845 ct, st = np.cos(t), np.sin(t) 2846 if show_lines: 2847 l = shapes.Line((0, 0, -0.01), (r2e * ct * 1.03, r2e * st * 1.03, -0.01)) 2848 rays.append(l) 2849 ct2, st2 = np.cos(t + np.pi / nrays), np.sin(t + np.pi / nrays) 2850 lm = shapes.DashedLine((0, 0, -0.01), (r2e * ct2, r2e * st2, -0.01), spacing=0.25) 2851 rays.append(lm) 2852 elif show_angles: # just the ticks 2853 l = shapes.Line( 2854 (r2e * ct * 0.98, r2e * st * 0.98, -0.01), 2855 (r2e * ct * 1.03, r2e * st * 1.03, -0.01), 2856 ) 2857 if show_angles: 2858 if 0 <= t < np.pi / 2: 2859 ju = "bottom-left" 2860 elif t == np.pi / 2: 2861 ju = "bottom-center" 2862 elif np.pi / 2 < t <= np.pi: 2863 ju = "bottom-right" 2864 elif np.pi < t < np.pi * 3 / 2: 2865 ju = "top-right" 2866 elif t == np.pi * 3 / 2: 2867 ju = "top-center" 2868 else: 2869 ju = "top-left" 2870 a = shapes.Text3D(int(t * k), pos=(0, 0, 0), s=lsize, depth=0, justify=ju) 2871 a.pos(r2e * ct * (1 + rgap), r2e * st * (1 + rgap), -0.01) 2872 angles.append(a) 2873 2874 mrg = merge(back, back2, angles, rays, ti) 2875 if mrg: 2876 mrg.color(bc).alpha(alpha).lighting("off") 2877 rh = Assembly([lines, ptsact, filling] + [mrg]) 2878 rh.name = "PlotPolar" 2879 return rh 2880 2881 2882def _plot_spheric(rfunc, normalize=True, res=33, scalarbar=True, c="grey", alpha=0.05, cmap="jet"): 2883 sg = shapes.Sphere(res=res, quads=True) 2884 sg.alpha(alpha).c(c).wireframe() 2885 2886 cgpts = sg.vertices 2887 r, theta, phi = cart2spher(*cgpts.T) 2888 2889 newr, inans = [], [] 2890 for i in range(len(r)): 2891 try: 2892 ri = rfunc(theta[i], phi[i]) 2893 if np.isnan(ri): 2894 inans.append(i) 2895 newr.append(1) 2896 else: 2897 newr.append(ri) 2898 except: 2899 inans.append(i) 2900 newr.append(1) 2901 2902 newr = np.array(newr, dtype=float) 2903 if normalize: 2904 newr = newr / np.max(newr) 2905 newr[inans] = 1 2906 2907 nanpts = [] 2908 if inans: 2909 redpts = spher2cart(newr[inans], theta[inans], phi[inans]).T 2910 nanpts.append(shapes.Points(redpts, r=4, c="r")) 2911 2912 pts = spher2cart(newr, theta, phi).T 2913 ssurf = sg.clone() 2914 ssurf.vertices = pts 2915 if inans: 2916 ssurf.delete_cells_by_point_index(inans) 2917 2918 ssurf.alpha(1).wireframe(0).lw(0.1) 2919 2920 ssurf.cmap(cmap, newr) 2921 ssurf.compute_normals() 2922 2923 if scalarbar: 2924 xm = np.max([np.max(pts[0]), 1]) 2925 ym = np.max([np.abs(np.max(pts[1])), 1]) 2926 ssurf.mapper.SetScalarRange(np.min(newr), np.max(newr)) 2927 sb3d = ssurf.add_scalarbar3d(size=(xm * 0.07, ym), c="k").scalarbar 2928 sb3d.rotate_x(90).pos(xm * 1.1, 0, -0.5) 2929 else: 2930 sb3d = None 2931 2932 sg.pickable(False) 2933 asse = Assembly([ssurf, sg] + nanpts + [sb3d]) 2934 asse.name = "PlotSpheric" 2935 return asse 2936 2937 2938def _histogram_quad_bin(x, y, **kwargs): 2939 # generate a histogram with 3D bars 2940 # 2941 histo = Histogram2D(x, y, **kwargs) 2942 2943 gap = kwargs.pop("gap", 0) 2944 zscale = kwargs.pop("zscale", 1) 2945 cmap = kwargs.pop("cmap", "Blues_r") 2946 2947 gr = histo.objects[2] 2948 d = gr.diagonal_size() 2949 tol = d / 1_000_000 # tolerance 2950 if gap >= 0: 2951 gr.shrink(1 - gap - tol) 2952 gr.map_cells_to_points() 2953 2954 faces = np.array(gr.cells) 2955 s = 1 / histo.entries * len(faces) * zscale 2956 zvals = gr.pointdata["Scalars"] * s 2957 2958 pts1 = gr.vertices 2959 pts2 = np.copy(pts1) 2960 pts2[:, 2] = zvals + tol 2961 newpts = np.vstack([pts1, pts2]) 2962 newzvals = np.hstack([zvals, zvals]) / s 2963 2964 n = pts1.shape[0] 2965 newfaces = [] 2966 for f in faces: 2967 f0, f1, f2, f3 = f 2968 f0n, f1n, f2n, f3n = f + n 2969 newfaces.extend( 2970 [ 2971 [f0, f1, f2, f3], 2972 [f0n, f1n, f2n, f3n], 2973 [f0, f1, f1n, f0n], 2974 [f1, f2, f2n, f1n], 2975 [f2, f3, f3n, f2n], 2976 [f3, f0, f0n, f3n], 2977 ] 2978 ) 2979 2980 msh = Mesh([newpts, newfaces]).pickable(False) 2981 msh.cmap(cmap, newzvals, name="Frequency") 2982 msh.lw(1).lighting("ambient") 2983 2984 histo.objects[2] = msh 2985 histo.RemovePart(gr.actor) 2986 histo.AddPart(msh.actor) 2987 histo.objects.append(msh) 2988 return histo 2989 2990 2991def _histogram_hex_bin( 2992 xvalues, yvalues, bins=12, norm=1, fill=True, c=None, cmap="terrain_r", alpha=1 2993) -> "Assembly": 2994 xmin, xmax = np.min(xvalues), np.max(xvalues) 2995 ymin, ymax = np.min(yvalues), np.max(yvalues) 2996 dx, dy = xmax - xmin, ymax - ymin 2997 2998 if utils.is_sequence(bins): 2999 n, m = bins 3000 else: 3001 if xmax - xmin < ymax - ymin: 3002 n = bins 3003 m = np.rint(dy / dx * n / 1.2 + 0.5).astype(int) 3004 else: 3005 m = bins 3006 n = np.rint(dx / dy * m * 1.2 + 0.5).astype(int) 3007 3008 values = np.stack((xvalues, yvalues), axis=1) 3009 zs = [[0.0]] * len(values) 3010 values = np.append(values, zs, axis=1) 3011 cloud = vedo.Points(values) 3012 3013 col = None 3014 if c is not None: 3015 col = colors.get_color(c) 3016 3017 hexs, binmax = [], 0 3018 ki, kj = 1.33, 1.12 3019 r = 0.47 / n * 1.2 * dx 3020 for i in range(n + 3): 3021 for j in range(m + 2): 3022 cyl = vtki.new("CylinderSource") 3023 cyl.SetResolution(6) 3024 cyl.CappingOn() 3025 cyl.SetRadius(0.5) 3026 cyl.SetHeight(0.1) 3027 cyl.Update() 3028 t = vtki.vtkTransform() 3029 if not i % 2: 3030 p = (i / ki, j / kj, 0) 3031 else: 3032 p = (i / ki, j / kj + 0.45, 0) 3033 q = (p[0] / n * 1.2 * dx + xmin, p[1] / m * dy + ymin, 0) 3034 ne = len(cloud.closest_point(q, radius=r)) 3035 if fill: 3036 t.Translate(p[0], p[1], ne / 2) 3037 t.Scale(1, 1, ne * 10) 3038 else: 3039 t.Translate(p[0], p[1], ne) 3040 t.RotateX(90) # put it along Z 3041 tf = vtki.new("TransformPolyDataFilter") 3042 tf.SetInputData(cyl.GetOutput()) 3043 tf.SetTransform(t) 3044 tf.Update() 3045 if c is None: 3046 col = i 3047 h = Mesh(tf.GetOutput(), c=col, alpha=alpha).flat() 3048 h.lighting("plastic") 3049 h.actor.PickableOff() 3050 hexs.append(h) 3051 if ne > binmax: 3052 binmax = ne 3053 3054 if cmap is not None: 3055 for h in hexs: 3056 z = h.bounds()[5] 3057 col = colors.color_map(z, cmap, 0, binmax) 3058 h.color(col) 3059 3060 asse = Assembly(hexs) 3061 asse.scale([1.2 / n * dx, 1 / m * dy, norm / binmax * (dx + dy) / 4]) 3062 asse.pos([xmin, ymin, 0]) 3063 asse.name = "HistogramHexBin" 3064 return asse 3065 3066 3067def _histogram_polar( 3068 values, 3069 weights=None, 3070 title="", 3071 tsize=0.1, 3072 bins=16, 3073 r1=0.25, 3074 r2=1, 3075 phigap=0.5, 3076 rgap=0.05, 3077 lpos=1, 3078 lsize=0.04, 3079 c="grey", 3080 bc="k", 3081 alpha=1, 3082 cmap=None, 3083 deg=False, 3084 vmin=None, 3085 vmax=None, 3086 labels=(), 3087 show_disc=True, 3088 nrays=8, 3089 show_lines=True, 3090 show_angles=True, 3091 show_errors=False, 3092): 3093 k = 180 / np.pi 3094 if deg: 3095 values = np.array(values, dtype=float) / k 3096 else: 3097 values = np.array(values, dtype=float) 3098 3099 vals = [] 3100 for v in values: # normalize range 3101 t = np.arctan2(np.sin(v), np.cos(v)) 3102 if t < 0: 3103 t += 2 * np.pi 3104 vals.append(t + 0.00001) 3105 3106 histodata, edges = np.histogram(vals, weights=weights, bins=bins, range=(0, 2 * np.pi)) 3107 3108 thetas = [] 3109 for i in range(bins): 3110 thetas.append((edges[i] + edges[i + 1]) / 2) 3111 3112 if vmin is None: 3113 vmin = np.min(histodata) 3114 if vmax is None: 3115 vmax = np.max(histodata) 3116 3117 errors = np.sqrt(histodata) 3118 r2e = r1 + r2 3119 if show_errors: 3120 r2e += np.max(errors) / vmax * 1.5 3121 3122 back = None 3123 if show_disc: 3124 back = shapes.Disc(r1=r2e, r2=r2e * 1.01, c=bc, res=(1, 360)) 3125 back.z(-0.01) 3126 3127 slices = [] 3128 lines = [] 3129 angles = [] 3130 errbars = [] 3131 3132 for i, t in enumerate(thetas): 3133 r = histodata[i] / vmax * r2 3134 d = shapes.Disc((0, 0, 0), r1, r1 + r, res=(1, 360)) 3135 delta = np.pi / bins - np.pi / 2 - phigap / k 3136 d.cut_with_plane(normal=(np.cos(t + delta), np.sin(t + delta), 0)) 3137 d.cut_with_plane(normal=(np.cos(t - delta), np.sin(t - delta), 0)) 3138 if cmap is not None: 3139 cslice = colors.color_map(histodata[i], cmap, vmin, vmax) 3140 d.color(cslice) 3141 else: 3142 if c is None: 3143 d.color(i) 3144 elif utils.is_sequence(c) and len(c) == bins: 3145 d.color(c[i]) 3146 else: 3147 d.color(c) 3148 d.alpha(alpha).lighting("off") 3149 slices.append(d) 3150 3151 ct, st = np.cos(t), np.sin(t) 3152 3153 if show_errors: 3154 err = np.sqrt(histodata[i]) / vmax * r2 3155 errl = shapes.Line( 3156 ((r1 + r - err) * ct, (r1 + r - err) * st, 0.01), 3157 ((r1 + r + err) * ct, (r1 + r + err) * st, 0.01), 3158 ) 3159 errl.alpha(alpha).lw(3).color(bc) 3160 errbars.append(errl) 3161 3162 labs = [] 3163 rays = [] 3164 if show_disc: 3165 outerdisc = shapes.Disc(r1=r2e, r2=r2e * 1.01, c=bc, res=(1, 360)) 3166 outerdisc.z(-0.01) 3167 innerdisc = shapes.Disc(r1=r2e / 2, r2=r2e / 2 * 1.005, c=bc, res=(1, 360)) 3168 innerdisc.z(-0.01) 3169 rays.append(outerdisc) 3170 rays.append(innerdisc) 3171 3172 rgap = 0.05 3173 for t in np.linspace(0, 2 * np.pi, num=nrays, endpoint=False): 3174 ct, st = np.cos(t), np.sin(t) 3175 if show_lines: 3176 l = shapes.Line((0, 0, -0.01), (r2e * ct * 1.03, r2e * st * 1.03, -0.01)) 3177 rays.append(l) 3178 ct2, st2 = np.cos(t + np.pi / nrays), np.sin(t + np.pi / nrays) 3179 lm = shapes.DashedLine((0, 0, -0.01), (r2e * ct2, r2e * st2, -0.01), spacing=0.25) 3180 rays.append(lm) 3181 elif show_angles: # just the ticks 3182 l = shapes.Line( 3183 (r2e * ct * 0.98, r2e * st * 0.98, -0.01), 3184 (r2e * ct * 1.03, r2e * st * 1.03, -0.01), 3185 ) 3186 if show_angles: 3187 if 0 <= t < np.pi / 2: 3188 ju = "bottom-left" 3189 elif t == np.pi / 2: 3190 ju = "bottom-center" 3191 elif np.pi / 2 < t <= np.pi: 3192 ju = "bottom-right" 3193 elif np.pi < t < np.pi * 3 / 2: 3194 ju = "top-right" 3195 elif t == np.pi * 3 / 2: 3196 ju = "top-center" 3197 else: 3198 ju = "top-left" 3199 a = shapes.Text3D(int(t * k), pos=(0, 0, 0), s=lsize, depth=0, justify=ju) 3200 a.pos(r2e * ct * (1 + rgap), r2e * st * (1 + rgap), -0.01) 3201 angles.append(a) 3202 3203 ti = None 3204 if title: 3205 ti = shapes.Text3D(title, (0, 0, 0), s=tsize, depth=0, justify="top-center") 3206 ti.pos(0, -r2e * 1.15, 0.01) 3207 3208 for i, t in enumerate(thetas): 3209 if i < len(labels): 3210 lab = shapes.Text3D( 3211 labels[i], (0, 0, 0), s=lsize, depth=0, justify="center" # font="VTK", 3212 ) 3213 lab.pos( 3214 r2e * np.cos(t) * (1 + rgap) * lpos / 2, 3215 r2e * np.sin(t) * (1 + rgap) * lpos / 2, 3216 0.01, 3217 ) 3218 labs.append(lab) 3219 3220 mrg = merge(lines, angles, rays, ti, labs) 3221 if mrg: 3222 mrg.color(bc).lighting("off") 3223 3224 acts = slices + errbars + [mrg] 3225 asse = Assembly(acts) 3226 asse.frequencies = histodata 3227 asse.bins = edges 3228 asse.name = "HistogramPolar" 3229 return asse 3230 3231 3232def _histogram_spheric(thetavalues, phivalues, rmax=1.2, res=8, cmap="rainbow", gap=0.1): 3233 3234 x, y, z = spher2cart(np.ones_like(thetavalues) * 1.1, thetavalues, phivalues) 3235 ptsvals = np.c_[x, y, z] 3236 3237 sg = shapes.Sphere(res=res, quads=True).shrink(1 - gap) 3238 sgfaces = sg.cells 3239 sgpts = sg.vertices 3240 3241 cntrs = sg.cell_centers 3242 counts = np.zeros(len(cntrs)) 3243 for p in ptsvals: 3244 cell = sg.closest_point(p, return_cell_id=True) 3245 counts[cell] += 1 3246 acounts = np.array(counts, dtype=float) 3247 counts *= (rmax - 1) / np.max(counts) 3248 3249 for cell, cn in enumerate(counts): 3250 if not cn: 3251 continue 3252 fs = sgfaces[cell] 3253 pts = sgpts[fs] 3254 _, t1, p1 = cart2spher(pts[:, 0], pts[:, 1], pts[:, 2]) 3255 x, y, z = spher2cart(1 + cn, t1, p1) 3256 sgpts[fs] = np.c_[x, y, z] 3257 3258 sg.vertices = sgpts 3259 sg.cmap(cmap, acounts, on="cells") 3260 vals = sg.celldata["Scalars"] 3261 3262 faces = sg.cells 3263 points = sg.vertices.tolist() + [[0.0, 0.0, 0.0]] 3264 lp = len(points) - 1 3265 newfaces = [] 3266 newvals = [] 3267 for i, f in enumerate(faces): 3268 p0, p1, p2, p3 = f 3269 newfaces.append(f) 3270 newfaces.append([p0, lp, p1]) 3271 newfaces.append([p1, lp, p2]) 3272 newfaces.append([p2, lp, p3]) 3273 newfaces.append([p3, lp, p0]) 3274 for _ in range(5): 3275 newvals.append(vals[i]) 3276 3277 newsg = Mesh([points, newfaces]).cmap(cmap, newvals, on="cells") 3278 newsg.compute_normals().flat() 3279 newsg.name = "HistogramSpheric" 3280 return newsg 3281 3282 3283def pie_chart( 3284 fractions, 3285 title="", 3286 tsize=0.3, 3287 r1=1.7, 3288 r2=1, 3289 phigap=0, 3290 lpos=0.8, 3291 lsize=0.15, 3292 c=None, 3293 bc="k", 3294 alpha=1, 3295 labels=(), 3296 show_disc=False, 3297) -> "Assembly": 3298 """ 3299 Donut plot or pie chart. 3300 3301 Arguments: 3302 title : (str) 3303 plot title 3304 tsize : (float) 3305 title size 3306 r1 : (float) inner radius 3307 r2 : (float) 3308 outer radius, starting from r1 3309 phigap : (float) 3310 gap angle btw 2 radial bars, in degrees 3311 lpos : (float) 3312 label gap factor along radius 3313 lsize : (float) 3314 label size 3315 c : (color) 3316 color of the plot slices 3317 bc : (color) 3318 color of the disc frame 3319 alpha : (float) 3320 opacity of the disc frame 3321 labels : (list) 3322 list of labels 3323 show_disc : (bool) 3324 show the outer ring axis 3325 3326 Examples: 3327 - [donut.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/donut.py) 3328 3329 ![](https://vedo.embl.es/images/pyplot/donut.png) 3330 """ 3331 fractions = np.array(fractions, dtype=float) 3332 angles = np.add.accumulate(2 * np.pi * fractions) 3333 angles[-1] = 2 * np.pi 3334 if angles[-2] > 2 * np.pi: 3335 print("Error in donut(): fractions must sum to 1.") 3336 raise RuntimeError 3337 3338 cols = [] 3339 for i, th in enumerate(np.linspace(0, 2 * np.pi, 360, endpoint=False)): 3340 for ia, a in enumerate(angles): 3341 if th < a: 3342 cols.append(c[ia]) 3343 break 3344 labs = [] 3345 if labels: 3346 angles = np.concatenate([[0], angles]) 3347 labs = [""] * 360 3348 for i in range(len(labels)): 3349 a = (angles[i + 1] + angles[i]) / 2 3350 j = int(a / np.pi * 180) 3351 labs[j] = labels[i] 3352 3353 data = np.linspace(0, 2 * np.pi, 360, endpoint=False) + 0.005 3354 dn = _histogram_polar( 3355 data, 3356 title=title, 3357 bins=360, 3358 r1=r1, 3359 r2=r2, 3360 phigap=phigap, 3361 lpos=lpos, 3362 lsize=lsize, 3363 tsize=tsize, 3364 c=cols, 3365 bc=bc, 3366 alpha=alpha, 3367 vmin=0, 3368 vmax=1, 3369 labels=labs, 3370 show_disc=show_disc, 3371 show_lines=0, 3372 show_angles=0, 3373 show_errors=0, 3374 ) 3375 dn.name = "Donut" 3376 return dn 3377 3378 3379def violin( 3380 values, 3381 bins=10, 3382 vlim=None, 3383 x=0, 3384 width=3, 3385 splined=True, 3386 fill=True, 3387 c="violet", 3388 alpha=1, 3389 outline=True, 3390 centerline=True, 3391 lc="darkorchid", 3392 lw=3, 3393) -> "Assembly": 3394 """ 3395 Violin style histogram. 3396 3397 Arguments: 3398 bins : (int) 3399 number of bins 3400 vlim : (list) 3401 input value limits. Crop values outside range 3402 x : (float) 3403 x-position of the violin axis 3404 width : (float) 3405 width factor of the normalized distribution 3406 splined : (bool) 3407 spline the outline 3408 fill : (bool) 3409 fill violin with solid color 3410 outline : (bool) 3411 add the distribution outline 3412 centerline : (bool) 3413 add the vertical centerline at x 3414 lc : (color) 3415 line color 3416 3417 Examples: 3418 - [histo_violin.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/histo_violin.py) 3419 3420 ![](https://vedo.embl.es/images/pyplot/histo_violin.png) 3421 """ 3422 fs, edges = np.histogram(values, bins=bins, range=vlim) 3423 mine, maxe = np.min(edges), np.max(edges) 3424 fs = fs.astype(float) / len(values) * width 3425 3426 rs = [] 3427 3428 if splined: 3429 lnl, lnr = [(0, edges[0], 0)], [(0, edges[0], 0)] 3430 for i in range(bins): 3431 xc = (edges[i] + edges[i + 1]) / 2 3432 yc = fs[i] 3433 lnl.append([-yc, xc, 0]) 3434 lnr.append([yc, xc, 0]) 3435 lnl.append((0, edges[-1], 0)) 3436 lnr.append((0, edges[-1], 0)) 3437 spl = shapes.KSpline(lnl).x(x) 3438 spr = shapes.KSpline(lnr).x(x) 3439 spl.color(lc).alpha(alpha).lw(lw) 3440 spr.color(lc).alpha(alpha).lw(lw) 3441 if outline: 3442 rs.append(spl) 3443 rs.append(spr) 3444 if fill: 3445 rb = shapes.Ribbon(spl, spr, c=c, alpha=alpha).lighting("off") 3446 rs.append(rb) 3447 3448 else: 3449 lns1 = [[0, mine, 0]] 3450 for i in range(bins): 3451 lns1.append([fs[i], edges[i], 0]) 3452 lns1.append([fs[i], edges[i + 1], 0]) 3453 lns1.append([0, maxe, 0]) 3454 3455 lns2 = [[0, mine, 0]] 3456 for i in range(bins): 3457 lns2.append([-fs[i], edges[i], 0]) 3458 lns2.append([-fs[i], edges[i + 1], 0]) 3459 lns2.append([0, maxe, 0]) 3460 3461 if outline: 3462 rs.append(shapes.Line(lns1, c=lc, alpha=alpha, lw=lw).x(x)) 3463 rs.append(shapes.Line(lns2, c=lc, alpha=alpha, lw=lw).x(x)) 3464 3465 if fill: 3466 for i in range(bins): 3467 p0 = (-fs[i], edges[i], 0) 3468 p1 = (fs[i], edges[i + 1], 0) 3469 r = shapes.Rectangle(p0, p1).x(p0[0] + x) 3470 r.color(c).alpha(alpha).lighting("off") 3471 rs.append(r) 3472 3473 if centerline: 3474 cl = shapes.Line([0, mine, 0.01], [0, maxe, 0.01], c=lc, alpha=alpha, lw=2).x(x) 3475 rs.append(cl) 3476 3477 asse = Assembly(rs) 3478 asse.name = "Violin" 3479 return asse 3480 3481 3482def whisker(data, s=0.25, c="k", lw=2, bc="blue", alpha=0.25, r=5, jitter=True, horizontal=False) -> "Assembly": 3483 """ 3484 Generate a "whisker" bar from a 1-dimensional dataset. 3485 3486 Arguments: 3487 s : (float) 3488 size of the box 3489 c : (color) 3490 color of the lines 3491 lw : (float) 3492 line width 3493 bc : (color) 3494 color of the box 3495 alpha : (float) 3496 transparency of the box 3497 r : (float) 3498 point radius in pixels (use value 0 to disable) 3499 jitter : (bool) 3500 add some randomness to points to avoid overlap 3501 horizontal : (bool) 3502 set horizontal layout 3503 3504 Examples: 3505 - [whiskers.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/whiskers.py) 3506 3507 ![](https://vedo.embl.es/images/pyplot/whiskers.png) 3508 """ 3509 xvals = np.zeros_like(np.asarray(data)) 3510 if jitter: 3511 xjit = np.random.randn(len(xvals)) * s / 9 3512 xjit = np.clip(xjit, -s / 2.1, s / 2.1) 3513 xvals += xjit 3514 3515 dmean = np.mean(data) 3516 dq05 = np.quantile(data, 0.05) 3517 dq25 = np.quantile(data, 0.25) 3518 dq75 = np.quantile(data, 0.75) 3519 dq95 = np.quantile(data, 0.95) 3520 3521 pts = None 3522 if r: 3523 pts = shapes.Points(np.array([xvals, data]).T, c=c, r=r) 3524 3525 rec = shapes.Rectangle([-s / 2, dq25], [s / 2, dq75], c=bc, alpha=alpha) 3526 rec.properties.LightingOff() 3527 rl = shapes.Line([[-s / 2, dq25], [s / 2, dq25], [s / 2, dq75], [-s / 2, dq75]], closed=True) 3528 l1 = shapes.Line([0, dq05, 0], [0, dq25, 0], c=c, lw=lw) 3529 l2 = shapes.Line([0, dq75, 0], [0, dq95, 0], c=c, lw=lw) 3530 lm = shapes.Line([-s / 2, dmean], [s / 2, dmean]) 3531 lns = merge(l1, l2, lm, rl) 3532 asse = Assembly([lns, rec, pts]) 3533 if horizontal: 3534 asse.rotate_z(-90) 3535 asse.name = "Whisker" 3536 asse.info["mean"] = dmean 3537 asse.info["quantile_05"] = dq05 3538 asse.info["quantile_25"] = dq25 3539 asse.info["quantile_75"] = dq75 3540 asse.info["quantile_95"] = dq95 3541 return asse 3542 3543 3544def streamplot( 3545 X, Y, U, V, direction="both", max_propagation=None, lw=2, cmap="viridis", probes=() 3546) -> Union["vedo.shapes.Lines", None]: 3547 """ 3548 Generate a streamline plot of a vectorial field (U,V) defined at positions (X,Y). 3549 Returns a `Mesh` object. 3550 3551 Arguments: 3552 direction : (str) 3553 either "forward", "backward" or "both" 3554 max_propagation : (float) 3555 maximum physical length of the streamline 3556 lw : (float) 3557 line width in absolute units 3558 3559 Examples: 3560 - [plot_stream.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/plot_stream.py) 3561 3562 ![](https://vedo.embl.es/images/pyplot/plot_stream.png) 3563 """ 3564 n = len(X) 3565 m = len(Y[0]) 3566 if n != m: 3567 print("Limitation in streamplot(): only square grids are allowed.", n, m) 3568 raise RuntimeError() 3569 3570 xmin, xmax = X[0][0], X[-1][-1] 3571 ymin, ymax = Y[0][0], Y[-1][-1] 3572 3573 field = np.sqrt(U * U + V * V) 3574 3575 vol = vedo.Volume(field, dims=(n, n, 1)) 3576 3577 uf = np.ravel(U, order="F") 3578 vf = np.ravel(V, order="F") 3579 vects = np.c_[uf, vf, np.zeros_like(uf)] 3580 vol.pointdata["StreamPlotField"] = vects 3581 3582 if len(probes) == 0: 3583 probe = shapes.Grid(pos=((n - 1) / 2, (n - 1) / 2, 0), s=(n - 1, n - 1), res=(n - 1, n - 1)) 3584 else: 3585 if isinstance(probes, vedo.Points): 3586 probes = probes.vertices 3587 else: 3588 probes = np.array(probes, dtype=float) 3589 if len(probes[0]) == 2: 3590 probes = np.c_[probes[:, 0], probes[:, 1], np.zeros(len(probes))] 3591 sv = [(n - 1) / (xmax - xmin), (n - 1) / (ymax - ymin), 1] 3592 probes = probes - [xmin, ymin, 0] 3593 probes = np.multiply(probes, sv) 3594 probe = vedo.Points(probes) 3595 3596 stream = vol.compute_streamlines(probe, direction=direction, max_propagation=max_propagation) 3597 if stream: 3598 stream.lw(lw).cmap(cmap).lighting("off") 3599 stream.scale([1 / (n - 1) * (xmax - xmin), 1 / (n - 1) * (ymax - ymin), 1]) 3600 stream.shift(xmin, ymin) 3601 return stream 3602 3603 3604def matrix( 3605 M, 3606 title="Matrix", 3607 xtitle="", 3608 ytitle="", 3609 xlabels=(), 3610 ylabels=(), 3611 xrotation=0, 3612 cmap="Reds", 3613 vmin=None, 3614 vmax=None, 3615 precision=2, 3616 font="Theemim", 3617 scale=0, 3618 scalarbar=True, 3619 lc="white", 3620 lw=0, 3621 c="black", 3622 alpha=1, 3623) -> "Assembly": 3624 """ 3625 Generate a matrix, or a 2D color-coded plot with bin labels. 3626 3627 Returns an `Assembly` object. 3628 3629 Arguments: 3630 M : (list, numpy array) 3631 the input array to visualize 3632 title : (str) 3633 title of the plot 3634 xtitle : (str) 3635 title of the horizontal colmuns 3636 ytitle : (str) 3637 title of the vertical rows 3638 xlabels : (list) 3639 individual string labels for each column. Must be of length m 3640 ylabels : (list) 3641 individual string labels for each row. Must be of length n 3642 xrotation : (float) 3643 rotation of the horizontal labels 3644 cmap : (str) 3645 color map name 3646 vmin : (float) 3647 minimum value of the colormap range 3648 vmax : (float) 3649 maximum value of the colormap range 3650 precision : (int) 3651 number of digits for the matrix entries or bins 3652 font : (str) 3653 font name. Check [available fonts here](https://vedo.embl.es/fonts). 3654 3655 scale : (float) 3656 size of the numeric entries or bin values 3657 scalarbar : (bool) 3658 add a scalar bar to the right of the plot 3659 lc : (str) 3660 color of the line separating the bins 3661 lw : (float) 3662 Width of the line separating the bins 3663 c : (str) 3664 text color 3665 alpha : (float) 3666 plot transparency 3667 3668 Examples: 3669 - [np_matrix.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/np_matrix.py) 3670 3671 ![](https://vedo.embl.es/images/pyplot/np_matrix.png) 3672 """ 3673 M = np.asarray(M) 3674 n, m = M.shape 3675 gr = shapes.Grid(res=(m, n), s=(m / (m + n) * 2, n / (m + n) * 2), c=c, alpha=alpha) 3676 gr.wireframe(False).lc(lc).lw(lw) 3677 3678 matr = np.flip(np.flip(M), axis=1).ravel(order="C") 3679 gr.cmap(cmap, matr, on="cells", vmin=vmin, vmax=vmax) 3680 sbar = None 3681 if scalarbar: 3682 gr.add_scalarbar3d(title_font=font, label_font=font) 3683 sbar = gr.scalarbar 3684 labs = None 3685 if scale != 0: 3686 gr.compute_normals(points=False) 3687 labs = gr.labels( 3688 on="cells", 3689 scale=scale / max(m, n), 3690 precision=precision, 3691 font=font, 3692 justify="center", 3693 c=c, 3694 ) 3695 labs.z(0.001) 3696 t = None 3697 if title: 3698 if title == "Matrix": 3699 title += " " + str(n) + "x" + str(m) 3700 t = shapes.Text3D(title, font=font, s=0.04, justify="bottom-center", c=c) 3701 t.shift(0, n / (m + n) * 1.05) 3702 3703 xlabs = None 3704 if len(xlabels) == m: 3705 xlabs = [] 3706 jus = "top-center" 3707 if xrotation > 44: 3708 jus = "right-center" 3709 for i in range(m): 3710 xl = shapes.Text3D(xlabels[i], font=font, s=0.02, justify=jus, c=c).rotate_z(xrotation) 3711 xl.shift((2 * i - m + 1) / (m + n), -n / (m + n) * 1.05) 3712 xlabs.append(xl) 3713 3714 ylabs = None 3715 if len(ylabels) == n: 3716 ylabels = list(reversed(ylabels)) 3717 ylabs = [] 3718 for i in range(n): 3719 yl = shapes.Text3D(ylabels[i], font=font, s=0.02, justify="right-center", c=c) 3720 yl.shift(-m / (m + n) * 1.05, (2 * i - n + 1) / (m + n)) 3721 ylabs.append(yl) 3722 3723 xt = None 3724 if xtitle: 3725 xt = shapes.Text3D(xtitle, font=font, s=0.035, justify="top-center", c=c) 3726 xt.shift(0, -n / (m + n) * 1.05) 3727 if xlabs is not None: 3728 y0, y1 = xlabs[0].ybounds() 3729 xt.shift(0, -(y1 - y0) - 0.55 / (m + n)) 3730 yt = None 3731 if ytitle: 3732 yt = shapes.Text3D(ytitle, font=font, s=0.035, justify="bottom-center", c=c).rotate_z(90) 3733 yt.shift(-m / (m + n) * 1.05, 0) 3734 if ylabs is not None: 3735 x0, x1 = ylabs[0].xbounds() 3736 yt.shift(-(x1 - x0) - 0.55 / (m + n), 0) 3737 asse = Assembly(gr, sbar, labs, t, xt, yt, xlabs, ylabs) 3738 asse.name = "Matrix" 3739 return asse 3740 3741 3742def CornerPlot(points, pos=1, s=0.2, title="", c="b", bg="k", lines=True, dots=True): 3743 """ 3744 Return a `vtkXYPlotActor` that is a plot of `x` versus `y`, 3745 where `points` is a list of `(x,y)` points. 3746 3747 Assign position following this convention: 3748 3749 - 1, topleft, 3750 - 2, topright, 3751 - 3, bottomleft, 3752 - 4, bottomright. 3753 """ 3754 if len(points) == 2: # passing [allx, ally] 3755 points = np.stack((points[0], points[1]), axis=1) 3756 3757 c = colors.get_color(c) # allow different codings 3758 array_x = vtki.vtkFloatArray() 3759 array_y = vtki.vtkFloatArray() 3760 array_x.SetNumberOfTuples(len(points)) 3761 array_y.SetNumberOfTuples(len(points)) 3762 for i, p in enumerate(points): 3763 array_x.InsertValue(i, p[0]) 3764 array_y.InsertValue(i, p[1]) 3765 field = vtki.vtkFieldData() 3766 field.AddArray(array_x) 3767 field.AddArray(array_y) 3768 data = vtki.vtkDataObject() 3769 data.SetFieldData(field) 3770 3771 xyplot = vtki.new("XYPlotActor") 3772 xyplot.AddDataObjectInput(data) 3773 xyplot.SetDataObjectXComponent(0, 0) 3774 xyplot.SetDataObjectYComponent(0, 1) 3775 xyplot.SetXValuesToValue() 3776 xyplot.SetAdjustXLabels(0) 3777 xyplot.SetAdjustYLabels(0) 3778 xyplot.SetNumberOfXLabels(3) 3779 3780 xyplot.GetProperty().SetPointSize(5) 3781 xyplot.GetProperty().SetLineWidth(2) 3782 xyplot.GetProperty().SetColor(colors.get_color(bg)) 3783 xyplot.SetPlotColor(0, c[0], c[1], c[2]) 3784 3785 xyplot.SetXTitle(title) 3786 xyplot.SetYTitle("") 3787 xyplot.ExchangeAxesOff() 3788 xyplot.SetPlotPoints(dots) 3789 3790 if not lines: 3791 xyplot.PlotLinesOff() 3792 3793 if isinstance(pos, str): 3794 spos = 2 3795 if "top" in pos: 3796 if "left" in pos: 3797 spos = 1 3798 elif "right" in pos: 3799 spos = 2 3800 elif "bottom" in pos: 3801 if "left" in pos: 3802 spos = 3 3803 elif "right" in pos: 3804 spos = 4 3805 pos = spos 3806 if pos == 1: 3807 xyplot.GetPositionCoordinate().SetValue(0.0, 0.8, 0) 3808 elif pos == 2: 3809 xyplot.GetPositionCoordinate().SetValue(0.76, 0.8, 0) 3810 elif pos == 3: 3811 xyplot.GetPositionCoordinate().SetValue(0.0, 0.0, 0) 3812 elif pos == 4: 3813 xyplot.GetPositionCoordinate().SetValue(0.76, 0.0, 0) 3814 else: 3815 xyplot.GetPositionCoordinate().SetValue(pos[0], pos[1], 0) 3816 3817 xyplot.GetPosition2Coordinate().SetValue(s, s, 0) 3818 return xyplot 3819 3820 3821def CornerHistogram( 3822 values, 3823 bins=20, 3824 vrange=None, 3825 minbin=0, 3826 logscale=False, 3827 title="", 3828 c="g", 3829 bg="k", 3830 alpha=1, 3831 pos="bottom-left", 3832 s=0.175, 3833 lines=True, 3834 dots=False, 3835 nmax=None, 3836): 3837 """ 3838 Build a histogram from a list of values in n bins. 3839 The resulting object is a 2D actor. 3840 3841 Use `vrange` to restrict the range of the histogram. 3842 3843 Use `nmax` to limit the sampling to this max nr of entries 3844 3845 Use `pos` to assign its position: 3846 - 1, topleft, 3847 - 2, topright, 3848 - 3, bottomleft, 3849 - 4, bottomright, 3850 - (x, y), as fraction of the rendering window 3851 """ 3852 if hasattr(values, "dataset"): 3853 values = utils.vtk2numpy(values.dataset.GetPointData().GetScalars()) 3854 3855 n = values.shape[0] 3856 if nmax and nmax < n: 3857 # subsample: 3858 idxs = np.linspace(0, n, num=int(nmax), endpoint=False).astype(int) 3859 values = values[idxs] 3860 3861 fs, edges = np.histogram(values, bins=bins, range=vrange) 3862 3863 if minbin: 3864 fs = fs[minbin:-1] 3865 if logscale: 3866 fs = np.log10(fs + 1) 3867 pts = [] 3868 for i in range(len(fs)): 3869 pts.append([(edges[i] + edges[i + 1]) / 2, fs[i]]) 3870 3871 cplot = CornerPlot(pts, pos, s, title, c, bg, lines, dots) 3872 cplot.SetNumberOfYLabels(2) 3873 cplot.SetNumberOfXLabels(3) 3874 tprop = vtki.vtkTextProperty() 3875 tprop.SetColor(colors.get_color(bg)) 3876 tprop.SetFontFamily(vtki.VTK_FONT_FILE) 3877 tprop.SetFontFile(utils.get_font_path("Calco")) 3878 tprop.SetOpacity(alpha) 3879 cplot.SetAxisTitleTextProperty(tprop) 3880 cplot.GetProperty().SetOpacity(alpha) 3881 cplot.GetXAxisActor2D().SetLabelTextProperty(tprop) 3882 cplot.GetXAxisActor2D().SetTitleTextProperty(tprop) 3883 cplot.GetXAxisActor2D().SetFontFactor(0.55) 3884 cplot.GetYAxisActor2D().SetLabelFactor(0.0) 3885 cplot.GetYAxisActor2D().LabelVisibilityOff() 3886 return cplot 3887 3888 3889class DirectedGraph(Assembly): 3890 """ 3891 Support for Directed Graphs. 3892 """ 3893 3894 def __init__(self, **kargs): 3895 """ 3896 A graph consists of a collection of nodes (without postional information) 3897 and a collection of edges connecting pairs of nodes. 3898 The task is to determine the node positions only based on their connections. 3899 3900 This class is derived from class `Assembly`, and it assembles 4 Mesh objects 3901 representing the graph, the node labels, edge labels and edge arrows. 3902 3903 Arguments: 3904 c : (color) 3905 Color of the Graph 3906 n : (int) 3907 number of the initial set of nodes 3908 layout : (int, str) 3909 layout in 3910 `['2d', 'fast2d', 'clustering2d', 'circular', 'circular3d', 'cone', 'force', 'tree']`. 3911 Each of these layouts has different available options. 3912 3913 --------------------------------------------------------------- 3914 .. note:: Options for layouts '2d', 'fast2d' and 'clustering2d' 3915 3916 Arguments: 3917 seed : (int) 3918 seed of the random number generator used to jitter point positions 3919 rest_distance : (float) 3920 manually set the resting distance 3921 nmax : (int) 3922 the maximum number of iterations to be used 3923 zrange : (list) 3924 expand 2d graph along z axis. 3925 3926 --------------------------------------------------------------- 3927 .. note:: Options for layouts 'circular', and 'circular3d': 3928 3929 Arguments: 3930 radius : (float) 3931 set the radius of the circles 3932 height : (float) 3933 set the vertical (local z) distance between the circles 3934 zrange : (float) 3935 expand 2d graph along z axis 3936 3937 --------------------------------------------------------------- 3938 .. note:: Options for layout 'cone' 3939 3940 Arguments: 3941 compactness : (float) 3942 ratio between the average width of a cone in the tree, 3943 and the height of the cone. 3944 compression : (bool) 3945 put children closer together, possibly allowing sub-trees to overlap. 3946 This is useful if the tree is actually the spanning tree of a graph. 3947 spacing : (float) 3948 space between layers of the tree 3949 3950 --------------------------------------------------------------- 3951 .. note:: Options for layout 'force' 3952 3953 Arguments: 3954 seed : (int) 3955 seed the random number generator used to jitter point positions 3956 bounds : (list) 3957 set the region in space in which to place the final graph 3958 nmax : (int) 3959 the maximum number of iterations to be used 3960 three_dimensional : (bool) 3961 allow optimization in the 3rd dimension too 3962 random_initial_points : (bool) 3963 use random positions within the graph bounds as initial points 3964 3965 Examples: 3966 - [lineage_graph.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/lineage_graph.py) 3967 3968 ![](https://vedo.embl.es/images/pyplot/graph_lineage.png) 3969 3970 - [graph_network.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/graph_network.py) 3971 3972 ![](https://vedo.embl.es/images/pyplot/graph_network.png) 3973 """ 3974 3975 super().__init__() 3976 3977 self.nodes = [] 3978 self.edges = [] 3979 3980 self._node_labels = [] # holds strings 3981 self._edge_labels = [] 3982 self.edge_orientations = [] 3983 self.edge_glyph_position = 0.6 3984 3985 self.zrange = 0.0 3986 3987 self.rotX = 0 3988 self.rotY = 0 3989 self.rotZ = 0 3990 3991 self.arrow_scale = 0.15 3992 self.node_label_scale = None 3993 self.node_label_justify = "bottom-left" 3994 3995 self.edge_label_scale = None 3996 3997 self.mdg = vtki.new("MutableDirectedGraph") 3998 3999 n = kargs.pop("n", 0) 4000 for _ in range(n): 4001 self.add_node() 4002 4003 self._c = kargs.pop("c", (0.3, 0.3, 0.3)) 4004 4005 self.gl = vtki.new("GraphLayout") 4006 4007 self.font = kargs.pop("font", "") 4008 4009 s = kargs.pop("layout", "2d") 4010 if isinstance(s, int): 4011 ss = ["2d", "fast2d", "clustering2d", "circular", "circular3d", "cone", "force", "tree"] 4012 s = ss[s] 4013 self.layout = s 4014 4015 if "2d" in s: 4016 if "clustering" in s: 4017 self.strategy = vtki.new("Clustering2DLayoutStrategy") 4018 elif "fast" in s: 4019 self.strategy = vtki.new("Fast2DLayoutStrategy") 4020 else: 4021 self.strategy = vtki.new("Simple2DLayoutStrategy") 4022 self.rotX = 180 4023 opt = kargs.pop("rest_distance", None) 4024 if opt is not None: 4025 self.strategy.SetRestDistance(opt) 4026 opt = kargs.pop("seed", None) 4027 if opt is not None: 4028 self.strategy.SetRandomSeed(opt) 4029 opt = kargs.pop("nmax", None) 4030 if opt is not None: 4031 self.strategy.SetMaxNumberOfIterations(opt) 4032 self.zrange = kargs.pop("zrange", 0) 4033 4034 elif "circ" in s: 4035 if "3d" in s: 4036 self.strategy = vtki.new("Simple3DCirclesStrategy") 4037 self.strategy.SetDirection(0, 0, -1) 4038 self.strategy.SetAutoHeight(True) 4039 self.strategy.SetMethod(1) 4040 self.rotX = -90 4041 opt = kargs.pop("radius", None) # float 4042 if opt is not None: 4043 self.strategy.SetMethod(0) 4044 self.strategy.SetRadius(opt) # float 4045 opt = kargs.pop("height", None) 4046 if opt is not None: 4047 self.strategy.SetAutoHeight(False) 4048 self.strategy.SetHeight(opt) # float 4049 else: 4050 self.strategy = vtki.new("CircularLayoutStrategy") 4051 self.zrange = kargs.pop("zrange", 0) 4052 4053 elif "cone" in s: 4054 self.strategy = vtki.new("ConeLayoutStrategy") 4055 self.rotX = 180 4056 opt = kargs.pop("compactness", None) 4057 if opt is not None: 4058 self.strategy.SetCompactness(opt) 4059 opt = kargs.pop("compression", None) 4060 if opt is not None: 4061 self.strategy.SetCompression(opt) 4062 opt = kargs.pop("spacing", None) 4063 if opt is not None: 4064 self.strategy.SetSpacing(opt) 4065 4066 elif "force" in s: 4067 self.strategy = vtki.new("ForceDirectedLayoutStrategy") 4068 opt = kargs.pop("seed", None) 4069 if opt is not None: 4070 self.strategy.SetRandomSeed(opt) 4071 opt = kargs.pop("bounds", None) 4072 if opt is not None: 4073 self.strategy.SetAutomaticBoundsComputation(False) 4074 self.strategy.SetGraphBounds(opt) # list 4075 opt = kargs.pop("nmax", None) 4076 if opt is not None: 4077 self.strategy.SetMaxNumberOfIterations(opt) # int 4078 opt = kargs.pop("three_dimensional", True) 4079 if opt is not None: 4080 self.strategy.SetThreeDimensionalLayout(opt) # bool 4081 opt = kargs.pop("random_initial_points", None) 4082 if opt is not None: 4083 self.strategy.SetRandomInitialPoints(opt) # bool 4084 4085 elif "tree" in s: 4086 self.strategy = vtki.new("SpanTreeLayoutStrategy") 4087 self.rotX = 180 4088 4089 else: 4090 vedo.logger.error(f"Cannot understand layout {s}. Available layouts:") 4091 vedo.logger.error("[2d,fast2d,clustering2d,circular,circular3d,cone,force,tree]") 4092 raise RuntimeError() 4093 4094 self.gl.SetLayoutStrategy(self.strategy) 4095 4096 if len(kargs) > 0: 4097 vedo.logger.error(f"Cannot understand options: {kargs}") 4098 4099 def add_node(self, label="id") -> int: 4100 """Add a new node to the `Graph`.""" 4101 v = self.mdg.AddVertex() # vtk calls it vertex.. 4102 self.nodes.append(v) 4103 if label == "id": 4104 label = int(v) 4105 self._node_labels.append(str(label)) 4106 return v 4107 4108 def add_edge(self, v1, v2, label="") -> int: 4109 """Add a new edge between to nodes. 4110 An extra node is created automatically if needed.""" 4111 nv = len(self.nodes) 4112 if v1 >= nv: 4113 for _ in range(nv, v1 + 1): 4114 self.add_node() 4115 nv = len(self.nodes) 4116 if v2 >= nv: 4117 for _ in range(nv, v2 + 1): 4118 self.add_node() 4119 e = self.mdg.AddEdge(v1, v2) 4120 self.edges.append(e) 4121 self._edge_labels.append(str(label)) 4122 return e 4123 4124 def add_child(self, v, node_label="id", edge_label="") -> int: 4125 """Add a new edge to a new node as its child. 4126 The extra node is created automatically if needed.""" 4127 nv = len(self.nodes) 4128 if v >= nv: 4129 for _ in range(nv, v + 1): 4130 self.add_node() 4131 child = self.mdg.AddChild(v) 4132 self.edges.append((v, child)) 4133 self.nodes.append(child) 4134 if node_label == "id": 4135 node_label = int(child) 4136 self._node_labels.append(str(node_label)) 4137 self._edge_labels.append(str(edge_label)) 4138 return child 4139 4140 def build(self): 4141 """ 4142 Build the `DirectedGraph(Assembly)`. 4143 Accessory objects are also created for labels and arrows. 4144 """ 4145 self.gl.SetZRange(self.zrange) 4146 self.gl.SetInputData(self.mdg) 4147 self.gl.Update() 4148 4149 gr2poly = vtki.new("GraphToPolyData") 4150 gr2poly.EdgeGlyphOutputOn() 4151 gr2poly.SetEdgeGlyphPosition(self.edge_glyph_position) 4152 gr2poly.SetInputData(self.gl.GetOutput()) 4153 gr2poly.Update() 4154 4155 dgraph = Mesh(gr2poly.GetOutput(0)) 4156 # dgraph.clean() # WRONG!!! dont uncomment 4157 dgraph.flat().color(self._c).lw(2) 4158 dgraph.name = "DirectedGraph" 4159 4160 diagsz = self.diagonal_size() / 1.42 4161 if not diagsz: 4162 return None 4163 4164 dgraph.scale(1 / diagsz) 4165 if self.rotX: 4166 dgraph.rotate_x(self.rotX) 4167 if self.rotY: 4168 dgraph.rotate_y(self.rotY) 4169 if self.rotZ: 4170 dgraph.rotate_z(self.rotZ) 4171 4172 vecs = gr2poly.GetOutput(1).GetPointData().GetVectors() 4173 self.edge_orientations = utils.vtk2numpy(vecs) 4174 4175 # Use Glyph3D to repeat the glyph on all edges. 4176 arrows = None 4177 if self.arrow_scale: 4178 arrow_source = vtki.new("GlyphSource2D") 4179 arrow_source.SetGlyphTypeToEdgeArrow() 4180 arrow_source.SetScale(self.arrow_scale) 4181 arrow_source.Update() 4182 arrow_glyph = vtki.vtkGlyph3D() 4183 arrow_glyph.SetInputData(0, gr2poly.GetOutput(1)) 4184 arrow_glyph.SetInputData(1, arrow_source.GetOutput()) 4185 arrow_glyph.Update() 4186 arrows = Mesh(arrow_glyph.GetOutput()) 4187 arrows.scale(1 / diagsz) 4188 arrows.lighting("off").color(self._c) 4189 if self.rotX: 4190 arrows.rotate_x(self.rotX) 4191 if self.rotY: 4192 arrows.rotate_y(self.rotY) 4193 if self.rotZ: 4194 arrows.rotate_z(self.rotZ) 4195 arrows.name = "DirectedGraphArrows" 4196 4197 node_labels = None 4198 if self._node_labels: 4199 node_labels = dgraph.labels( 4200 self._node_labels, 4201 scale=self.node_label_scale, 4202 precision=0, 4203 font=self.font, 4204 justify=self.node_label_justify, 4205 ) 4206 node_labels.color(self._c).pickable(True) 4207 node_labels.name = "DirectedGraphNodeLabels" 4208 4209 edge_labels = None 4210 if self._edge_labels: 4211 edge_labels = dgraph.labels( 4212 self._edge_labels, on="cells", scale=self.edge_label_scale, precision=0, font=self.font 4213 ) 4214 edge_labels.color(self._c).pickable(True) 4215 edge_labels.name = "DirectedGraphEdgeLabels" 4216 4217 super().__init__([dgraph, node_labels, edge_labels, arrows]) 4218 self.name = "DirectedGraphAssembly" 4219 return self
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 data = data[~np.isnan(data).any(axis=1), :] 1574 1575 fig_kwargs["title"] = title 1576 fig_kwargs["xtitle"] = xtitle 1577 fig_kwargs["ytitle"] = ytitle 1578 fig_kwargs["ac"] = ac 1579 fig_kwargs["ztolerance"] = ztolerance 1580 fig_kwargs["grid"] = grid 1581 1582 x0, y0 = np.min(data, axis=0) 1583 x1, y1 = np.max(data, axis=0) 1584 if xerrors is not None and not error_band: 1585 x0 = min(data[:, 0] - xerrors) 1586 x1 = max(data[:, 0] + xerrors) 1587 if yerrors is not None: 1588 y0 = min(data[:, 1] - yerrors) 1589 y1 = max(data[:, 1] + yerrors) 1590 1591 if like is None: 1592 if xlim is None: 1593 xlim = (None, None) 1594 xlim = list(xlim) 1595 if xlim[0] is None: 1596 xlim[0] = x0 1597 if xlim[1] is None: 1598 xlim[1] = x1 1599 ylim = list(ylim) 1600 if ylim[0] is None: 1601 ylim[0] = y0 1602 if ylim[1] is None: 1603 ylim[1] = y1 1604 1605 self.entries = len(data) 1606 self.mean = data.mean() 1607 self.std = data.std() 1608 1609 self.ztolerance = 0 1610 1611 ######### the PlotXY marker 1612 # fall back solutions logic for colors 1613 if "c" in fig_kwargs: 1614 if mc is None: 1615 mc = fig_kwargs["c"] 1616 if lc is None: 1617 lc = fig_kwargs["c"] 1618 if ec is None: 1619 ec = fig_kwargs["c"] 1620 if lc is None: 1621 lc = "k" 1622 if mc is None: 1623 mc = lc 1624 if ma is None: 1625 ma = la 1626 if ec is None: 1627 if mc is None: 1628 ec = lc 1629 else: 1630 ec = mc 1631 1632 if label: 1633 nlab = LabelData() 1634 nlab.text = label 1635 nlab.tcolor = ac 1636 nlab.marker = marker 1637 if line and marker == "": 1638 nlab.marker = "-" 1639 nlab.mcolor = mc 1640 fig_kwargs["label"] = nlab 1641 1642 ############################################### Figure init 1643 super().__init__(xlim, ylim, aspect, padding, **fig_kwargs) 1644 1645 if not self.yscale: 1646 return 1647 1648 acts = [] 1649 1650 ######### the PlotXY Line or Spline 1651 if dashed: 1652 l = shapes.DashedLine(data, c=lc, alpha=la, lw=lw) 1653 acts.append(l) 1654 elif splined: 1655 l = shapes.KSpline(data).lw(lw).c(lc).alpha(la) 1656 acts.append(l) 1657 elif line: 1658 l = shapes.Line(data, c=lc, alpha=la).lw(lw) 1659 acts.append(l) 1660 1661 if marker: 1662 1663 pts = np.c_[data, np.zeros(len(data))] 1664 1665 if utils.is_sequence(ms): 1666 ### variable point size 1667 mk = shapes.Marker(marker, s=1) 1668 mk.scale([1, 1 / self.yscale, 1]) 1669 msv = np.zeros_like(pts) 1670 msv[:, 0] = ms 1671 marked = shapes.Glyph( 1672 pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True 1673 ) 1674 else: 1675 ### fixed point size 1676 if ms is None: 1677 ms = (xlim[1] - xlim[0]) / 100.0 1678 1679 if utils.is_sequence(mc): 1680 fig_kwargs["marker_color"] = None # for labels 1681 mk = shapes.Marker(marker, s=ms) 1682 mk.scale([1, 1 / self.yscale, 1]) 1683 msv = np.zeros_like(pts) 1684 msv[:, 0] = 1 1685 marked = shapes.Glyph( 1686 pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True 1687 ) 1688 else: 1689 mk = shapes.Marker(marker, s=ms) 1690 mk.scale([1, 1 / self.yscale, 1]) 1691 marked = shapes.Glyph(pts, mk, c=mc) 1692 1693 marked.name = "Marker" 1694 marked.alpha(ma) 1695 marked.z(3 * self.ztolerance) 1696 acts.append(marked) 1697 1698 ######### the PlotXY marker errors 1699 ztol = self.ztolerance 1700 1701 if error_band: 1702 yerrors = np.abs(yerrors) 1703 du = np.array(data) 1704 dd = np.array(data) 1705 du[:, 1] += yerrors 1706 dd[:, 1] -= yerrors 1707 if splined: 1708 res = len(data) * 20 1709 band1 = shapes.KSpline(du, res=res) 1710 band2 = shapes.KSpline(dd, res=res) 1711 band = shapes.Ribbon(band1, band2, res=(res, 2)) 1712 else: 1713 dd = list(reversed(dd.tolist())) 1714 band = shapes.Line(du.tolist() + dd, closed=True) 1715 band.triangulate().lw(0) 1716 if ec is None: 1717 band.c(lc) 1718 else: 1719 band.c(ec) 1720 band.lighting("off").alpha(la).z(ztol / 20) 1721 acts.append(band) 1722 1723 else: 1724 1725 ## xerrors 1726 if xerrors is not None: 1727 if len(xerrors) == len(data): 1728 errs = [] 1729 for i, val in enumerate(data): 1730 xval, yval = val 1731 xerr = xerrors[i] / 2 1732 el = shapes.Line((xval - xerr, yval, ztol), (xval + xerr, yval, ztol)) 1733 el.lw(elw) 1734 errs.append(el) 1735 mxerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol) 1736 acts.append(mxerrs) 1737 else: 1738 vedo.logger.error("in PlotXY(xerrors=...): mismatch in array length") 1739 1740 ## yerrors 1741 if yerrors is not None: 1742 if len(yerrors) == len(data): 1743 errs = [] 1744 for i, val in enumerate(data): 1745 xval, yval = val 1746 yerr = yerrors[i] 1747 el = shapes.Line((xval, yval - yerr, ztol), (xval, yval + yerr, ztol)) 1748 el.lw(elw) 1749 errs.append(el) 1750 myerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol) 1751 acts.append(myerrs) 1752 else: 1753 vedo.logger.error("in PlotXY(yerrors=...): mismatch in array length") 1754 1755 self.insert(*acts, as3d=False) 1756 self.name = "PlotXY"
Creates a PlotXY(Figure)
object.
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 data = data[~np.isnan(data).any(axis=1), :] 1574 1575 fig_kwargs["title"] = title 1576 fig_kwargs["xtitle"] = xtitle 1577 fig_kwargs["ytitle"] = ytitle 1578 fig_kwargs["ac"] = ac 1579 fig_kwargs["ztolerance"] = ztolerance 1580 fig_kwargs["grid"] = grid 1581 1582 x0, y0 = np.min(data, axis=0) 1583 x1, y1 = np.max(data, axis=0) 1584 if xerrors is not None and not error_band: 1585 x0 = min(data[:, 0] - xerrors) 1586 x1 = max(data[:, 0] + xerrors) 1587 if yerrors is not None: 1588 y0 = min(data[:, 1] - yerrors) 1589 y1 = max(data[:, 1] + yerrors) 1590 1591 if like is None: 1592 if xlim is None: 1593 xlim = (None, None) 1594 xlim = list(xlim) 1595 if xlim[0] is None: 1596 xlim[0] = x0 1597 if xlim[1] is None: 1598 xlim[1] = x1 1599 ylim = list(ylim) 1600 if ylim[0] is None: 1601 ylim[0] = y0 1602 if ylim[1] is None: 1603 ylim[1] = y1 1604 1605 self.entries = len(data) 1606 self.mean = data.mean() 1607 self.std = data.std() 1608 1609 self.ztolerance = 0 1610 1611 ######### the PlotXY marker 1612 # fall back solutions logic for colors 1613 if "c" in fig_kwargs: 1614 if mc is None: 1615 mc = fig_kwargs["c"] 1616 if lc is None: 1617 lc = fig_kwargs["c"] 1618 if ec is None: 1619 ec = fig_kwargs["c"] 1620 if lc is None: 1621 lc = "k" 1622 if mc is None: 1623 mc = lc 1624 if ma is None: 1625 ma = la 1626 if ec is None: 1627 if mc is None: 1628 ec = lc 1629 else: 1630 ec = mc 1631 1632 if label: 1633 nlab = LabelData() 1634 nlab.text = label 1635 nlab.tcolor = ac 1636 nlab.marker = marker 1637 if line and marker == "": 1638 nlab.marker = "-" 1639 nlab.mcolor = mc 1640 fig_kwargs["label"] = nlab 1641 1642 ############################################### Figure init 1643 super().__init__(xlim, ylim, aspect, padding, **fig_kwargs) 1644 1645 if not self.yscale: 1646 return 1647 1648 acts = [] 1649 1650 ######### the PlotXY Line or Spline 1651 if dashed: 1652 l = shapes.DashedLine(data, c=lc, alpha=la, lw=lw) 1653 acts.append(l) 1654 elif splined: 1655 l = shapes.KSpline(data).lw(lw).c(lc).alpha(la) 1656 acts.append(l) 1657 elif line: 1658 l = shapes.Line(data, c=lc, alpha=la).lw(lw) 1659 acts.append(l) 1660 1661 if marker: 1662 1663 pts = np.c_[data, np.zeros(len(data))] 1664 1665 if utils.is_sequence(ms): 1666 ### variable point size 1667 mk = shapes.Marker(marker, s=1) 1668 mk.scale([1, 1 / self.yscale, 1]) 1669 msv = np.zeros_like(pts) 1670 msv[:, 0] = ms 1671 marked = shapes.Glyph( 1672 pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True 1673 ) 1674 else: 1675 ### fixed point size 1676 if ms is None: 1677 ms = (xlim[1] - xlim[0]) / 100.0 1678 1679 if utils.is_sequence(mc): 1680 fig_kwargs["marker_color"] = None # for labels 1681 mk = shapes.Marker(marker, s=ms) 1682 mk.scale([1, 1 / self.yscale, 1]) 1683 msv = np.zeros_like(pts) 1684 msv[:, 0] = 1 1685 marked = shapes.Glyph( 1686 pts, mk, c=mc, orientation_array=msv, scale_by_vector_size=True 1687 ) 1688 else: 1689 mk = shapes.Marker(marker, s=ms) 1690 mk.scale([1, 1 / self.yscale, 1]) 1691 marked = shapes.Glyph(pts, mk, c=mc) 1692 1693 marked.name = "Marker" 1694 marked.alpha(ma) 1695 marked.z(3 * self.ztolerance) 1696 acts.append(marked) 1697 1698 ######### the PlotXY marker errors 1699 ztol = self.ztolerance 1700 1701 if error_band: 1702 yerrors = np.abs(yerrors) 1703 du = np.array(data) 1704 dd = np.array(data) 1705 du[:, 1] += yerrors 1706 dd[:, 1] -= yerrors 1707 if splined: 1708 res = len(data) * 20 1709 band1 = shapes.KSpline(du, res=res) 1710 band2 = shapes.KSpline(dd, res=res) 1711 band = shapes.Ribbon(band1, band2, res=(res, 2)) 1712 else: 1713 dd = list(reversed(dd.tolist())) 1714 band = shapes.Line(du.tolist() + dd, closed=True) 1715 band.triangulate().lw(0) 1716 if ec is None: 1717 band.c(lc) 1718 else: 1719 band.c(ec) 1720 band.lighting("off").alpha(la).z(ztol / 20) 1721 acts.append(band) 1722 1723 else: 1724 1725 ## xerrors 1726 if xerrors is not None: 1727 if len(xerrors) == len(data): 1728 errs = [] 1729 for i, val in enumerate(data): 1730 xval, yval = val 1731 xerr = xerrors[i] / 2 1732 el = shapes.Line((xval - xerr, yval, ztol), (xval + xerr, yval, ztol)) 1733 el.lw(elw) 1734 errs.append(el) 1735 mxerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol) 1736 acts.append(mxerrs) 1737 else: 1738 vedo.logger.error("in PlotXY(xerrors=...): mismatch in array length") 1739 1740 ## yerrors 1741 if yerrors is not None: 1742 if len(yerrors) == len(data): 1743 errs = [] 1744 for i, val in enumerate(data): 1745 xval, yval = val 1746 yerr = yerrors[i] 1747 el = shapes.Line((xval, yval - yerr, ztol), (xval, yval + yerr, ztol)) 1748 el.lw(elw) 1749 errs.append(el) 1750 myerrs = merge(errs).c(ec).lw(lw).alpha(ma).z(2 * ztol) 1751 acts.append(myerrs) 1752 else: 1753 vedo.logger.error("in PlotXY(yerrors=...): mismatch in array length") 1754 1755 self.insert(*acts, as3d=False) 1756 self.name = "PlotXY"
Arguments:
- xerrors : (bool) show error bars associated to each point in x
- yerrors : (bool) show error bars associated to each point in y
- lw : (int) width of the line connecting points in pixel units. Set it to 0 to remove the line.
- lc : (str) line color
- la : (float) line "alpha", opacity of the line
- dashed : (bool) draw a dashed line instead of a continuous line
- splined : (bool) spline the line joining the point as a countinous curve
- elw : (int) width of error bar lines in units of pixels
- ec : (color) color of error bar, by default the same as marker color
- error_band : (bool)
represent errors on y as a filled error band.
Use
ec
keyword to modify its color. - marker : (str, int) use a marker for the data points
- ms : (float) marker size
- mc : (color) color of the marker
- ma : (float) opacity of the marker
- xlim : (list) set limits to the range for the x variable
- ylim : (list) set limits to the range for the y variable
- aspect : (float, str)
Desired aspect ratio.
Use
aspect="equal"
to force the same units in x and y. Scaling factor is saved in Figure.yscale. - padding : (float, list) keep a padding space from the axes (as a fraction of the axis size). This can be a list of four numbers.
- title : (str) title to appear on the top of the frame, like a header.
- xtitle : (str)
title for the x-axis, can also be set using
axes=dict(xtitle="my x axis")
- ytitle : (str)
title for the y-axis, can also be set using
axes=dict(ytitle="my y axis")
- ac : (str) axes color
- grid : (bool)
show the background grid for the axes, can also be set using
axes=dict(xygrid=True)
- ztolerance : (float) a tolerance factor to superimpose objects (along the z-axis).
Example:
import numpy as np from vedo.pyplot import plot x = np.arange(0, np.pi, 0.1) fig = plot(x, np.sin(2*x), 'r0-', aspect='equal') fig+= plot(x, np.cos(2*x), 'blue4 o-', like=fig) fig.show().close()
Examples:
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
1759def plot(*args, **kwargs): 1760 """ 1761 Draw a 2D line plot, or scatter plot, of variable x vs variable y. 1762 Input format can be either `[allx], [allx, ally] or [(x1,y1), (x2,y2), ...]` 1763 1764 Use `like=...` if you want to use the same format of a previously 1765 created Figure (useful when superimposing Figures) to make sure 1766 they are compatible and comparable. If they are not compatible 1767 you will receive an error message. 1768 1769 Arguments: 1770 xerrors : (bool) 1771 show error bars associated to each point in x 1772 yerrors : (bool) 1773 show error bars associated to each point in y 1774 lw : (int) 1775 width of the line connecting points in pixel units. 1776 Set it to 0 to remove the line. 1777 lc : (str) 1778 line color 1779 la : (float) 1780 line "alpha", opacity of the line 1781 dashed : (bool) 1782 draw a dashed line instead of a continuous line 1783 splined : (bool) 1784 spline the line joining the point as a countinous curve 1785 elw : (int) 1786 width of error bar lines in units of pixels 1787 ec : (color) 1788 color of error bar, by default the same as marker color 1789 error_band : (bool) 1790 represent errors on y as a filled error band. 1791 Use `ec` keyword to modify its color. 1792 marker : (str, int) 1793 use a marker for the data points 1794 ms : (float) 1795 marker size 1796 mc : (color) 1797 color of the marker 1798 ma : (float) 1799 opacity of the marker 1800 xlim : (list) 1801 set limits to the range for the x variable 1802 ylim : (list) 1803 set limits to the range for the y variable 1804 aspect : (float) 1805 Desired aspect ratio. 1806 If None, it is automatically calculated to get a reasonable aspect ratio. 1807 Scaling factor is saved in Figure.yscale 1808 padding : (float, list) 1809 keep a padding space from the axes (as a fraction of the axis size). 1810 This can be a list of four numbers. 1811 title : (str) 1812 title to appear on the top of the frame, like a header. 1813 xtitle : (str) 1814 title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")` 1815 ytitle : (str) 1816 title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")` 1817 ac : (str) 1818 axes color 1819 grid : (bool) 1820 show the background grid for the axes, can also be set using `axes=dict(xygrid=True)` 1821 ztolerance : (float) 1822 a tolerance factor to superimpose objects (along the z-axis). 1823 1824 Example: 1825 ```python 1826 import numpy as np 1827 from vedo.pyplot import plot 1828 from vedo import settings 1829 settings.remember_last_figure_format = True ############# 1830 x = np.linspace(0, 6.28, num=50) 1831 fig = plot(np.sin(x), 'r-') 1832 fig+= plot(np.cos(x), 'bo-') # no need to specify like=... 1833 fig.show().close() 1834 ``` 1835 <img src="https://user-images.githubusercontent.com/32848391/74363882-c3638300-4dcb-11ea-8a78-eb492ad9711f.png" width="600"> 1836 1837 Examples: 1838 - [plot_errbars.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errbars.py) 1839 - [plot_errband.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_errband.py) 1840 - [plot_pip.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_pip.py) 1841 1842 ![](https://vedo.embl.es/images/pyplot/plot_pip.png) 1843 1844 - [scatter1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter1.py) 1845 - [scatter2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/scatter2.py) 1846 1847 1848 1849 ------------------------------------------------------------------------- 1850 .. note:: mode="bar" 1851 1852 Creates a `PlotBars(Figure)` object. 1853 1854 Input must be in format `[counts, labels, colors, edges]`. 1855 Either or both `edges` and `colors` are optional and can be omitted. 1856 1857 Arguments: 1858 errors : (bool) 1859 show error bars 1860 logscale : (bool) 1861 use logscale on y-axis 1862 fill : (bool) 1863 fill bars with solid color `c` 1864 gap : (float) 1865 leave a small space btw bars 1866 radius : (float) 1867 border radius of the top of the histogram bar. Default value is 0.1. 1868 texture : (str) 1869 url or path to an image to be used as texture for the bin 1870 outline : (bool) 1871 show outline of the bins 1872 xtitle : (str) 1873 title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")` 1874 ytitle : (str) 1875 title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")` 1876 ac : (str) 1877 axes color 1878 padding : (float, list) 1879 keep a padding space from the axes (as a fraction of the axis size). 1880 This can be a list of four numbers. 1881 aspect : (float) 1882 the desired aspect ratio of the figure. Default is 4/3. 1883 grid : (bool) 1884 show the background grid for the axes, can also be set using `axes=dict(xygrid=True)` 1885 1886 Examples: 1887 - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py) 1888 - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py) 1889 - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py) 1890 - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py) 1891 1892 ![](https://vedo.embl.es/images/pyplot/histo_1D.png) 1893 1894 1895 ---------------------------------------------------------------------- 1896 .. note:: 2D functions 1897 1898 If input is an external function or a formula, draw the surface 1899 representing the function `f(x,y)`. 1900 1901 Arguments: 1902 x : (float) 1903 x range of values 1904 y : (float) 1905 y range of values 1906 zlimits : (float) 1907 limit the z range of the independent variable 1908 zlevels : (int) 1909 will draw the specified number of z-levels contour lines 1910 show_nan : (bool) 1911 show where the function does not exist as red points 1912 bins : (list) 1913 number of bins in x and y 1914 1915 Examples: 1916 - [plot_fxy1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy1.py) 1917 1918 ![](https://vedo.embl.es/images/pyplot/plot_fxy.png) 1919 1920 - [plot_fxy2.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy2.py) 1921 1922 1923 -------------------------------------------------------------------- 1924 .. note:: mode="complex" 1925 1926 If `mode='complex'` draw the real value of the function and color map the imaginary part. 1927 1928 Arguments: 1929 cmap : (str) 1930 diverging color map (white means `imag(z)=0`) 1931 lw : (float) 1932 line with of the binning 1933 bins : (list) 1934 binning in x and y 1935 1936 Examples: 1937 - [plot_fxy.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_fxy.py) 1938 1939 ![](https://user-images.githubusercontent.com/32848391/73392962-1709a300-42db-11ea-9278-30c9d6e5eeaa.png) 1940 1941 1942 -------------------------------------------------------------------- 1943 .. note:: mode="polar" 1944 1945 If `mode='polar'` input arrays are interpreted as a list of polar angles and radii. 1946 Build a polar (radar) plot by joining the set of points in polar coordinates. 1947 1948 Arguments: 1949 title : (str) 1950 plot title 1951 tsize : (float) 1952 title size 1953 bins : (int) 1954 number of bins in phi 1955 r1 : (float) 1956 inner radius 1957 r2 : (float) 1958 outer radius 1959 lsize : (float) 1960 label size 1961 c : (color) 1962 color of the line 1963 ac : (color) 1964 color of the frame and labels 1965 alpha : (float) 1966 opacity of the frame 1967 ps : (int) 1968 point size in pixels, if ps=0 no point is drawn 1969 lw : (int) 1970 line width in pixels, if lw=0 no line is drawn 1971 deg : (bool) 1972 input array is in degrees 1973 vmax : (float) 1974 normalize radius to this maximum value 1975 fill : (bool) 1976 fill convex area with solid color 1977 splined : (bool) 1978 interpolate the set of input points 1979 show_disc : (bool) 1980 draw the outer ring axis 1981 nrays : (int) 1982 draw this number of axis rays (continuous and dashed) 1983 show_lines : (bool) 1984 draw lines to the origin 1985 show_angles : (bool) 1986 draw angle values 1987 1988 Examples: 1989 - [histo_polar.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_polar.py) 1990 1991 ![](https://user-images.githubusercontent.com/32848391/64992590-7fc82400-d8d4-11e9-9c10-795f4756a73f.png) 1992 1993 1994 -------------------------------------------------------------------- 1995 .. note:: mode="spheric" 1996 1997 If `mode='spheric'` input must be an external function rho(theta, phi). 1998 A surface is created in spherical coordinates. 1999 2000 Return an `Figure(Assembly)` of 2 objects: the unit 2001 sphere (in wireframe representation) and the surface `rho(theta, phi)`. 2002 2003 Arguments: 2004 rfunc : function 2005 handle to a user defined function `rho(theta, phi)`. 2006 normalize : (bool) 2007 scale surface to fit inside the unit sphere 2008 res : (int) 2009 grid resolution of the unit sphere 2010 scalarbar : (bool) 2011 add a 3D scalarbar to the plot for radius 2012 c : (color) 2013 color of the unit sphere 2014 alpha : (float) 2015 opacity of the unit sphere 2016 cmap : (str) 2017 color map for the surface 2018 2019 Examples: 2020 - [plot_spheric.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/plot_spheric.py) 2021 2022 ![](https://vedo.embl.es/images/pyplot/plot_spheric.png) 2023 """ 2024 mode = kwargs.pop("mode", "") 2025 if "spher" in mode: 2026 return _plot_spheric(args[0], **kwargs) 2027 2028 if "bar" in mode: 2029 return PlotBars(args[0], **kwargs) 2030 2031 if isinstance(args[0], str) or "function" in str(type(args[0])): 2032 if "complex" in mode: 2033 return _plot_fz(args[0], **kwargs) 2034 return _plot_fxy(args[0], **kwargs) 2035 2036 # grab the matplotlib-like options 2037 optidx = None 2038 for i, a in enumerate(args): 2039 if i > 0 and isinstance(a, str): 2040 optidx = i 2041 break 2042 if optidx: 2043 opts = args[optidx].replace(" ", "") 2044 if "--" in opts: 2045 opts = opts.replace("--", "") 2046 kwargs["dashed"] = True 2047 elif "-" in opts: 2048 opts = opts.replace("-", "") 2049 else: 2050 kwargs["lw"] = 0 2051 2052 symbs = [".", "o", "O", "0", "p", "*", "h", "D", "d", "v", "^", ">", "<", "s", "x", "a"] 2053 2054 allcols = list(colors.colors.keys()) + list(colors.color_nicks.keys()) 2055 for cc in allcols: 2056 if cc == "o": 2057 continue 2058 if cc in opts: 2059 opts = opts.replace(cc, "") 2060 kwargs["lc"] = cc 2061 kwargs["mc"] = cc 2062 break 2063 2064 for ss in symbs: 2065 if ss in opts: 2066 opts = opts.replace(ss, "", 1) 2067 kwargs["marker"] = ss 2068 break 2069 2070 opts.replace(" ", "") 2071 if opts: 2072 vedo.logger.error(f"in plot(), could not understand option(s): {opts}") 2073 2074 if optidx == 1 or optidx is None: 2075 if utils.is_sequence(args[0][0]) and len(args[0][0]) > 1: 2076 # print('------------- case 1', 'plot([(x,y),..])') 2077 data = np.asarray(args[0]) # (x,y) 2078 x = np.asarray(data[:, 0]) 2079 y = np.asarray(data[:, 1]) 2080 2081 elif len(args) == 1 or optidx == 1: 2082 # print('------------- case 2', 'plot(x)') 2083 if "pandas" in str(type(args[0])): 2084 if "ytitle" not in kwargs: 2085 kwargs.update({"ytitle": args[0].name.replace("_", "_ ")}) 2086 x = np.linspace(0, len(args[0]), num=len(args[0])) 2087 y = np.asarray(args[0]).ravel() 2088 2089 elif utils.is_sequence(args[1]): 2090 # print('------------- case 3', 'plot(allx,ally)',str(type(args[0]))) 2091 if "pandas" in str(type(args[0])): 2092 if "xtitle" not in kwargs: 2093 kwargs.update({"xtitle": args[0].name.replace("_", "_ ")}) 2094 if "pandas" in str(type(args[1])): 2095 if "ytitle" not in kwargs: 2096 kwargs.update({"ytitle": args[1].name.replace("_", "_ ")}) 2097 x = np.asarray(args[0]).ravel() 2098 y = np.asarray(args[1]).ravel() 2099 2100 elif utils.is_sequence(args[0]) and utils.is_sequence(args[0][0]): 2101 # print('------------- case 4', 'plot([allx,ally])') 2102 x = np.asarray(args[0][0]).ravel() 2103 y = np.asarray(args[0][1]).ravel() 2104 2105 elif optidx == 2: 2106 # print('------------- case 5', 'plot(x,y)') 2107 x = np.asarray(args[0]).ravel() 2108 y = np.asarray(args[1]).ravel() 2109 2110 else: 2111 vedo.logger.error(f"plot(): Could not understand input arguments {args}") 2112 return None 2113 2114 if "polar" in mode: 2115 return _plot_polar(np.c_[x, y], **kwargs) 2116 2117 return PlotXY(np.c_[x, y], **kwargs)
Draw a 2D line plot, or scatter plot, of variable x vs variable y.
Input format can be either [allx], [allx, ally] or [(x1,y1), (x2,y2), ...]
Use like=...
if you want to use the same format of a previously
created Figure (useful when superimposing Figures) to make sure
they are compatible and comparable. If they are not compatible
you will receive an error message.
Arguments:
- xerrors : (bool) show error bars associated to each point in x
- yerrors : (bool) show error bars associated to each point in y
- lw : (int) width of the line connecting points in pixel units. Set it to 0 to remove the line.
- lc : (str) line color
- la : (float) line "alpha", opacity of the line
- dashed : (bool) draw a dashed line instead of a continuous line
- splined : (bool) spline the line joining the point as a countinous curve
- elw : (int) width of error bar lines in units of pixels
- ec : (color) color of error bar, by default the same as marker color
- error_band : (bool)
represent errors on y as a filled error band.
Use
ec
keyword to modify its color. - marker : (str, int) use a marker for the data points
- ms : (float) marker size
- mc : (color) color of the marker
- ma : (float) opacity of the marker
- xlim : (list) set limits to the range for the x variable
- ylim : (list) set limits to the range for the y variable
- aspect : (float) Desired aspect ratio. If None, it is automatically calculated to get a reasonable aspect ratio. Scaling factor is saved in Figure.yscale
- padding : (float, list) keep a padding space from the axes (as a fraction of the axis size). This can be a list of four numbers.
- title : (str) title to appear on the top of the frame, like a header.
- xtitle : (str)
title for the x-axis, can also be set using
axes=dict(xtitle="my x axis")
- ytitle : (str)
title for the y-axis, can also be set using
axes=dict(ytitle="my y axis")
- ac : (str) axes color
- grid : (bool)
show the background grid for the axes, can also be set using
axes=dict(xygrid=True)
- ztolerance : (float) a tolerance factor to superimpose objects (along the z-axis).
Example:
import numpy as np from vedo.pyplot import plot from vedo import settings settings.remember_last_figure_format = True ############# x = np.linspace(0, 6.28, num=50) fig = plot(np.sin(x), 'r-') fig+= plot(np.cos(x), 'bo-') # no need to specify like=... fig.show().close()
Examples:
mode="bar"
Creates a PlotBars(Figure)
object.
Input must be in format [counts, labels, colors, edges]
.
Either or both edges
and colors
are optional and can be omitted.
Arguments:
- errors : (bool) show error bars
- logscale : (bool) use logscale on y-axis
- fill : (bool)
fill bars with solid color
c
- gap : (float) leave a small space btw bars
- radius : (float) border radius of the top of the histogram bar. Default value is 0.1.
- texture : (str) url or path to an image to be used as texture for the bin
- outline : (bool) show outline of the bins
- xtitle : (str)
title for the x-axis, can also be set using
axes=dict(xtitle="my x axis")
- ytitle : (str)
title for the y-axis, can also be set using
axes=dict(ytitle="my y axis")
- ac : (str) axes color
- padding : (float, list) keep a padding space from the axes (as a fraction of the axis size). This can be a list of four numbers.
- aspect : (float) the desired aspect ratio of the figure. Default is 4/3.
- grid : (bool)
show the background grid for the axes, can also be set using
axes=dict(xygrid=True)
Examples:
2D functions
If input is an external function or a formula, draw the surface
representing the function f(x,y)
.
Arguments:
- x : (float) x range of values
- y : (float) y range of values
- zlimits : (float) limit the z range of the independent variable
- zlevels : (int) will draw the specified number of z-levels contour lines
- show_nan : (bool) show where the function does not exist as red points
- bins : (list) number of bins in x and y
Examples:
mode="complex"
If mode='complex'
draw the real value of the function and color map the imaginary part.
Arguments:
- cmap : (str)
diverging color map (white means
imag(z)=0
) - lw : (float) line with of the binning
- bins : (list) binning in x and y
Examples:
mode="polar"
If mode='polar'
input arrays are interpreted as a list of polar angles and radii.
Build a polar (radar) plot by joining the set of points in polar coordinates.
Arguments:
- title : (str) plot title
- tsize : (float) title size
- bins : (int) number of bins in phi
- r1 : (float) inner radius
- r2 : (float) outer radius
- lsize : (float) label size
- c : (color) color of the line
- ac : (color) color of the frame and labels
- alpha : (float) opacity of the frame
- ps : (int) point size in pixels, if ps=0 no point is drawn
- lw : (int) line width in pixels, if lw=0 no line is drawn
- deg : (bool) input array is in degrees
- vmax : (float) normalize radius to this maximum value
- fill : (bool) fill convex area with solid color
- splined : (bool) interpolate the set of input points
- show_disc : (bool) draw the outer ring axis
- nrays : (int) draw this number of axis rays (continuous and dashed)
- show_lines : (bool) draw lines to the origin
- show_angles : (bool) draw angle values
Examples:
mode="spheric"
If mode='spheric'
input must be an external function rho(theta, phi).
A surface is created in spherical coordinates.
Return an Figure(Assembly)
of 2 objects: the unit
sphere (in wireframe representation) and the surface rho(theta, phi)
.
Arguments:
- rfunc : function
handle to a user defined function
rho(theta, phi)
. - normalize : (bool) scale surface to fit inside the unit sphere
- res : (int) grid resolution of the unit sphere
- scalarbar : (bool) add a 3D scalarbar to the plot for radius
- c : (color) color of the unit sphere
- alpha : (float) opacity of the unit sphere
- cmap : (str) color map for the surface
Examples:
2120def histogram(*args, **kwargs): 2121 """ 2122 Histogramming for 1D and 2D data arrays. 2123 2124 This is meant as a convenience function that creates the appropriate object 2125 based on the shape of the provided input data. 2126 2127 Use keyword `like=...` if you want to use the same format of a previously 2128 created Figure (useful when superimposing Figures) to make sure 2129 they are compatible and comparable. If they are not compatible 2130 you will receive an error message. 2131 2132 ------------------------------------------------------------------------- 2133 .. note:: default mode, for 1D arrays 2134 2135 Creates a `Histogram1D(Figure)` object. 2136 2137 Arguments: 2138 weights : (list) 2139 An array of weights, of the same shape as `data`. Each value in `data` 2140 only contributes its associated weight towards the bin count (instead of 1). 2141 bins : (int) 2142 number of bins 2143 vrange : (list) 2144 restrict the range of the histogram 2145 density : (bool) 2146 normalize the area to 1 by dividing by the nr of entries and bin size 2147 logscale : (bool) 2148 use logscale on y-axis 2149 fill : (bool) 2150 fill bars with solid color `c` 2151 gap : (float) 2152 leave a small space btw bars 2153 radius : (float) 2154 border radius of the top of the histogram bar. Default value is 0.1. 2155 texture : (str) 2156 url or path to an image to be used as texture for the bin 2157 outline : (bool) 2158 show outline of the bins 2159 errors : (bool) 2160 show error bars 2161 xtitle : (str) 2162 title for the x-axis, can also be set using `axes=dict(xtitle="my x axis")` 2163 ytitle : (str) 2164 title for the y-axis, can also be set using `axes=dict(ytitle="my y axis")` 2165 padding : (float, list) 2166 keep a padding space from the axes (as a fraction of the axis size). 2167 This can be a list of four numbers. 2168 aspect : (float) 2169 the desired aspect ratio of the histogram. Default is 4/3. 2170 grid : (bool) 2171 show the background grid for the axes, can also be set using `axes=dict(xygrid=True)` 2172 ztolerance : (float) 2173 a tolerance factor to superimpose objects (along the z-axis). 2174 2175 Examples: 2176 - [histo_1d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_a.py) 2177 - [histo_1d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_b.py) 2178 - [histo_1d_c.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_c.py) 2179 - [histo_1d_d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_1d_d.py) 2180 2181 ![](https://vedo.embl.es/images/pyplot/histo_1D.png) 2182 2183 2184 ------------------------------------------------------------------------- 2185 .. note:: default mode, for 2D arrays 2186 2187 Input data formats `[(x1,x2,..), (y1,y2,..)] or [(x1,y1), (x2,y2),..]` 2188 are both valid. 2189 2190 Arguments: 2191 bins : (list) 2192 binning as (nx, ny) 2193 weights : (list) 2194 array of weights to assign to each entry 2195 cmap : (str, lookuptable) 2196 color map name or look up table 2197 alpha : (float) 2198 opacity of the histogram 2199 gap : (float) 2200 separation between adjacent bins as a fraction for their size. 2201 Set gap=-1 to generate a quad surface. 2202 scalarbar : (bool) 2203 add a scalarbar to right of the histogram 2204 like : (Figure) 2205 grab and use the same format of the given Figure (for superimposing) 2206 xlim : (list) 2207 [x0, x1] range of interest. If left to None will automatically 2208 choose the minimum or the maximum of the data range. 2209 Data outside the range are completely ignored. 2210 ylim : (list) 2211 [y0, y1] range of interest. If left to None will automatically 2212 choose the minimum or the maximum of the data range. 2213 Data outside the range are completely ignored. 2214 aspect : (float) 2215 the desired aspect ratio of the figure. 2216 title : (str) 2217 title of the plot to appear on top. 2218 If left blank some statistics will be shown. 2219 xtitle : (str) 2220 x axis title 2221 ytitle : (str) 2222 y axis title 2223 ztitle : (str) 2224 title for the scalar bar 2225 ac : (str) 2226 axes color, additional keyword for Axes can also be added 2227 using e.g. `axes=dict(xygrid=True)` 2228 2229 Examples: 2230 - [histo_2d_a.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_a.py) 2231 - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_2d_b.py) 2232 2233 ![](https://vedo.embl.es/images/pyplot/histo_2D.png) 2234 2235 2236 ------------------------------------------------------------------------- 2237 .. note:: mode="3d" 2238 2239 If `mode='3d'`, build a 2D histogram as 3D bars from a list of x and y values. 2240 2241 Arguments: 2242 xtitle : (str) 2243 x axis title 2244 bins : (int) 2245 nr of bins for the smaller range in x or y 2246 vrange : (list) 2247 range in x and y in format `[(xmin,xmax), (ymin,ymax)]` 2248 norm : (float) 2249 sets a scaling factor for the z axis (frequency axis) 2250 fill : (bool) 2251 draw solid hexagons 2252 cmap : (str) 2253 color map name for elevation 2254 gap : (float) 2255 keep a internal empty gap between bins [0,1] 2256 zscale : (float) 2257 rescale the (already normalized) zaxis for visual convenience 2258 2259 Examples: 2260 - [histo_2d_b.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/histo_2d_b.py) 2261 2262 2263 ------------------------------------------------------------------------- 2264 .. note:: mode="hexbin" 2265 2266 If `mode='hexbin'`, build a hexagonal histogram from a list of x and y values. 2267 2268 Arguments: 2269 xtitle : (str) 2270 x axis title 2271 bins : (int) 2272 nr of bins for the smaller range in x or y 2273 vrange : (list) 2274 range in x and y in format `[(xmin,xmax), (ymin,ymax)]` 2275 norm : (float) 2276 sets a scaling factor for the z axis (frequency axis) 2277 fill : (bool) 2278 draw solid hexagons 2279 cmap : (str) 2280 color map name for elevation 2281 2282 Examples: 2283 - [histo_hexagonal.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_hexagonal.py) 2284 2285 ![](https://vedo.embl.es/images/pyplot/histo_hexagonal.png) 2286 2287 2288 ------------------------------------------------------------------------- 2289 .. note:: mode="polar" 2290 2291 If `mode='polar'` assume input is polar coordinate system (rho, theta): 2292 2293 Arguments: 2294 weights : (list) 2295 Array of weights, of the same shape as the input. 2296 Each value only contributes its associated weight towards the bin count (instead of 1). 2297 title : (str) 2298 histogram title 2299 tsize : (float) 2300 title size 2301 bins : (int) 2302 number of bins in phi 2303 r1 : (float) 2304 inner radius 2305 r2 : (float) 2306 outer radius 2307 phigap : (float) 2308 gap angle btw 2 radial bars, in degrees 2309 rgap : (float) 2310 gap factor along radius of numeric angle labels 2311 lpos : (float) 2312 label gap factor along radius 2313 lsize : (float) 2314 label size 2315 c : (color) 2316 color of the histogram bars, can be a list of length `bins` 2317 bc : (color) 2318 color of the frame and labels 2319 alpha : (float) 2320 opacity of the frame 2321 cmap : (str) 2322 color map name 2323 deg : (bool) 2324 input array is in degrees 2325 vmin : (float) 2326 minimum value of the radial axis 2327 vmax : (float) 2328 maximum value of the radial axis 2329 labels : (list) 2330 list of labels, must be of length `bins` 2331 show_disc : (bool) 2332 show the outer ring axis 2333 nrays : (int) 2334 draw this number of axis rays (continuous and dashed) 2335 show_lines : (bool) 2336 show lines to the origin 2337 show_angles : (bool) 2338 show angular values 2339 show_errors : (bool) 2340 show error bars 2341 2342 Examples: 2343 - [histo_polar.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_polar.py) 2344 2345 ![](https://vedo.embl.es/images/pyplot/histo_polar.png) 2346 2347 2348 ------------------------------------------------------------------------- 2349 .. note:: mode="spheric" 2350 2351 If `mode='spheric'`, build a histogram from list of theta and phi values. 2352 2353 Arguments: 2354 rmax : (float) 2355 maximum radial elevation of bin 2356 res : (int) 2357 sphere resolution 2358 cmap : (str) 2359 color map name 2360 lw : (int) 2361 line width of the bin edges 2362 2363 Examples: 2364 - [histo_spheric.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_spheric.py) 2365 2366 ![](https://vedo.embl.es/images/pyplot/histo_spheric.png) 2367 """ 2368 mode = kwargs.pop("mode", "") 2369 if len(args) == 2: # x, y 2370 2371 if "spher" in mode: 2372 return _histogram_spheric(args[0], args[1], **kwargs) 2373 2374 if "hex" in mode: 2375 return _histogram_hex_bin(args[0], args[1], **kwargs) 2376 2377 if "3d" in mode.lower(): 2378 return _histogram_quad_bin(args[0], args[1], **kwargs) 2379 2380 return Histogram2D(args[0], args[1], **kwargs) 2381 2382 elif len(args) == 1: 2383 2384 if isinstance(args[0], vedo.Volume): 2385 data = args[0].pointdata[0] 2386 elif isinstance(args[0], vedo.Points): 2387 pd0 = args[0].pointdata[0] 2388 if pd0 is not None: 2389 data = pd0.ravel() 2390 else: 2391 data = args[0].celldata[0].ravel() 2392 else: 2393 try: 2394 if "pandas" in str(type(args[0])): 2395 if "xtitle" not in kwargs: 2396 kwargs.update({"xtitle": args[0].name.replace("_", "_ ")}) 2397 except: 2398 pass 2399 data = np.asarray(args[0]) 2400 2401 if "spher" in mode: 2402 return _histogram_spheric(args[0][:, 0], args[0][:, 1], **kwargs) 2403 2404 if data.ndim == 1: 2405 if "polar" in mode: 2406 return _histogram_polar(data, **kwargs) 2407 return Histogram1D(data, **kwargs) 2408 2409 if "hex" in mode: 2410 return _histogram_hex_bin(args[0][:, 0], args[0][:, 1], **kwargs) 2411 2412 if "3d" in mode.lower(): 2413 return _histogram_quad_bin(args[0][:, 0], args[0][:, 1], **kwargs) 2414 2415 return Histogram2D(args[0], **kwargs) 2416 2417 vedo.logger.error(f"in histogram(): could not understand input {args[0]}") 2418 return None
Histogramming for 1D and 2D data arrays.
This is meant as a convenience function that creates the appropriate object based on the shape of the provided input data.
Use keyword like=...
if you want to use the same format of a previously
created Figure (useful when superimposing Figures) to make sure
they are compatible and comparable. If they are not compatible
you will receive an error message.
default mode, for 1D arrays
Creates a Histogram1D(Figure)
object.
Arguments:
- weights : (list)
An array of weights, of the same shape as
data
. Each value 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:
2421def fit( 2422 points, deg=1, niter=0, nstd=3, xerrors=None, yerrors=None, vrange=None, res=250, lw=3, c="red4" 2423) -> "vedo.shapes.Line": 2424 """ 2425 Polynomial fitting with parameter error and error bands calculation. 2426 Errors bars in both x and y are supported. 2427 2428 Returns a `vedo.shapes.Line` object. 2429 2430 Additional information about the fitting output can be accessed with: 2431 2432 `fitd = fit(pts)` 2433 2434 - `fitd.coefficients` will contain the coefficients of the polynomial fit 2435 - `fitd.coefficient_errors`, errors on the fitting coefficients 2436 - `fitd.monte_carlo_coefficients`, fitting coefficient set from MC generation 2437 - `fitd.covariance_matrix`, covariance matrix as a numpy array 2438 - `fitd.reduced_chi2`, reduced chi-square of the fitting 2439 - `fitd.ndof`, number of degrees of freedom 2440 - `fitd.data_sigma`, mean data dispersion from the central fit assuming `Chi2=1` 2441 - `fitd.error_lines`, a `vedo.shapes.Line` object for the upper and lower error band 2442 - `fitd.error_band`, the `vedo.mesh.Mesh` object representing the error band 2443 2444 Errors on x and y can be specified. If left to `None` an estimate is made from 2445 the statistical spread of the dataset itself. Errors are always assumed gaussian. 2446 2447 Arguments: 2448 deg : (int) 2449 degree of the polynomial to be fitted 2450 niter : (int) 2451 number of monte-carlo iterations to compute error bands. 2452 If set to 0, return the simple least-squares fit with naive error estimation 2453 on coefficients only. A reasonable non-zero value to set is about 500, in 2454 this case *error_lines*, *error_band* and the other class attributes are filled 2455 nstd : (float) 2456 nr. of standard deviation to use for error calculation 2457 xerrors : (list) 2458 array of the same length of points with the errors on x 2459 yerrors : (list) 2460 array of the same length of points with the errors on y 2461 vrange : (list) 2462 specify the domain range of the fitting line 2463 (only affects visualization, but can be used to extrapolate the fit 2464 outside the data range) 2465 res : (int) 2466 resolution of the output fitted line and error lines 2467 2468 Examples: 2469 - [fit_polynomial1.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/fit_polynomial1.py) 2470 2471 ![](https://vedo.embl.es/images/pyplot/fitPolynomial1.png) 2472 """ 2473 if isinstance(points, vedo.pointcloud.Points): 2474 points = points.vertices 2475 points = np.asarray(points) 2476 if len(points) == 2: # assume user is passing [x,y] 2477 points = np.c_[points[0], points[1]] 2478 x = points[:, 0] 2479 y = points[:, 1] # ignore z 2480 2481 n = len(x) 2482 ndof = n - deg - 1 2483 if vrange is not None: 2484 x0, x1 = vrange 2485 else: 2486 x0, x1 = np.min(x), np.max(x) 2487 if xerrors is not None: 2488 x0 -= xerrors[0] / 2 2489 x1 += xerrors[-1] / 2 2490 2491 tol = (x1 - x0) / 10000 2492 xr = np.linspace(x0, x1, res) 2493 2494 # project x errs on y 2495 if xerrors is not None: 2496 xerrors = np.asarray(xerrors) 2497 if yerrors is not None: 2498 yerrors = np.asarray(yerrors) 2499 w = 1.0 / yerrors 2500 coeffs = np.polyfit(x, y, deg, w=w, rcond=None) 2501 else: 2502 coeffs = np.polyfit(x, y, deg, rcond=None) 2503 # update yerrors, 1 bootstrap iteration is enough 2504 p1d = np.poly1d(coeffs) 2505 der = (p1d(x + tol) - p1d(x)) / tol 2506 yerrors = np.sqrt(yerrors * yerrors + np.power(der * xerrors, 2)) 2507 2508 if yerrors is not None: 2509 yerrors = np.asarray(yerrors) 2510 w = 1.0 / yerrors 2511 coeffs, V = np.polyfit(x, y, deg, w=w, rcond=None, cov=True) 2512 else: 2513 w = 1 2514 coeffs, V = np.polyfit(x, y, deg, rcond=None, cov=True) 2515 2516 p1d = np.poly1d(coeffs) 2517 theor = p1d(xr) 2518 fitl = shapes.Line(np.c_[xr, theor], lw=lw, c=c).z(tol * 2) 2519 fitl.coefficients = coeffs 2520 fitl.covariance_matrix = V 2521 residuals2_sum = np.sum(np.power(p1d(x) - y, 2)) / ndof 2522 sigma = np.sqrt(residuals2_sum) 2523 fitl.reduced_chi2 = np.sum(np.power((p1d(x) - y) * w, 2)) / ndof 2524 fitl.ndof = ndof 2525 fitl.data_sigma = sigma # worked out from data using chi2=1 hypo 2526 fitl.name = "LinearPolynomialFit" 2527 2528 if not niter: 2529 fitl.coefficient_errors = np.sqrt(np.diag(V)) 2530 return fitl ################################ 2531 2532 if yerrors is not None: 2533 sigma = yerrors 2534 else: 2535 w = None 2536 fitl.reduced_chi2 = 1 2537 2538 Theors, all_coeffs = [], [] 2539 for i in range(niter): 2540 noise = np.random.randn(n) * sigma 2541 coeffs = np.polyfit(x, y + noise, deg, w=w, rcond=None) 2542 all_coeffs.append(coeffs) 2543 P1d = np.poly1d(coeffs) 2544 Theor = P1d(xr) 2545 Theors.append(Theor) 2546 # all_coeffs = np.array(all_coeffs) 2547 fitl.monte_carlo_coefficients = np.array(all_coeffs) 2548 2549 stds = np.std(Theors, axis=0) 2550 fitl.coefficient_errors = np.std(all_coeffs, axis=0) 2551 2552 # check distributions on the fly 2553 # for i in range(deg+1): 2554 # histogram(all_coeffs[:,i],title='par'+str(i)).show(new=1) 2555 # histogram(all_coeffs[:,0], all_coeffs[:,1], 2556 # xtitle='param0', ytitle='param1',scalarbar=1).show(new=1) 2557 # histogram(all_coeffs[:,1], all_coeffs[:,2], 2558 # xtitle='param1', ytitle='param2').show(new=1) 2559 # histogram(all_coeffs[:,0], all_coeffs[:,2], 2560 # xtitle='param0', ytitle='param2').show(new=1) 2561 2562 error_lines = [] 2563 for i in [nstd, -nstd]: 2564 pp = np.c_[xr, theor + stds * i] 2565 el = shapes.Line(pp, lw=1, alpha=0.2, c="k").z(tol) 2566 error_lines.append(el) 2567 el.name = "ErrorLine for sigma=" + str(i) 2568 2569 fitl.error_lines = error_lines 2570 l1 = error_lines[0].vertices.tolist() 2571 cband = l1 + list(reversed(error_lines[1].vertices.tolist())) + [l1[0]] 2572 fitl.error_band = shapes.Line(cband).triangulate().lw(0).c("k", 0.15) 2573 fitl.error_band.name = "PolynomialFitErrorBand" 2574 return fitl
Polynomial fitting with parameter error and error bands calculation. Errors bars in both x and y are supported.
Returns a vedo.shapes.Line
object.
Additional information about the fitting output can be accessed with:
fitd = fit(pts)
fitd.coefficients
will contain the coefficients of the polynomial 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:
3284def pie_chart( 3285 fractions, 3286 title="", 3287 tsize=0.3, 3288 r1=1.7, 3289 r2=1, 3290 phigap=0, 3291 lpos=0.8, 3292 lsize=0.15, 3293 c=None, 3294 bc="k", 3295 alpha=1, 3296 labels=(), 3297 show_disc=False, 3298) -> "Assembly": 3299 """ 3300 Donut plot or pie chart. 3301 3302 Arguments: 3303 title : (str) 3304 plot title 3305 tsize : (float) 3306 title size 3307 r1 : (float) inner radius 3308 r2 : (float) 3309 outer radius, starting from r1 3310 phigap : (float) 3311 gap angle btw 2 radial bars, in degrees 3312 lpos : (float) 3313 label gap factor along radius 3314 lsize : (float) 3315 label size 3316 c : (color) 3317 color of the plot slices 3318 bc : (color) 3319 color of the disc frame 3320 alpha : (float) 3321 opacity of the disc frame 3322 labels : (list) 3323 list of labels 3324 show_disc : (bool) 3325 show the outer ring axis 3326 3327 Examples: 3328 - [donut.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/donut.py) 3329 3330 ![](https://vedo.embl.es/images/pyplot/donut.png) 3331 """ 3332 fractions = np.array(fractions, dtype=float) 3333 angles = np.add.accumulate(2 * np.pi * fractions) 3334 angles[-1] = 2 * np.pi 3335 if angles[-2] > 2 * np.pi: 3336 print("Error in donut(): fractions must sum to 1.") 3337 raise RuntimeError 3338 3339 cols = [] 3340 for i, th in enumerate(np.linspace(0, 2 * np.pi, 360, endpoint=False)): 3341 for ia, a in enumerate(angles): 3342 if th < a: 3343 cols.append(c[ia]) 3344 break 3345 labs = [] 3346 if labels: 3347 angles = np.concatenate([[0], angles]) 3348 labs = [""] * 360 3349 for i in range(len(labels)): 3350 a = (angles[i + 1] + angles[i]) / 2 3351 j = int(a / np.pi * 180) 3352 labs[j] = labels[i] 3353 3354 data = np.linspace(0, 2 * np.pi, 360, endpoint=False) + 0.005 3355 dn = _histogram_polar( 3356 data, 3357 title=title, 3358 bins=360, 3359 r1=r1, 3360 r2=r2, 3361 phigap=phigap, 3362 lpos=lpos, 3363 lsize=lsize, 3364 tsize=tsize, 3365 c=cols, 3366 bc=bc, 3367 alpha=alpha, 3368 vmin=0, 3369 vmax=1, 3370 labels=labs, 3371 show_disc=show_disc, 3372 show_lines=0, 3373 show_angles=0, 3374 show_errors=0, 3375 ) 3376 dn.name = "Donut" 3377 return dn
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:
3380def violin( 3381 values, 3382 bins=10, 3383 vlim=None, 3384 x=0, 3385 width=3, 3386 splined=True, 3387 fill=True, 3388 c="violet", 3389 alpha=1, 3390 outline=True, 3391 centerline=True, 3392 lc="darkorchid", 3393 lw=3, 3394) -> "Assembly": 3395 """ 3396 Violin style histogram. 3397 3398 Arguments: 3399 bins : (int) 3400 number of bins 3401 vlim : (list) 3402 input value limits. Crop values outside range 3403 x : (float) 3404 x-position of the violin axis 3405 width : (float) 3406 width factor of the normalized distribution 3407 splined : (bool) 3408 spline the outline 3409 fill : (bool) 3410 fill violin with solid color 3411 outline : (bool) 3412 add the distribution outline 3413 centerline : (bool) 3414 add the vertical centerline at x 3415 lc : (color) 3416 line color 3417 3418 Examples: 3419 - [histo_violin.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/histo_violin.py) 3420 3421 ![](https://vedo.embl.es/images/pyplot/histo_violin.png) 3422 """ 3423 fs, edges = np.histogram(values, bins=bins, range=vlim) 3424 mine, maxe = np.min(edges), np.max(edges) 3425 fs = fs.astype(float) / len(values) * width 3426 3427 rs = [] 3428 3429 if splined: 3430 lnl, lnr = [(0, edges[0], 0)], [(0, edges[0], 0)] 3431 for i in range(bins): 3432 xc = (edges[i] + edges[i + 1]) / 2 3433 yc = fs[i] 3434 lnl.append([-yc, xc, 0]) 3435 lnr.append([yc, xc, 0]) 3436 lnl.append((0, edges[-1], 0)) 3437 lnr.append((0, edges[-1], 0)) 3438 spl = shapes.KSpline(lnl).x(x) 3439 spr = shapes.KSpline(lnr).x(x) 3440 spl.color(lc).alpha(alpha).lw(lw) 3441 spr.color(lc).alpha(alpha).lw(lw) 3442 if outline: 3443 rs.append(spl) 3444 rs.append(spr) 3445 if fill: 3446 rb = shapes.Ribbon(spl, spr, c=c, alpha=alpha).lighting("off") 3447 rs.append(rb) 3448 3449 else: 3450 lns1 = [[0, mine, 0]] 3451 for i in range(bins): 3452 lns1.append([fs[i], edges[i], 0]) 3453 lns1.append([fs[i], edges[i + 1], 0]) 3454 lns1.append([0, maxe, 0]) 3455 3456 lns2 = [[0, mine, 0]] 3457 for i in range(bins): 3458 lns2.append([-fs[i], edges[i], 0]) 3459 lns2.append([-fs[i], edges[i + 1], 0]) 3460 lns2.append([0, maxe, 0]) 3461 3462 if outline: 3463 rs.append(shapes.Line(lns1, c=lc, alpha=alpha, lw=lw).x(x)) 3464 rs.append(shapes.Line(lns2, c=lc, alpha=alpha, lw=lw).x(x)) 3465 3466 if fill: 3467 for i in range(bins): 3468 p0 = (-fs[i], edges[i], 0) 3469 p1 = (fs[i], edges[i + 1], 0) 3470 r = shapes.Rectangle(p0, p1).x(p0[0] + x) 3471 r.color(c).alpha(alpha).lighting("off") 3472 rs.append(r) 3473 3474 if centerline: 3475 cl = shapes.Line([0, mine, 0.01], [0, maxe, 0.01], c=lc, alpha=alpha, lw=2).x(x) 3476 rs.append(cl) 3477 3478 asse = Assembly(rs) 3479 asse.name = "Violin" 3480 return asse
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:
3483def whisker(data, s=0.25, c="k", lw=2, bc="blue", alpha=0.25, r=5, jitter=True, horizontal=False) -> "Assembly": 3484 """ 3485 Generate a "whisker" bar from a 1-dimensional dataset. 3486 3487 Arguments: 3488 s : (float) 3489 size of the box 3490 c : (color) 3491 color of the lines 3492 lw : (float) 3493 line width 3494 bc : (color) 3495 color of the box 3496 alpha : (float) 3497 transparency of the box 3498 r : (float) 3499 point radius in pixels (use value 0 to disable) 3500 jitter : (bool) 3501 add some randomness to points to avoid overlap 3502 horizontal : (bool) 3503 set horizontal layout 3504 3505 Examples: 3506 - [whiskers.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/whiskers.py) 3507 3508 ![](https://vedo.embl.es/images/pyplot/whiskers.png) 3509 """ 3510 xvals = np.zeros_like(np.asarray(data)) 3511 if jitter: 3512 xjit = np.random.randn(len(xvals)) * s / 9 3513 xjit = np.clip(xjit, -s / 2.1, s / 2.1) 3514 xvals += xjit 3515 3516 dmean = np.mean(data) 3517 dq05 = np.quantile(data, 0.05) 3518 dq25 = np.quantile(data, 0.25) 3519 dq75 = np.quantile(data, 0.75) 3520 dq95 = np.quantile(data, 0.95) 3521 3522 pts = None 3523 if r: 3524 pts = shapes.Points(np.array([xvals, data]).T, c=c, r=r) 3525 3526 rec = shapes.Rectangle([-s / 2, dq25], [s / 2, dq75], c=bc, alpha=alpha) 3527 rec.properties.LightingOff() 3528 rl = shapes.Line([[-s / 2, dq25], [s / 2, dq25], [s / 2, dq75], [-s / 2, dq75]], closed=True) 3529 l1 = shapes.Line([0, dq05, 0], [0, dq25, 0], c=c, lw=lw) 3530 l2 = shapes.Line([0, dq75, 0], [0, dq95, 0], c=c, lw=lw) 3531 lm = shapes.Line([-s / 2, dmean], [s / 2, dmean]) 3532 lns = merge(l1, l2, lm, rl) 3533 asse = Assembly([lns, rec, pts]) 3534 if horizontal: 3535 asse.rotate_z(-90) 3536 asse.name = "Whisker" 3537 asse.info["mean"] = dmean 3538 asse.info["quantile_05"] = dq05 3539 asse.info["quantile_25"] = dq25 3540 asse.info["quantile_75"] = dq75 3541 asse.info["quantile_95"] = dq95 3542 return asse
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:
3545def streamplot( 3546 X, Y, U, V, direction="both", max_propagation=None, lw=2, cmap="viridis", probes=() 3547) -> Union["vedo.shapes.Lines", None]: 3548 """ 3549 Generate a streamline plot of a vectorial field (U,V) defined at positions (X,Y). 3550 Returns a `Mesh` object. 3551 3552 Arguments: 3553 direction : (str) 3554 either "forward", "backward" or "both" 3555 max_propagation : (float) 3556 maximum physical length of the streamline 3557 lw : (float) 3558 line width in absolute units 3559 3560 Examples: 3561 - [plot_stream.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/plot_stream.py) 3562 3563 ![](https://vedo.embl.es/images/pyplot/plot_stream.png) 3564 """ 3565 n = len(X) 3566 m = len(Y[0]) 3567 if n != m: 3568 print("Limitation in streamplot(): only square grids are allowed.", n, m) 3569 raise RuntimeError() 3570 3571 xmin, xmax = X[0][0], X[-1][-1] 3572 ymin, ymax = Y[0][0], Y[-1][-1] 3573 3574 field = np.sqrt(U * U + V * V) 3575 3576 vol = vedo.Volume(field, dims=(n, n, 1)) 3577 3578 uf = np.ravel(U, order="F") 3579 vf = np.ravel(V, order="F") 3580 vects = np.c_[uf, vf, np.zeros_like(uf)] 3581 vol.pointdata["StreamPlotField"] = vects 3582 3583 if len(probes) == 0: 3584 probe = shapes.Grid(pos=((n - 1) / 2, (n - 1) / 2, 0), s=(n - 1, n - 1), res=(n - 1, n - 1)) 3585 else: 3586 if isinstance(probes, vedo.Points): 3587 probes = probes.vertices 3588 else: 3589 probes = np.array(probes, dtype=float) 3590 if len(probes[0]) == 2: 3591 probes = np.c_[probes[:, 0], probes[:, 1], np.zeros(len(probes))] 3592 sv = [(n - 1) / (xmax - xmin), (n - 1) / (ymax - ymin), 1] 3593 probes = probes - [xmin, ymin, 0] 3594 probes = np.multiply(probes, sv) 3595 probe = vedo.Points(probes) 3596 3597 stream = vol.compute_streamlines(probe, direction=direction, max_propagation=max_propagation) 3598 if stream: 3599 stream.lw(lw).cmap(cmap).lighting("off") 3600 stream.scale([1 / (n - 1) * (xmax - xmin), 1 / (n - 1) * (ymax - ymin), 1]) 3601 stream.shift(xmin, ymin) 3602 return stream
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:
3605def matrix( 3606 M, 3607 title="Matrix", 3608 xtitle="", 3609 ytitle="", 3610 xlabels=(), 3611 ylabels=(), 3612 xrotation=0, 3613 cmap="Reds", 3614 vmin=None, 3615 vmax=None, 3616 precision=2, 3617 font="Theemim", 3618 scale=0, 3619 scalarbar=True, 3620 lc="white", 3621 lw=0, 3622 c="black", 3623 alpha=1, 3624) -> "Assembly": 3625 """ 3626 Generate a matrix, or a 2D color-coded plot with bin labels. 3627 3628 Returns an `Assembly` object. 3629 3630 Arguments: 3631 M : (list, numpy array) 3632 the input array to visualize 3633 title : (str) 3634 title of the plot 3635 xtitle : (str) 3636 title of the horizontal colmuns 3637 ytitle : (str) 3638 title of the vertical rows 3639 xlabels : (list) 3640 individual string labels for each column. Must be of length m 3641 ylabels : (list) 3642 individual string labels for each row. Must be of length n 3643 xrotation : (float) 3644 rotation of the horizontal labels 3645 cmap : (str) 3646 color map name 3647 vmin : (float) 3648 minimum value of the colormap range 3649 vmax : (float) 3650 maximum value of the colormap range 3651 precision : (int) 3652 number of digits for the matrix entries or bins 3653 font : (str) 3654 font name. Check [available fonts here](https://vedo.embl.es/fonts). 3655 3656 scale : (float) 3657 size of the numeric entries or bin values 3658 scalarbar : (bool) 3659 add a scalar bar to the right of the plot 3660 lc : (str) 3661 color of the line separating the bins 3662 lw : (float) 3663 Width of the line separating the bins 3664 c : (str) 3665 text color 3666 alpha : (float) 3667 plot transparency 3668 3669 Examples: 3670 - [np_matrix.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/np_matrix.py) 3671 3672 ![](https://vedo.embl.es/images/pyplot/np_matrix.png) 3673 """ 3674 M = np.asarray(M) 3675 n, m = M.shape 3676 gr = shapes.Grid(res=(m, n), s=(m / (m + n) * 2, n / (m + n) * 2), c=c, alpha=alpha) 3677 gr.wireframe(False).lc(lc).lw(lw) 3678 3679 matr = np.flip(np.flip(M), axis=1).ravel(order="C") 3680 gr.cmap(cmap, matr, on="cells", vmin=vmin, vmax=vmax) 3681 sbar = None 3682 if scalarbar: 3683 gr.add_scalarbar3d(title_font=font, label_font=font) 3684 sbar = gr.scalarbar 3685 labs = None 3686 if scale != 0: 3687 gr.compute_normals(points=False) 3688 labs = gr.labels( 3689 on="cells", 3690 scale=scale / max(m, n), 3691 precision=precision, 3692 font=font, 3693 justify="center", 3694 c=c, 3695 ) 3696 labs.z(0.001) 3697 t = None 3698 if title: 3699 if title == "Matrix": 3700 title += " " + str(n) + "x" + str(m) 3701 t = shapes.Text3D(title, font=font, s=0.04, justify="bottom-center", c=c) 3702 t.shift(0, n / (m + n) * 1.05) 3703 3704 xlabs = None 3705 if len(xlabels) == m: 3706 xlabs = [] 3707 jus = "top-center" 3708 if xrotation > 44: 3709 jus = "right-center" 3710 for i in range(m): 3711 xl = shapes.Text3D(xlabels[i], font=font, s=0.02, justify=jus, c=c).rotate_z(xrotation) 3712 xl.shift((2 * i - m + 1) / (m + n), -n / (m + n) * 1.05) 3713 xlabs.append(xl) 3714 3715 ylabs = None 3716 if len(ylabels) == n: 3717 ylabels = list(reversed(ylabels)) 3718 ylabs = [] 3719 for i in range(n): 3720 yl = shapes.Text3D(ylabels[i], font=font, s=0.02, justify="right-center", c=c) 3721 yl.shift(-m / (m + n) * 1.05, (2 * i - n + 1) / (m + n)) 3722 ylabs.append(yl) 3723 3724 xt = None 3725 if xtitle: 3726 xt = shapes.Text3D(xtitle, font=font, s=0.035, justify="top-center", c=c) 3727 xt.shift(0, -n / (m + n) * 1.05) 3728 if xlabs is not None: 3729 y0, y1 = xlabs[0].ybounds() 3730 xt.shift(0, -(y1 - y0) - 0.55 / (m + n)) 3731 yt = None 3732 if ytitle: 3733 yt = shapes.Text3D(ytitle, font=font, s=0.035, justify="bottom-center", c=c).rotate_z(90) 3734 yt.shift(-m / (m + n) * 1.05, 0) 3735 if ylabs is not None: 3736 x0, x1 = ylabs[0].xbounds() 3737 yt.shift(-(x1 - x0) - 0.55 / (m + n), 0) 3738 asse = Assembly(gr, sbar, labs, t, xt, yt, xlabs, ylabs) 3739 asse.name = "Matrix" 3740 return asse
Generate a matrix, or a 2D color-coded plot with bin labels.
Returns an Assembly
object.
Arguments:
- M : (list, numpy array) the input array to visualize
- title : (str) title of the plot
- xtitle : (str) title of the horizontal colmuns
- ytitle : (str) title of the vertical rows
- xlabels : (list) individual string labels for each column. Must be of length m
- ylabels : (list) individual string labels for each row. Must be of length n
- xrotation : (float) rotation of the horizontal labels
- cmap : (str) color map name
- vmin : (float) minimum value of the colormap range
- vmax : (float) maximum value of the colormap range
- precision : (int) number of digits for the matrix entries or bins
- font : (str) font name. Check available fonts here.
- scale : (float) size of the numeric entries or bin values
- scalarbar : (bool) add a scalar bar to the right of the plot
- lc : (str) color of the line separating the bins
- lw : (float) Width of the line separating the bins
- c : (str) text color
- alpha : (float) plot transparency
Examples:
3890class DirectedGraph(Assembly): 3891 """ 3892 Support for Directed Graphs. 3893 """ 3894 3895 def __init__(self, **kargs): 3896 """ 3897 A graph consists of a collection of nodes (without postional information) 3898 and a collection of edges connecting pairs of nodes. 3899 The task is to determine the node positions only based on their connections. 3900 3901 This class is derived from class `Assembly`, and it assembles 4 Mesh objects 3902 representing the graph, the node labels, edge labels and edge arrows. 3903 3904 Arguments: 3905 c : (color) 3906 Color of the Graph 3907 n : (int) 3908 number of the initial set of nodes 3909 layout : (int, str) 3910 layout in 3911 `['2d', 'fast2d', 'clustering2d', 'circular', 'circular3d', 'cone', 'force', 'tree']`. 3912 Each of these layouts has different available options. 3913 3914 --------------------------------------------------------------- 3915 .. note:: Options for layouts '2d', 'fast2d' and 'clustering2d' 3916 3917 Arguments: 3918 seed : (int) 3919 seed of the random number generator used to jitter point positions 3920 rest_distance : (float) 3921 manually set the resting distance 3922 nmax : (int) 3923 the maximum number of iterations to be used 3924 zrange : (list) 3925 expand 2d graph along z axis. 3926 3927 --------------------------------------------------------------- 3928 .. note:: Options for layouts 'circular', and 'circular3d': 3929 3930 Arguments: 3931 radius : (float) 3932 set the radius of the circles 3933 height : (float) 3934 set the vertical (local z) distance between the circles 3935 zrange : (float) 3936 expand 2d graph along z axis 3937 3938 --------------------------------------------------------------- 3939 .. note:: Options for layout 'cone' 3940 3941 Arguments: 3942 compactness : (float) 3943 ratio between the average width of a cone in the tree, 3944 and the height of the cone. 3945 compression : (bool) 3946 put children closer together, possibly allowing sub-trees to overlap. 3947 This is useful if the tree is actually the spanning tree of a graph. 3948 spacing : (float) 3949 space between layers of the tree 3950 3951 --------------------------------------------------------------- 3952 .. note:: Options for layout 'force' 3953 3954 Arguments: 3955 seed : (int) 3956 seed the random number generator used to jitter point positions 3957 bounds : (list) 3958 set the region in space in which to place the final graph 3959 nmax : (int) 3960 the maximum number of iterations to be used 3961 three_dimensional : (bool) 3962 allow optimization in the 3rd dimension too 3963 random_initial_points : (bool) 3964 use random positions within the graph bounds as initial points 3965 3966 Examples: 3967 - [lineage_graph.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/lineage_graph.py) 3968 3969 ![](https://vedo.embl.es/images/pyplot/graph_lineage.png) 3970 3971 - [graph_network.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/graph_network.py) 3972 3973 ![](https://vedo.embl.es/images/pyplot/graph_network.png) 3974 """ 3975 3976 super().__init__() 3977 3978 self.nodes = [] 3979 self.edges = [] 3980 3981 self._node_labels = [] # holds strings 3982 self._edge_labels = [] 3983 self.edge_orientations = [] 3984 self.edge_glyph_position = 0.6 3985 3986 self.zrange = 0.0 3987 3988 self.rotX = 0 3989 self.rotY = 0 3990 self.rotZ = 0 3991 3992 self.arrow_scale = 0.15 3993 self.node_label_scale = None 3994 self.node_label_justify = "bottom-left" 3995 3996 self.edge_label_scale = None 3997 3998 self.mdg = vtki.new("MutableDirectedGraph") 3999 4000 n = kargs.pop("n", 0) 4001 for _ in range(n): 4002 self.add_node() 4003 4004 self._c = kargs.pop("c", (0.3, 0.3, 0.3)) 4005 4006 self.gl = vtki.new("GraphLayout") 4007 4008 self.font = kargs.pop("font", "") 4009 4010 s = kargs.pop("layout", "2d") 4011 if isinstance(s, int): 4012 ss = ["2d", "fast2d", "clustering2d", "circular", "circular3d", "cone", "force", "tree"] 4013 s = ss[s] 4014 self.layout = s 4015 4016 if "2d" in s: 4017 if "clustering" in s: 4018 self.strategy = vtki.new("Clustering2DLayoutStrategy") 4019 elif "fast" in s: 4020 self.strategy = vtki.new("Fast2DLayoutStrategy") 4021 else: 4022 self.strategy = vtki.new("Simple2DLayoutStrategy") 4023 self.rotX = 180 4024 opt = kargs.pop("rest_distance", None) 4025 if opt is not None: 4026 self.strategy.SetRestDistance(opt) 4027 opt = kargs.pop("seed", None) 4028 if opt is not None: 4029 self.strategy.SetRandomSeed(opt) 4030 opt = kargs.pop("nmax", None) 4031 if opt is not None: 4032 self.strategy.SetMaxNumberOfIterations(opt) 4033 self.zrange = kargs.pop("zrange", 0) 4034 4035 elif "circ" in s: 4036 if "3d" in s: 4037 self.strategy = vtki.new("Simple3DCirclesStrategy") 4038 self.strategy.SetDirection(0, 0, -1) 4039 self.strategy.SetAutoHeight(True) 4040 self.strategy.SetMethod(1) 4041 self.rotX = -90 4042 opt = kargs.pop("radius", None) # float 4043 if opt is not None: 4044 self.strategy.SetMethod(0) 4045 self.strategy.SetRadius(opt) # float 4046 opt = kargs.pop("height", None) 4047 if opt is not None: 4048 self.strategy.SetAutoHeight(False) 4049 self.strategy.SetHeight(opt) # float 4050 else: 4051 self.strategy = vtki.new("CircularLayoutStrategy") 4052 self.zrange = kargs.pop("zrange", 0) 4053 4054 elif "cone" in s: 4055 self.strategy = vtki.new("ConeLayoutStrategy") 4056 self.rotX = 180 4057 opt = kargs.pop("compactness", None) 4058 if opt is not None: 4059 self.strategy.SetCompactness(opt) 4060 opt = kargs.pop("compression", None) 4061 if opt is not None: 4062 self.strategy.SetCompression(opt) 4063 opt = kargs.pop("spacing", None) 4064 if opt is not None: 4065 self.strategy.SetSpacing(opt) 4066 4067 elif "force" in s: 4068 self.strategy = vtki.new("ForceDirectedLayoutStrategy") 4069 opt = kargs.pop("seed", None) 4070 if opt is not None: 4071 self.strategy.SetRandomSeed(opt) 4072 opt = kargs.pop("bounds", None) 4073 if opt is not None: 4074 self.strategy.SetAutomaticBoundsComputation(False) 4075 self.strategy.SetGraphBounds(opt) # list 4076 opt = kargs.pop("nmax", None) 4077 if opt is not None: 4078 self.strategy.SetMaxNumberOfIterations(opt) # int 4079 opt = kargs.pop("three_dimensional", True) 4080 if opt is not None: 4081 self.strategy.SetThreeDimensionalLayout(opt) # bool 4082 opt = kargs.pop("random_initial_points", None) 4083 if opt is not None: 4084 self.strategy.SetRandomInitialPoints(opt) # bool 4085 4086 elif "tree" in s: 4087 self.strategy = vtki.new("SpanTreeLayoutStrategy") 4088 self.rotX = 180 4089 4090 else: 4091 vedo.logger.error(f"Cannot understand layout {s}. Available layouts:") 4092 vedo.logger.error("[2d,fast2d,clustering2d,circular,circular3d,cone,force,tree]") 4093 raise RuntimeError() 4094 4095 self.gl.SetLayoutStrategy(self.strategy) 4096 4097 if len(kargs) > 0: 4098 vedo.logger.error(f"Cannot understand options: {kargs}") 4099 4100 def add_node(self, label="id") -> int: 4101 """Add a new node to the `Graph`.""" 4102 v = self.mdg.AddVertex() # vtk calls it vertex.. 4103 self.nodes.append(v) 4104 if label == "id": 4105 label = int(v) 4106 self._node_labels.append(str(label)) 4107 return v 4108 4109 def add_edge(self, v1, v2, label="") -> int: 4110 """Add a new edge between to nodes. 4111 An extra node is created automatically if needed.""" 4112 nv = len(self.nodes) 4113 if v1 >= nv: 4114 for _ in range(nv, v1 + 1): 4115 self.add_node() 4116 nv = len(self.nodes) 4117 if v2 >= nv: 4118 for _ in range(nv, v2 + 1): 4119 self.add_node() 4120 e = self.mdg.AddEdge(v1, v2) 4121 self.edges.append(e) 4122 self._edge_labels.append(str(label)) 4123 return e 4124 4125 def add_child(self, v, node_label="id", edge_label="") -> int: 4126 """Add a new edge to a new node as its child. 4127 The extra node is created automatically if needed.""" 4128 nv = len(self.nodes) 4129 if v >= nv: 4130 for _ in range(nv, v + 1): 4131 self.add_node() 4132 child = self.mdg.AddChild(v) 4133 self.edges.append((v, child)) 4134 self.nodes.append(child) 4135 if node_label == "id": 4136 node_label = int(child) 4137 self._node_labels.append(str(node_label)) 4138 self._edge_labels.append(str(edge_label)) 4139 return child 4140 4141 def build(self): 4142 """ 4143 Build the `DirectedGraph(Assembly)`. 4144 Accessory objects are also created for labels and arrows. 4145 """ 4146 self.gl.SetZRange(self.zrange) 4147 self.gl.SetInputData(self.mdg) 4148 self.gl.Update() 4149 4150 gr2poly = vtki.new("GraphToPolyData") 4151 gr2poly.EdgeGlyphOutputOn() 4152 gr2poly.SetEdgeGlyphPosition(self.edge_glyph_position) 4153 gr2poly.SetInputData(self.gl.GetOutput()) 4154 gr2poly.Update() 4155 4156 dgraph = Mesh(gr2poly.GetOutput(0)) 4157 # dgraph.clean() # WRONG!!! dont uncomment 4158 dgraph.flat().color(self._c).lw(2) 4159 dgraph.name = "DirectedGraph" 4160 4161 diagsz = self.diagonal_size() / 1.42 4162 if not diagsz: 4163 return None 4164 4165 dgraph.scale(1 / diagsz) 4166 if self.rotX: 4167 dgraph.rotate_x(self.rotX) 4168 if self.rotY: 4169 dgraph.rotate_y(self.rotY) 4170 if self.rotZ: 4171 dgraph.rotate_z(self.rotZ) 4172 4173 vecs = gr2poly.GetOutput(1).GetPointData().GetVectors() 4174 self.edge_orientations = utils.vtk2numpy(vecs) 4175 4176 # Use Glyph3D to repeat the glyph on all edges. 4177 arrows = None 4178 if self.arrow_scale: 4179 arrow_source = vtki.new("GlyphSource2D") 4180 arrow_source.SetGlyphTypeToEdgeArrow() 4181 arrow_source.SetScale(self.arrow_scale) 4182 arrow_source.Update() 4183 arrow_glyph = vtki.vtkGlyph3D() 4184 arrow_glyph.SetInputData(0, gr2poly.GetOutput(1)) 4185 arrow_glyph.SetInputData(1, arrow_source.GetOutput()) 4186 arrow_glyph.Update() 4187 arrows = Mesh(arrow_glyph.GetOutput()) 4188 arrows.scale(1 / diagsz) 4189 arrows.lighting("off").color(self._c) 4190 if self.rotX: 4191 arrows.rotate_x(self.rotX) 4192 if self.rotY: 4193 arrows.rotate_y(self.rotY) 4194 if self.rotZ: 4195 arrows.rotate_z(self.rotZ) 4196 arrows.name = "DirectedGraphArrows" 4197 4198 node_labels = None 4199 if self._node_labels: 4200 node_labels = dgraph.labels( 4201 self._node_labels, 4202 scale=self.node_label_scale, 4203 precision=0, 4204 font=self.font, 4205 justify=self.node_label_justify, 4206 ) 4207 node_labels.color(self._c).pickable(True) 4208 node_labels.name = "DirectedGraphNodeLabels" 4209 4210 edge_labels = None 4211 if self._edge_labels: 4212 edge_labels = dgraph.labels( 4213 self._edge_labels, on="cells", scale=self.edge_label_scale, precision=0, font=self.font 4214 ) 4215 edge_labels.color(self._c).pickable(True) 4216 edge_labels.name = "DirectedGraphEdgeLabels" 4217 4218 super().__init__([dgraph, node_labels, edge_labels, arrows]) 4219 self.name = "DirectedGraphAssembly" 4220 return self
Support for Directed Graphs.
3895 def __init__(self, **kargs): 3896 """ 3897 A graph consists of a collection of nodes (without postional information) 3898 and a collection of edges connecting pairs of nodes. 3899 The task is to determine the node positions only based on their connections. 3900 3901 This class is derived from class `Assembly`, and it assembles 4 Mesh objects 3902 representing the graph, the node labels, edge labels and edge arrows. 3903 3904 Arguments: 3905 c : (color) 3906 Color of the Graph 3907 n : (int) 3908 number of the initial set of nodes 3909 layout : (int, str) 3910 layout in 3911 `['2d', 'fast2d', 'clustering2d', 'circular', 'circular3d', 'cone', 'force', 'tree']`. 3912 Each of these layouts has different available options. 3913 3914 --------------------------------------------------------------- 3915 .. note:: Options for layouts '2d', 'fast2d' and 'clustering2d' 3916 3917 Arguments: 3918 seed : (int) 3919 seed of the random number generator used to jitter point positions 3920 rest_distance : (float) 3921 manually set the resting distance 3922 nmax : (int) 3923 the maximum number of iterations to be used 3924 zrange : (list) 3925 expand 2d graph along z axis. 3926 3927 --------------------------------------------------------------- 3928 .. note:: Options for layouts 'circular', and 'circular3d': 3929 3930 Arguments: 3931 radius : (float) 3932 set the radius of the circles 3933 height : (float) 3934 set the vertical (local z) distance between the circles 3935 zrange : (float) 3936 expand 2d graph along z axis 3937 3938 --------------------------------------------------------------- 3939 .. note:: Options for layout 'cone' 3940 3941 Arguments: 3942 compactness : (float) 3943 ratio between the average width of a cone in the tree, 3944 and the height of the cone. 3945 compression : (bool) 3946 put children closer together, possibly allowing sub-trees to overlap. 3947 This is useful if the tree is actually the spanning tree of a graph. 3948 spacing : (float) 3949 space between layers of the tree 3950 3951 --------------------------------------------------------------- 3952 .. note:: Options for layout 'force' 3953 3954 Arguments: 3955 seed : (int) 3956 seed the random number generator used to jitter point positions 3957 bounds : (list) 3958 set the region in space in which to place the final graph 3959 nmax : (int) 3960 the maximum number of iterations to be used 3961 three_dimensional : (bool) 3962 allow optimization in the 3rd dimension too 3963 random_initial_points : (bool) 3964 use random positions within the graph bounds as initial points 3965 3966 Examples: 3967 - [lineage_graph.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/lineage_graph.py) 3968 3969 ![](https://vedo.embl.es/images/pyplot/graph_lineage.png) 3970 3971 - [graph_network.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/pyplot/graph_network.py) 3972 3973 ![](https://vedo.embl.es/images/pyplot/graph_network.png) 3974 """ 3975 3976 super().__init__() 3977 3978 self.nodes = [] 3979 self.edges = [] 3980 3981 self._node_labels = [] # holds strings 3982 self._edge_labels = [] 3983 self.edge_orientations = [] 3984 self.edge_glyph_position = 0.6 3985 3986 self.zrange = 0.0 3987 3988 self.rotX = 0 3989 self.rotY = 0 3990 self.rotZ = 0 3991 3992 self.arrow_scale = 0.15 3993 self.node_label_scale = None 3994 self.node_label_justify = "bottom-left" 3995 3996 self.edge_label_scale = None 3997 3998 self.mdg = vtki.new("MutableDirectedGraph") 3999 4000 n = kargs.pop("n", 0) 4001 for _ in range(n): 4002 self.add_node() 4003 4004 self._c = kargs.pop("c", (0.3, 0.3, 0.3)) 4005 4006 self.gl = vtki.new("GraphLayout") 4007 4008 self.font = kargs.pop("font", "") 4009 4010 s = kargs.pop("layout", "2d") 4011 if isinstance(s, int): 4012 ss = ["2d", "fast2d", "clustering2d", "circular", "circular3d", "cone", "force", "tree"] 4013 s = ss[s] 4014 self.layout = s 4015 4016 if "2d" in s: 4017 if "clustering" in s: 4018 self.strategy = vtki.new("Clustering2DLayoutStrategy") 4019 elif "fast" in s: 4020 self.strategy = vtki.new("Fast2DLayoutStrategy") 4021 else: 4022 self.strategy = vtki.new("Simple2DLayoutStrategy") 4023 self.rotX = 180 4024 opt = kargs.pop("rest_distance", None) 4025 if opt is not None: 4026 self.strategy.SetRestDistance(opt) 4027 opt = kargs.pop("seed", None) 4028 if opt is not None: 4029 self.strategy.SetRandomSeed(opt) 4030 opt = kargs.pop("nmax", None) 4031 if opt is not None: 4032 self.strategy.SetMaxNumberOfIterations(opt) 4033 self.zrange = kargs.pop("zrange", 0) 4034 4035 elif "circ" in s: 4036 if "3d" in s: 4037 self.strategy = vtki.new("Simple3DCirclesStrategy") 4038 self.strategy.SetDirection(0, 0, -1) 4039 self.strategy.SetAutoHeight(True) 4040 self.strategy.SetMethod(1) 4041 self.rotX = -90 4042 opt = kargs.pop("radius", None) # float 4043 if opt is not None: 4044 self.strategy.SetMethod(0) 4045 self.strategy.SetRadius(opt) # float 4046 opt = kargs.pop("height", None) 4047 if opt is not None: 4048 self.strategy.SetAutoHeight(False) 4049 self.strategy.SetHeight(opt) # float 4050 else: 4051 self.strategy = vtki.new("CircularLayoutStrategy") 4052 self.zrange = kargs.pop("zrange", 0) 4053 4054 elif "cone" in s: 4055 self.strategy = vtki.new("ConeLayoutStrategy") 4056 self.rotX = 180 4057 opt = kargs.pop("compactness", None) 4058 if opt is not None: 4059 self.strategy.SetCompactness(opt) 4060 opt = kargs.pop("compression", None) 4061 if opt is not None: 4062 self.strategy.SetCompression(opt) 4063 opt = kargs.pop("spacing", None) 4064 if opt is not None: 4065 self.strategy.SetSpacing(opt) 4066 4067 elif "force" in s: 4068 self.strategy = vtki.new("ForceDirectedLayoutStrategy") 4069 opt = kargs.pop("seed", None) 4070 if opt is not None: 4071 self.strategy.SetRandomSeed(opt) 4072 opt = kargs.pop("bounds", None) 4073 if opt is not None: 4074 self.strategy.SetAutomaticBoundsComputation(False) 4075 self.strategy.SetGraphBounds(opt) # list 4076 opt = kargs.pop("nmax", None) 4077 if opt is not None: 4078 self.strategy.SetMaxNumberOfIterations(opt) # int 4079 opt = kargs.pop("three_dimensional", True) 4080 if opt is not None: 4081 self.strategy.SetThreeDimensionalLayout(opt) # bool 4082 opt = kargs.pop("random_initial_points", None) 4083 if opt is not None: 4084 self.strategy.SetRandomInitialPoints(opt) # bool 4085 4086 elif "tree" in s: 4087 self.strategy = vtki.new("SpanTreeLayoutStrategy") 4088 self.rotX = 180 4089 4090 else: 4091 vedo.logger.error(f"Cannot understand layout {s}. Available layouts:") 4092 vedo.logger.error("[2d,fast2d,clustering2d,circular,circular3d,cone,force,tree]") 4093 raise RuntimeError() 4094 4095 self.gl.SetLayoutStrategy(self.strategy) 4096 4097 if len(kargs) > 0: 4098 vedo.logger.error(f"Cannot understand options: {kargs}")
A graph consists of a collection of nodes (without postional information) and a collection of edges connecting pairs of nodes. The task is to determine the node positions only based on their connections.
This class is derived from class Assembly
, and it assembles 4 Mesh objects
representing the graph, the node labels, edge labels and edge arrows.
Arguments:
- c : (color) Color of the Graph
- n : (int) number of the initial set of nodes
- layout : (int, str)
layout in
['2d', 'fast2d', 'clustering2d', 'circular', 'circular3d', 'cone', 'force', 'tree']
. Each of these layouts has different available options.
Options for layouts '2d', 'fast2d' and 'clustering2d'
Arguments:
- seed : (int) seed of the random number generator used to jitter point positions
- rest_distance : (float) manually set the resting distance
- nmax : (int) the maximum number of iterations to be used
- zrange : (list) expand 2d graph along z axis.
Options for layouts 'circular', and 'circular3d':
Arguments:
- radius : (float) set the radius of the circles
- height : (float) set the vertical (local z) distance between the circles
- zrange : (float) expand 2d graph along z axis
Options for layout 'cone'
Arguments:
- compactness : (float) ratio between the average width of a cone in the tree, and the height of the cone.
- compression : (bool) put children closer together, possibly allowing sub-trees to overlap. This is useful if the tree is actually the spanning tree of a graph.
- spacing : (float) space between layers of the tree
Options for layout 'force'
Arguments:
- seed : (int) seed the random number generator used to jitter point positions
- bounds : (list) set the region in space in which to place the final graph
- nmax : (int) the maximum number of iterations to be used
- three_dimensional : (bool) allow optimization in the 3rd dimension too
- random_initial_points : (bool) use random positions within the graph bounds as initial points
Examples:
4100 def add_node(self, label="id") -> int: 4101 """Add a new node to the `Graph`.""" 4102 v = self.mdg.AddVertex() # vtk calls it vertex.. 4103 self.nodes.append(v) 4104 if label == "id": 4105 label = int(v) 4106 self._node_labels.append(str(label)) 4107 return v
Add a new node to the Graph
.
4109 def add_edge(self, v1, v2, label="") -> int: 4110 """Add a new edge between to nodes. 4111 An extra node is created automatically if needed.""" 4112 nv = len(self.nodes) 4113 if v1 >= nv: 4114 for _ in range(nv, v1 + 1): 4115 self.add_node() 4116 nv = len(self.nodes) 4117 if v2 >= nv: 4118 for _ in range(nv, v2 + 1): 4119 self.add_node() 4120 e = self.mdg.AddEdge(v1, v2) 4121 self.edges.append(e) 4122 self._edge_labels.append(str(label)) 4123 return e
Add a new edge between to nodes. An extra node is created automatically if needed.
4125 def add_child(self, v, node_label="id", edge_label="") -> int: 4126 """Add a new edge to a new node as its child. 4127 The extra node is created automatically if needed.""" 4128 nv = len(self.nodes) 4129 if v >= nv: 4130 for _ in range(nv, v + 1): 4131 self.add_node() 4132 child = self.mdg.AddChild(v) 4133 self.edges.append((v, child)) 4134 self.nodes.append(child) 4135 if node_label == "id": 4136 node_label = int(child) 4137 self._node_labels.append(str(node_label)) 4138 self._edge_labels.append(str(edge_label)) 4139 return child
Add a new edge to a new node as its child. The extra node is created automatically if needed.
4141 def build(self): 4142 """ 4143 Build the `DirectedGraph(Assembly)`. 4144 Accessory objects are also created for labels and arrows. 4145 """ 4146 self.gl.SetZRange(self.zrange) 4147 self.gl.SetInputData(self.mdg) 4148 self.gl.Update() 4149 4150 gr2poly = vtki.new("GraphToPolyData") 4151 gr2poly.EdgeGlyphOutputOn() 4152 gr2poly.SetEdgeGlyphPosition(self.edge_glyph_position) 4153 gr2poly.SetInputData(self.gl.GetOutput()) 4154 gr2poly.Update() 4155 4156 dgraph = Mesh(gr2poly.GetOutput(0)) 4157 # dgraph.clean() # WRONG!!! dont uncomment 4158 dgraph.flat().color(self._c).lw(2) 4159 dgraph.name = "DirectedGraph" 4160 4161 diagsz = self.diagonal_size() / 1.42 4162 if not diagsz: 4163 return None 4164 4165 dgraph.scale(1 / diagsz) 4166 if self.rotX: 4167 dgraph.rotate_x(self.rotX) 4168 if self.rotY: 4169 dgraph.rotate_y(self.rotY) 4170 if self.rotZ: 4171 dgraph.rotate_z(self.rotZ) 4172 4173 vecs = gr2poly.GetOutput(1).GetPointData().GetVectors() 4174 self.edge_orientations = utils.vtk2numpy(vecs) 4175 4176 # Use Glyph3D to repeat the glyph on all edges. 4177 arrows = None 4178 if self.arrow_scale: 4179 arrow_source = vtki.new("GlyphSource2D") 4180 arrow_source.SetGlyphTypeToEdgeArrow() 4181 arrow_source.SetScale(self.arrow_scale) 4182 arrow_source.Update() 4183 arrow_glyph = vtki.vtkGlyph3D() 4184 arrow_glyph.SetInputData(0, gr2poly.GetOutput(1)) 4185 arrow_glyph.SetInputData(1, arrow_source.GetOutput()) 4186 arrow_glyph.Update() 4187 arrows = Mesh(arrow_glyph.GetOutput()) 4188 arrows.scale(1 / diagsz) 4189 arrows.lighting("off").color(self._c) 4190 if self.rotX: 4191 arrows.rotate_x(self.rotX) 4192 if self.rotY: 4193 arrows.rotate_y(self.rotY) 4194 if self.rotZ: 4195 arrows.rotate_z(self.rotZ) 4196 arrows.name = "DirectedGraphArrows" 4197 4198 node_labels = None 4199 if self._node_labels: 4200 node_labels = dgraph.labels( 4201 self._node_labels, 4202 scale=self.node_label_scale, 4203 precision=0, 4204 font=self.font, 4205 justify=self.node_label_justify, 4206 ) 4207 node_labels.color(self._c).pickable(True) 4208 node_labels.name = "DirectedGraphNodeLabels" 4209 4210 edge_labels = None 4211 if self._edge_labels: 4212 edge_labels = dgraph.labels( 4213 self._edge_labels, on="cells", scale=self.edge_label_scale, precision=0, font=self.font 4214 ) 4215 edge_labels.color(self._c).pickable(True) 4216 edge_labels.name = "DirectedGraphEdgeLabels" 4217 4218 super().__init__([dgraph, node_labels, edge_labels, arrows]) 4219 self.name = "DirectedGraphAssembly" 4220 return self
Build the DirectedGraph(Assembly)
.
Accessory objects are also created for labels and arrows.