vispy 0.14.3__cp311-cp311-macosx_11_0_arm64.whl → 0.15.2__cp311-cp311-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of vispy might be problematic. Click here for more details.
- vispy/app/backends/_qt.py +46 -11
- vispy/color/colormap.py +106 -27
- vispy/color/tests/test_color.py +27 -1
- vispy/ext/cocoapy.py +1 -21
- vispy/geometry/meshdata.py +3 -1
- vispy/geometry/tests/test_triangulation.py +88 -0
- vispy/geometry/triangulation.py +11 -13
- vispy/gloo/program.py +2 -2
- vispy/scene/cameras/arcball.py +1 -2
- vispy/scene/cameras/base_camera.py +63 -50
- vispy/scene/cameras/panzoom.py +4 -1
- vispy/scene/cameras/perspective.py +6 -1
- vispy/scene/cameras/turntable.py +11 -1
- vispy/scene/widgets/grid.py +191 -71
- vispy/scene/widgets/widget.py +11 -0
- vispy/version.py +9 -4
- vispy/visuals/_scalable_textures.py +5 -4
- vispy/visuals/filters/mesh.py +6 -1
- vispy/visuals/gridlines.py +61 -5
- vispy/visuals/image.py +19 -7
- vispy/visuals/surface_plot.py +1 -1
- vispy/visuals/tests/test_gridlines.py +30 -0
- vispy/visuals/tests/test_image.py +17 -15
- vispy/visuals/tests/test_scalable_textures.py +16 -0
- vispy/visuals/tests/test_surface_plot.py +8 -3
- vispy/visuals/text/_sdf_cpu.cpython-311-darwin.so +0 -0
- {vispy-0.14.3.dist-info → vispy-0.15.2.dist-info}/METADATA +50 -27
- {vispy-0.14.3.dist-info → vispy-0.15.2.dist-info}/RECORD +31 -30
- {vispy-0.14.3.dist-info → vispy-0.15.2.dist-info}/WHEEL +2 -1
- {vispy-0.14.3.dist-info → vispy-0.15.2.dist-info/licenses}/LICENSE.txt +1 -1
- {vispy-0.14.3.dist-info → vispy-0.15.2.dist-info}/top_level.txt +0 -0
vispy/scene/widgets/grid.py
CHANGED
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
"""Grid widget for providing a gridded layout to child widgets."""
|
|
5
5
|
|
|
6
6
|
from __future__ import division
|
|
7
|
+
|
|
8
|
+
from typing import Tuple, Union, Dict
|
|
9
|
+
|
|
7
10
|
import numpy as np
|
|
11
|
+
from numpy.typing import NDArray
|
|
8
12
|
|
|
9
13
|
from vispy.geometry import Rect
|
|
10
14
|
from .widget import Widget
|
|
@@ -21,17 +25,19 @@ class Grid(Widget):
|
|
|
21
25
|
|
|
22
26
|
Parameters
|
|
23
27
|
----------
|
|
24
|
-
spacing : int
|
|
25
|
-
Spacing between widgets.
|
|
28
|
+
spacing : int | tuple[int, int]
|
|
29
|
+
Spacing between widgets. If `tuple` then it must be of length two, the first element
|
|
30
|
+
being `width_spacing` and the second being `height_spacing`.
|
|
26
31
|
**kwargs : dict
|
|
27
32
|
Keyword arguments to pass to `Widget`.
|
|
28
33
|
"""
|
|
29
34
|
|
|
30
|
-
def __init__(self, spacing=
|
|
35
|
+
def __init__(self, spacing=0, **kwargs):
|
|
31
36
|
"""Create solver and basic grid parameters."""
|
|
32
37
|
self._next_cell = [0, 0] # row, col
|
|
33
38
|
self._cells = {}
|
|
34
39
|
self._grid_widgets = {}
|
|
40
|
+
|
|
35
41
|
self.spacing = spacing
|
|
36
42
|
self._n_added = 0
|
|
37
43
|
self._default_class = ViewBox # what to add when __getitem__ is used
|
|
@@ -45,9 +51,6 @@ class Grid(Widget):
|
|
|
45
51
|
self._width_grid = None
|
|
46
52
|
self._height_grid = None
|
|
47
53
|
|
|
48
|
-
# self._height_stay = None
|
|
49
|
-
# self._width_stay = None
|
|
50
|
-
|
|
51
54
|
Widget.__init__(self, **kwargs)
|
|
52
55
|
|
|
53
56
|
def __getitem__(self, idxs):
|
|
@@ -256,53 +259,129 @@ class Grid(Widget):
|
|
|
256
259
|
locs[r:r + rs, c:c + cs] = key
|
|
257
260
|
return locs
|
|
258
261
|
|
|
262
|
+
@property
|
|
263
|
+
def spacing(self):
|
|
264
|
+
"""
|
|
265
|
+
The spacing between individual Viewbox widgets in the grid.
|
|
266
|
+
"""
|
|
267
|
+
return self._spacing
|
|
268
|
+
|
|
269
|
+
@spacing.setter
|
|
270
|
+
def spacing(self, value: Union[int, Tuple[int, int]]):
|
|
271
|
+
if not (
|
|
272
|
+
isinstance(value, int)
|
|
273
|
+
or isinstance(value, tuple)
|
|
274
|
+
and len(value) == 2
|
|
275
|
+
and isinstance(value[0], int)
|
|
276
|
+
and isinstance(value[1], int)
|
|
277
|
+
):
|
|
278
|
+
raise ValueError('spacing must be of type int | tuple[int, int]')
|
|
279
|
+
|
|
280
|
+
self._spacing = value
|
|
281
|
+
self._need_solver_recreate = True
|
|
282
|
+
|
|
259
283
|
def __repr__(self):
|
|
260
284
|
return (('<Grid at %s:\n' % hex(id(self))) +
|
|
261
285
|
str(self.layout_array + 1) + '>')
|
|
262
286
|
|
|
263
287
|
@staticmethod
|
|
264
|
-
def
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
for w in ws[1:]:
|
|
268
|
-
width_expr += w
|
|
269
|
-
solver.addConstraint(width_expr == _var_w)
|
|
288
|
+
def _add_total_dim_length_constraints(solver: Solver, grid_dim_variables: NDArray[Variable],
|
|
289
|
+
n_added: int, _var_dim_length: Variable, spacing: float):
|
|
290
|
+
"""Add constraint: total height == sum(col heights) + sum(spacing).
|
|
270
291
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
292
|
+
The total height of the grid is constrained to be equal to the sum of the heights of
|
|
293
|
+
its columns, including spacing between widgets.
|
|
294
|
+
|
|
295
|
+
Parameters
|
|
296
|
+
----------
|
|
297
|
+
solver: Solver
|
|
298
|
+
Solver for a system of linear equations.
|
|
299
|
+
grid_dim_variables: NDArray[Variable]:
|
|
300
|
+
The grid of width or height variables of either shape col * row or row * col with each element being a
|
|
301
|
+
Variable in the solver representing the height or width of each grid box.
|
|
302
|
+
n_added: int
|
|
303
|
+
The number of ViewBoxes added to the grid.
|
|
304
|
+
_var_dim_length: Variable
|
|
305
|
+
The solver variable representing either total width or height of the grid.
|
|
306
|
+
spacing: float
|
|
307
|
+
The amount of spacing between single adjacent Viewbox widgets in the grid.
|
|
308
|
+
"""
|
|
309
|
+
total_spacing = 0
|
|
310
|
+
if n_added > 1:
|
|
311
|
+
for _ in range(grid_dim_variables.shape[1] - 1):
|
|
312
|
+
total_spacing += spacing
|
|
313
|
+
|
|
314
|
+
for ds in grid_dim_variables:
|
|
315
|
+
dim_length_expr = ds[0]
|
|
316
|
+
for d in ds[1:]:
|
|
317
|
+
dim_length_expr += d
|
|
318
|
+
dim_length_expr += total_spacing
|
|
319
|
+
solver.addConstraint(dim_length_expr == _var_dim_length)
|
|
278
320
|
|
|
279
321
|
@staticmethod
|
|
280
|
-
def
|
|
322
|
+
def _add_gridding_dim_constraints(solver: Solver, grid_dim_variables: NDArray[Variable]):
|
|
323
|
+
"""Add constraint: all viewbox dims in each dimension are equal.
|
|
324
|
+
|
|
325
|
+
With all dims the reserved space for a widget with a col_span and row_span of 1 is meant, e.g. we have 3
|
|
326
|
+
widgets arranged in columns or rows with col_span or row_span 1 and those are being constrained to all be of
|
|
327
|
+
width/height 100. In other words the same dim length is reserved for each position in the grid, not taking
|
|
328
|
+
into account the spacing between grid positions.
|
|
329
|
+
|
|
330
|
+
Parameters
|
|
331
|
+
----------
|
|
332
|
+
solver: Solver
|
|
333
|
+
Solver for a system of linear equations.
|
|
334
|
+
grid_dim_variables:
|
|
335
|
+
The grid of width or height variables of either shape col * row or row * col with each element being a
|
|
336
|
+
Variable in the solver representing the height or width of each grid box.
|
|
337
|
+
"""
|
|
281
338
|
# access widths of one "y", different x
|
|
282
|
-
for
|
|
283
|
-
for
|
|
284
|
-
solver.addConstraint(
|
|
339
|
+
for ds in grid_dim_variables.T:
|
|
340
|
+
for d in ds[1:]:
|
|
341
|
+
solver.addConstraint(ds[0] == d)
|
|
285
342
|
|
|
286
343
|
@staticmethod
|
|
287
|
-
def
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
344
|
+
def _add_stretch_constraints(solver: Solver, width_grid: NDArray[Variable] , height_grid: NDArray[Variable],
|
|
345
|
+
grid_widgets: Dict[int, Tuple[int, int, int, int, ViewBox]],
|
|
346
|
+
widget_grid: NDArray[ViewBox]):
|
|
347
|
+
"""
|
|
348
|
+
Add proportional stretch constraints to the linear system solver of the grid.
|
|
292
349
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
350
|
+
This method enforces that grid rows and columns stretch in proportion
|
|
351
|
+
to the widgets' specified stretch factors. It uses weak constraints
|
|
352
|
+
so that proportionality is preserved when possible but can be violated
|
|
353
|
+
if stronger layout constraints are present.
|
|
354
|
+
|
|
355
|
+
Parameters
|
|
356
|
+
----------
|
|
357
|
+
solver : Solver
|
|
358
|
+
Solver for a system of linear equations.
|
|
359
|
+
width_grid : NDArray[Variable]
|
|
360
|
+
The grid of width variables in the linear system of equations to be solved.
|
|
361
|
+
height_grid : NDArray[Variable]
|
|
362
|
+
The grid of height variables in the linear system of equations to be solved.
|
|
363
|
+
grid_widgets : dict[int, tuple[int, int, int, int, ViewBox]]
|
|
364
|
+
Dictionary mapping order of viewboxes added as int to their grid layout description:
|
|
365
|
+
(start_y, start_x, span_y, span_x, ViewBox).
|
|
366
|
+
widget_grid : NDArray[ViewBox]
|
|
367
|
+
Array of viewboxes in shape n_columns x n_rows.
|
|
368
|
+
|
|
369
|
+
Notes
|
|
370
|
+
-----
|
|
371
|
+
- Stretch constraints are added with 'weak' strength, allowing them to
|
|
372
|
+
be overridden by stronger constraints such as fixed sizes or min/max bounds.
|
|
373
|
+
- The constraint `total_size / stretch_factor` is used to maintain
|
|
374
|
+
proportional relationships among rows and columns.
|
|
375
|
+
"""
|
|
296
376
|
xmax = len(height_grid)
|
|
297
377
|
ymax = len(width_grid)
|
|
298
378
|
|
|
299
|
-
stretch_widths = [[] for _ in range(
|
|
300
|
-
stretch_heights = [[] for _ in range(
|
|
379
|
+
stretch_widths = [[] for _ in range(ymax)]
|
|
380
|
+
stretch_heights = [[] for _ in range(xmax)]
|
|
301
381
|
|
|
302
382
|
for (y, x, ys, xs, widget) in grid_widgets.values():
|
|
303
383
|
for ws in width_grid[y:y+ys]:
|
|
304
384
|
total_w = np.sum(ws[x:x+xs])
|
|
305
|
-
|
|
306
385
|
for sw in stretch_widths[y:y+ys]:
|
|
307
386
|
sw.append((total_w, widget.stretch[0]))
|
|
308
387
|
|
|
@@ -339,14 +418,36 @@ class Grid(Widget):
|
|
|
339
418
|
'weak')
|
|
340
419
|
|
|
341
420
|
@staticmethod
|
|
342
|
-
def _add_widget_dim_constraints(solver, width_grid, height_grid,
|
|
343
|
-
total_var_w, total_var_h,
|
|
421
|
+
def _add_widget_dim_constraints(solver: Solver, width_grid: NDArray[Variable], height_grid: NDArray[Variable],
|
|
422
|
+
total_var_w: Variable, total_var_h: Variable,
|
|
423
|
+
grid_widgets: Dict[int, Tuple[int, int, int, int, ViewBox]]):
|
|
424
|
+
"""Add constraints based on min/max width/height of widgets.
|
|
425
|
+
|
|
426
|
+
These constraints ensure that each widget's dimensions stay within its
|
|
427
|
+
specified minimum and maximum values.
|
|
428
|
+
|
|
429
|
+
Parameters
|
|
430
|
+
----------
|
|
431
|
+
solver : Solver
|
|
432
|
+
Solver for a system of linear equations.
|
|
433
|
+
width_grid : NDArray[Variable]
|
|
434
|
+
The grid of width variables in the linear system of equations to be solved.
|
|
435
|
+
height_grid : NDArray[Variable]
|
|
436
|
+
The grid of height variables in the linear system of equations to be solved.
|
|
437
|
+
total_var_w : Variable
|
|
438
|
+
The Variable representing the total width of the grid in the linear system of equations.
|
|
439
|
+
total_var_w : Variable
|
|
440
|
+
The Variable representing the total height of the grid in the linear system of equations.
|
|
441
|
+
grid_widgets : dict[int, tuple[int, int, int, int, ViewBox]]
|
|
442
|
+
Dictionary mapping order of viewboxes added as int to their grid layout description:
|
|
443
|
+
(start_y, start_x, span_y, span_x, ViewBox).
|
|
444
|
+
"""
|
|
344
445
|
assert(total_var_w is not None)
|
|
345
446
|
assert(total_var_h is not None)
|
|
346
447
|
|
|
347
448
|
for ws in width_grid:
|
|
348
449
|
for w in ws:
|
|
349
|
-
solver.addConstraint(w >= 0
|
|
450
|
+
solver.addConstraint(w >= 0)
|
|
350
451
|
|
|
351
452
|
for hs in height_grid:
|
|
352
453
|
for h in hs:
|
|
@@ -375,6 +476,7 @@ class Grid(Widget):
|
|
|
375
476
|
solver.addConstraint(total_h <= total_var_h)
|
|
376
477
|
|
|
377
478
|
def _recreate_solver(self):
|
|
479
|
+
"""Recreate the linear system solver with all constraints."""
|
|
378
480
|
self._solver.reset()
|
|
379
481
|
self._var_w = Variable("w_rect")
|
|
380
482
|
self._var_h = Variable("h_rect")
|
|
@@ -390,43 +492,40 @@ class Grid(Widget):
|
|
|
390
492
|
self._solver.addConstraint(self._var_w >= 0)
|
|
391
493
|
self._solver.addConstraint(self._var_h >= 0)
|
|
392
494
|
|
|
393
|
-
# self._height_stay = None
|
|
394
|
-
# self._width_stay = None
|
|
395
|
-
|
|
396
495
|
# add widths
|
|
397
|
-
self._width_grid = np.array(
|
|
398
|
-
|
|
399
|
-
|
|
496
|
+
self._width_grid = np.array(
|
|
497
|
+
[
|
|
498
|
+
[Variable(f"width(x: {x}, y: {y})") for x in range(xmax)]
|
|
499
|
+
for y in range(ymax)
|
|
500
|
+
]
|
|
501
|
+
)
|
|
400
502
|
|
|
401
503
|
# add heights
|
|
402
|
-
self._height_grid = np.array(
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
504
|
+
self._height_grid = np.array(
|
|
505
|
+
[
|
|
506
|
+
[Variable(f"height(x: {x}, y: {y})") for y in range(ymax)]
|
|
507
|
+
for x in range(xmax)
|
|
508
|
+
]
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
if isinstance(self.spacing, tuple):
|
|
512
|
+
width_spacing, height_spacing = self.spacing
|
|
513
|
+
else:
|
|
514
|
+
width_spacing = height_spacing = self.spacing
|
|
414
515
|
# even though these are REQUIRED, these should never fail
|
|
415
516
|
# since they're added first, and thus the slack will "simply work".
|
|
416
|
-
Grid.
|
|
417
|
-
self._width_grid, self._var_w)
|
|
418
|
-
Grid.
|
|
419
|
-
self._height_grid, self._var_h)
|
|
517
|
+
Grid._add_total_dim_length_constraints(self._solver,
|
|
518
|
+
self._width_grid, self._n_added, self._var_w, width_spacing)
|
|
519
|
+
Grid._add_total_dim_length_constraints(self._solver,
|
|
520
|
+
self._height_grid, self._n_added, self._var_h, height_spacing)
|
|
420
521
|
|
|
421
522
|
try:
|
|
422
523
|
# these are REQUIRED constraints for width and height.
|
|
423
524
|
# These are the constraints which can fail if
|
|
424
525
|
# the corresponding dimension of the widget cannot be fit in the
|
|
425
526
|
# grid.
|
|
426
|
-
Grid.
|
|
427
|
-
|
|
428
|
-
Grid._add_gridding_height_constraints(self._solver,
|
|
429
|
-
self._height_grid)
|
|
527
|
+
Grid._add_gridding_dim_constraints(self._solver, self._width_grid)
|
|
528
|
+
Grid._add_gridding_dim_constraints(self._solver, self._height_grid)
|
|
430
529
|
except UnsatisfiableConstraint:
|
|
431
530
|
self._need_solver_recreate = True
|
|
432
531
|
|
|
@@ -436,24 +535,27 @@ class Grid(Widget):
|
|
|
436
535
|
self._width_grid,
|
|
437
536
|
self._height_grid,
|
|
438
537
|
self._grid_widgets,
|
|
439
|
-
self._widget_grid
|
|
538
|
+
self._widget_grid,
|
|
539
|
+
)
|
|
440
540
|
|
|
441
541
|
Grid._add_widget_dim_constraints(self._solver,
|
|
442
542
|
self._width_grid,
|
|
443
543
|
self._height_grid,
|
|
444
544
|
self._var_w,
|
|
445
545
|
self._var_h,
|
|
446
|
-
self._grid_widgets
|
|
546
|
+
self._grid_widgets
|
|
547
|
+
)
|
|
447
548
|
|
|
448
549
|
self._solver.updateVariables()
|
|
449
550
|
|
|
450
551
|
def _update_child_widget_dim(self):
|
|
552
|
+
"""Solve the linear system of equations in order to assign Viewbox parameters such as position."""
|
|
451
553
|
# think in terms of (x, y). (row, col) makes code harder to read
|
|
452
554
|
ymax, xmax = self.grid_size
|
|
453
555
|
if ymax <= 0 or xmax <= 0:
|
|
454
556
|
return
|
|
455
557
|
|
|
456
|
-
rect = self.rect
|
|
558
|
+
rect = self.rect.padded(self.padding + self.margin)
|
|
457
559
|
if rect.width <= 0 or rect.height <= 0:
|
|
458
560
|
return
|
|
459
561
|
if self._need_solver_recreate:
|
|
@@ -474,22 +576,40 @@ class Grid(Widget):
|
|
|
474
576
|
|
|
475
577
|
value_vectorized = np.vectorize(lambda x: x.value())
|
|
476
578
|
|
|
477
|
-
|
|
579
|
+
if isinstance(self.spacing, tuple):
|
|
580
|
+
width_spacing, height_spacing = self.spacing
|
|
581
|
+
else:
|
|
582
|
+
width_spacing = height_spacing = self.spacing
|
|
583
|
+
|
|
584
|
+
for index, (_, val) in enumerate(self._grid_widgets.items()):
|
|
478
585
|
(row, col, rspan, cspan, widget) = val
|
|
479
586
|
|
|
587
|
+
# If spacing, always one spacing unit between 2 grid positions, even when span is > 1.
|
|
588
|
+
# If span is > 1, spacing will be added to the dim length of Viewbox
|
|
589
|
+
spacing_width_offset = col * width_spacing if self._n_added > 1 else 0
|
|
590
|
+
spacing_height_offset = row * height_spacing if self._n_added > 1 else 0
|
|
591
|
+
|
|
592
|
+
# Add one spacing unit to dim length of the Viewbox per grid positions the ViewBox spans if span > 1.
|
|
593
|
+
width_increase_spacing = width_spacing * (cspan - 1)
|
|
594
|
+
height_increase_spacing = height_spacing * (rspan - 1)
|
|
595
|
+
|
|
480
596
|
width = np.sum(value_vectorized(
|
|
481
|
-
self._width_grid[row][col:col+cspan]))
|
|
597
|
+
self._width_grid[row][col:col+cspan])) + width_increase_spacing
|
|
482
598
|
height = np.sum(value_vectorized(
|
|
483
|
-
self._height_grid[col][row:row+rspan]))
|
|
599
|
+
self._height_grid[col][row:row+rspan])) + height_increase_spacing
|
|
600
|
+
|
|
484
601
|
if col == 0:
|
|
485
602
|
x = 0
|
|
486
603
|
else:
|
|
487
|
-
x = np.sum(value_vectorized(self._width_grid[row][
|
|
604
|
+
x = np.sum(value_vectorized(self._width_grid[row][:col])) + spacing_width_offset
|
|
488
605
|
|
|
489
606
|
if row == 0:
|
|
490
607
|
y = 0
|
|
491
608
|
else:
|
|
492
|
-
y = np.sum(value_vectorized(self._height_grid[col][
|
|
609
|
+
y = np.sum(value_vectorized(self._height_grid[col][:row])) + spacing_height_offset
|
|
610
|
+
|
|
611
|
+
x += self.padding
|
|
612
|
+
y += self.padding
|
|
493
613
|
|
|
494
614
|
if isinstance(widget, ViewBox):
|
|
495
615
|
widget.rect = Rect(x, y, width, height)
|
vispy/scene/widgets/widget.py
CHANGED
|
@@ -312,6 +312,17 @@ class Widget(Compound):
|
|
|
312
312
|
self._update_line()
|
|
313
313
|
self.update()
|
|
314
314
|
|
|
315
|
+
@property
|
|
316
|
+
def border_width(self):
|
|
317
|
+
"""The width of the border."""
|
|
318
|
+
return self._border_width
|
|
319
|
+
|
|
320
|
+
@border_width.setter
|
|
321
|
+
def border_width(self, b):
|
|
322
|
+
self._border_width = float(b)
|
|
323
|
+
self._update_line()
|
|
324
|
+
self.update()
|
|
325
|
+
|
|
315
326
|
@property
|
|
316
327
|
def bgcolor(self):
|
|
317
328
|
"""The background color of the Widget."""
|
vispy/version.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '0.
|
|
16
|
-
__version_tuple__ = version_tuple = (0,
|
|
20
|
+
__version__ = version = '0.15.2'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 15, 2)
|
|
@@ -291,10 +291,11 @@ class CPUScaledTextureMixin(_ScaledTextureMixin):
|
|
|
291
291
|
|
|
292
292
|
range_min, range_max = self._data_limits
|
|
293
293
|
clim_min, clim_max = self.clim
|
|
294
|
-
|
|
294
|
+
full_range = range_max - range_min
|
|
295
|
+
if clim_min == clim_max or full_range == 0:
|
|
295
296
|
return 0, np.inf
|
|
296
|
-
clim_min = (clim_min - range_min) /
|
|
297
|
-
clim_max = (clim_max - range_min) /
|
|
297
|
+
clim_min = (clim_min - range_min) / full_range
|
|
298
|
+
clim_max = (clim_max - range_min) / full_range
|
|
298
299
|
return clim_min, clim_max
|
|
299
300
|
|
|
300
301
|
@staticmethod
|
|
@@ -312,7 +313,7 @@ class CPUScaledTextureMixin(_ScaledTextureMixin):
|
|
|
312
313
|
def scale_and_set_data(self, data, offset=None, copy=True):
|
|
313
314
|
"""Upload new data to the GPU, scaling if necessary."""
|
|
314
315
|
if self._data_dtype is None:
|
|
315
|
-
|
|
316
|
+
self._data_dtype = data.dtype
|
|
316
317
|
|
|
317
318
|
# ensure dtype is the same as it was before, or funny things happen
|
|
318
319
|
# no copy is performed unless asked for or necessary
|
vispy/visuals/filters/mesh.py
CHANGED
|
@@ -421,6 +421,7 @@ class ShadingFilter(Filter):
|
|
|
421
421
|
ffunc = Function(self._shaders['fragment'])
|
|
422
422
|
|
|
423
423
|
self._normals = VertexBuffer(np.zeros((0, 3), dtype=np.float32))
|
|
424
|
+
self._normals_cache = None
|
|
424
425
|
vfunc['normal'] = self._normals
|
|
425
426
|
|
|
426
427
|
super().__init__(vcode=vfunc, fcode=ffunc)
|
|
@@ -550,7 +551,11 @@ class ShadingFilter(Filter):
|
|
|
550
551
|
)
|
|
551
552
|
|
|
552
553
|
normals = self._visual.mesh_data.get_vertex_normals(indexed='faces')
|
|
553
|
-
self.
|
|
554
|
+
if normals is not self._normals_cache:
|
|
555
|
+
# limit how often we upload new normal arrays
|
|
556
|
+
# gotcha: if normals are changed in place then this won't invalidate this cache
|
|
557
|
+
self._normals_cache = normals
|
|
558
|
+
self._normals.set_data(self._normals_cache, convert=True)
|
|
554
559
|
|
|
555
560
|
def on_mesh_data_updated(self, event):
|
|
556
561
|
self._update_data()
|
vispy/visuals/gridlines.py
CHANGED
|
@@ -4,27 +4,31 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import division
|
|
6
6
|
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
7
9
|
from .image import ImageVisual
|
|
8
10
|
from ..color import Color
|
|
9
11
|
from .shaders import Function
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
_GRID_COLOR = """
|
|
15
|
+
uniform vec4 u_gridlines_bounds;
|
|
16
|
+
uniform float u_border_width;
|
|
17
|
+
|
|
13
18
|
vec4 grid_color(vec2 pos) {
|
|
14
19
|
vec4 px_pos = $map_to_doc(vec4(pos, 0, 1));
|
|
15
20
|
px_pos /= px_pos.w;
|
|
16
21
|
|
|
17
22
|
// Compute vectors representing width, height of pixel in local coords
|
|
18
|
-
float s = 1.;
|
|
19
23
|
vec4 local_pos = $map_doc_to_local(px_pos);
|
|
20
|
-
vec4 dx = $map_doc_to_local(px_pos + vec4(1.0
|
|
21
|
-
vec4 dy = $map_doc_to_local(px_pos + vec4(0, 1.0
|
|
24
|
+
vec4 dx = $map_doc_to_local(px_pos + vec4(1.0, 0, 0, 0));
|
|
25
|
+
vec4 dy = $map_doc_to_local(px_pos + vec4(0, 1.0, 0, 0));
|
|
22
26
|
local_pos /= local_pos.w;
|
|
23
27
|
dx = dx / dx.w - local_pos;
|
|
24
28
|
dy = dy / dy.w - local_pos;
|
|
25
29
|
|
|
26
30
|
// Pixel length along each axis, rounded to the nearest power of 10
|
|
27
|
-
vec2 px =
|
|
31
|
+
vec2 px = vec2(abs(dx.x) + abs(dy.x), abs(dx.y) + abs(dy.y));
|
|
28
32
|
float log10 = log(10.0);
|
|
29
33
|
float sx = pow(10.0, floor(log(px.x) / log10) + 1.) * $scale.x;
|
|
30
34
|
float sy = pow(10.0, floor(log(px.y) / log10) + 1.) * $scale.y;
|
|
@@ -57,6 +61,17 @@ vec4 grid_color(vec2 pos) {
|
|
|
57
61
|
if (alpha == 0.) {
|
|
58
62
|
discard;
|
|
59
63
|
}
|
|
64
|
+
|
|
65
|
+
if (any(lessThan(local_pos.xy + u_border_width / 2, u_gridlines_bounds.xz)) ||
|
|
66
|
+
any(greaterThan(local_pos.xy - u_border_width / 2, u_gridlines_bounds.yw))) {
|
|
67
|
+
discard;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (any(lessThan(local_pos.xy - u_gridlines_bounds.xz, vec2(u_border_width / 2))) ||
|
|
71
|
+
any(lessThan(u_gridlines_bounds.yw - local_pos.xy, vec2(u_border_width / 2)))) {
|
|
72
|
+
alpha = 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
return vec4($color.rgb, $color.a * alpha);
|
|
61
76
|
}
|
|
62
77
|
"""
|
|
@@ -73,9 +88,16 @@ class GridLinesVisual(ImageVisual):
|
|
|
73
88
|
color : Color
|
|
74
89
|
The base color for grid lines. The final color may have its alpha
|
|
75
90
|
channel modified.
|
|
91
|
+
grid_bounds : tuple or None
|
|
92
|
+
The lower and upper bound for each axis beyond which no grid is rendered.
|
|
93
|
+
In the form of (minx, maxx, miny, maxy).
|
|
94
|
+
border_width : float
|
|
95
|
+
Tickness of the border rendered at the bounds of the grid.
|
|
76
96
|
"""
|
|
77
97
|
|
|
78
|
-
def __init__(self, scale=(1, 1), color='w'
|
|
98
|
+
def __init__(self, scale=(1, 1), color='w',
|
|
99
|
+
grid_bounds=None,
|
|
100
|
+
border_width=2):
|
|
79
101
|
# todo: PlaneVisual should support subdivide/impostor methods from
|
|
80
102
|
# image and gridlines should inherit from plane instead.
|
|
81
103
|
self._grid_color_fn = Function(_GRID_COLOR)
|
|
@@ -86,6 +108,40 @@ class GridLinesVisual(ImageVisual):
|
|
|
86
108
|
self.shared_program.frag['get_data'] = self._grid_color_fn
|
|
87
109
|
cfun = Function('vec4 null(vec4 x) { return x; }')
|
|
88
110
|
self.shared_program.frag['color_transform'] = cfun
|
|
111
|
+
self.unfreeze()
|
|
112
|
+
self.grid_bounds = grid_bounds
|
|
113
|
+
self.border_width = border_width
|
|
114
|
+
self.freeze()
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def grid_bounds(self):
|
|
118
|
+
return self._grid_bounds
|
|
119
|
+
|
|
120
|
+
@grid_bounds.setter
|
|
121
|
+
def grid_bounds(self, value):
|
|
122
|
+
if value is None:
|
|
123
|
+
value = (None,) * 4
|
|
124
|
+
grid_bounds = []
|
|
125
|
+
for i, v in enumerate(value):
|
|
126
|
+
if v is None:
|
|
127
|
+
if i % 2:
|
|
128
|
+
v = -np.inf
|
|
129
|
+
else:
|
|
130
|
+
v = np.inf
|
|
131
|
+
grid_bounds.append(v)
|
|
132
|
+
self.shared_program['u_gridlines_bounds'] = value
|
|
133
|
+
self._grid_bounds = grid_bounds
|
|
134
|
+
self.update()
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def border_width(self):
|
|
138
|
+
return self._border_width
|
|
139
|
+
|
|
140
|
+
@border_width.setter
|
|
141
|
+
def border_width(self, value):
|
|
142
|
+
self.shared_program['u_border_width'] = value
|
|
143
|
+
self._border_width = value
|
|
144
|
+
self.update()
|
|
89
145
|
|
|
90
146
|
@property
|
|
91
147
|
def size(self):
|
vispy/visuals/image.py
CHANGED
|
@@ -91,11 +91,9 @@ _TEXTURE_LOOKUP = """
|
|
|
91
91
|
|
|
92
92
|
_APPLY_CLIM_FLOAT = """
|
|
93
93
|
float apply_clim(float data) {
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
discard;
|
|
98
|
-
}
|
|
94
|
+
// pass through NaN values to get handled by the colormap
|
|
95
|
+
if (!(data <= 0.0 || 0.0 <= data)) return data;
|
|
96
|
+
|
|
99
97
|
data = clamp(data, min($clim.x, $clim.y), max($clim.x, $clim.y));
|
|
100
98
|
data = (data - $clim.x) / ($clim.y - $clim.x);
|
|
101
99
|
return data;
|
|
@@ -103,7 +101,7 @@ _APPLY_CLIM_FLOAT = """
|
|
|
103
101
|
|
|
104
102
|
_APPLY_CLIM = """
|
|
105
103
|
vec4 apply_clim(vec4 color) {
|
|
106
|
-
// Handle NaN values
|
|
104
|
+
// Handle NaN values (clamp them to the minimum value)
|
|
107
105
|
// http://stackoverflow.com/questions/11810158/how-to-deal-with-nan-or-inf-in-opengl-es-2-0-shaders
|
|
108
106
|
color.r = !(color.r <= 0.0 || 0.0 <= color.r) ? min($clim.x, $clim.y) : color.r;
|
|
109
107
|
color.g = !(color.g <= 0.0 || 0.0 <= color.g) ? min($clim.x, $clim.y) : color.g;
|
|
@@ -117,6 +115,9 @@ _APPLY_CLIM = """
|
|
|
117
115
|
|
|
118
116
|
_APPLY_GAMMA_FLOAT = """
|
|
119
117
|
float apply_gamma(float data) {
|
|
118
|
+
// pass through NaN values to get handled by the colormap
|
|
119
|
+
if (!(data <= 0.0 || 0.0 <= data)) return data;
|
|
120
|
+
|
|
120
121
|
return pow(data, $gamma);
|
|
121
122
|
}"""
|
|
122
123
|
|
|
@@ -129,7 +130,7 @@ _APPLY_GAMMA = """
|
|
|
129
130
|
|
|
130
131
|
_NULL_COLOR_TRANSFORM = 'vec4 pass(vec4 color) { return color; }'
|
|
131
132
|
|
|
132
|
-
_C2L_RED = 'float
|
|
133
|
+
_C2L_RED = 'float color_to_luminance(vec4 color) { return color.r; }'
|
|
133
134
|
|
|
134
135
|
_CUSTOM_FILTER = """
|
|
135
136
|
vec4 texture_lookup(vec2 texcoord) {
|
|
@@ -456,6 +457,17 @@ class ImageVisual(Visual):
|
|
|
456
457
|
self.shared_program.frag['color_transform'][2]['gamma'] = self._gamma
|
|
457
458
|
self.update()
|
|
458
459
|
|
|
460
|
+
@property
|
|
461
|
+
def bad_color(self):
|
|
462
|
+
"""Color used to render NaN values."""
|
|
463
|
+
return self._cmap.get_bad_color()
|
|
464
|
+
|
|
465
|
+
@bad_color.setter
|
|
466
|
+
def bad_color(self, color):
|
|
467
|
+
self._cmap.set_bad_color(color)
|
|
468
|
+
self._need_colortransform_update = True
|
|
469
|
+
self.update()
|
|
470
|
+
|
|
459
471
|
@property
|
|
460
472
|
def method(self):
|
|
461
473
|
"""Get rendering method name."""
|
vispy/visuals/surface_plot.py
CHANGED
|
@@ -126,7 +126,7 @@ class SurfacePlotVisual(MeshVisual):
|
|
|
126
126
|
# convert (width, height, 4) to (num_verts, 4)
|
|
127
127
|
vert_shape = self.__vertices.shape
|
|
128
128
|
num_vertices = vert_shape[0] * vert_shape[1]
|
|
129
|
-
colors = colors.reshape(num_vertices,
|
|
129
|
+
colors = colors.reshape(num_vertices, colors.shape[-1])
|
|
130
130
|
return colors
|
|
131
131
|
|
|
132
132
|
def set_data(self, x=None, y=None, z=None, colors=None):
|