Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 13 additions & 15 deletions src/plopp/backends/pythreejs/scatter3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)

import uuid
from typing import Literal
from typing import Literal, cast

import numpy as np
import pythreejs as p3
Expand Down Expand Up @@ -75,8 +75,12 @@ def __init__(
self._x = x
self._y = y
self._z = z
self._color = color
self._artist_number = artist_number
self._color = None
if colormapper is None:
self._color = np.array(
to_rgb(f'C{artist_number}' if color is None else color),
dtype='float32',
)

# TODO: remove pixel_size in the next release
self._size = size if pixel_size is None else pixel_size
Expand Down Expand Up @@ -124,18 +128,8 @@ def _make_positions(self) -> np.ndarray:
def _make_colors(self) -> np.ndarray:
if self._colormapper is not None:
return self._colormapper.rgba(self._data)[..., :3].astype('float32')
else:
return np.broadcast_to(
np.array(
to_rgb(
f'C{self._artist_number}'
if self._color is None
else self._color
),
dtype='float32',
),
(self._data.coords[self._x].shape[0], 3),
)
color = cast(np.ndarray, self._color)
return np.broadcast_to(color, (self._data.coords[self._x].shape[0], 3))

def _make_geometry(
self, positions: np.ndarray, colors: np.ndarray
Expand Down Expand Up @@ -212,6 +206,10 @@ def color(self) -> np.ndarray:
def color(self, val: np.ndarray):
self.geometry.attributes['color'].array = val

def _copy_color_from(self, other: 'Scatter3d') -> None:
self._color = other.color[0].copy()
self.color = self._make_colors()

@property
def geometry(self) -> p3.BufferGeometry:
"""
Expand Down
5 changes: 5 additions & 0 deletions src/plopp/widgets/clip3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,11 @@ def update_controls(self):
self._nodes.clear()

self.update_state()
if self._view.colormapper is None:
for source_id, node in self._nodes.items():
self._view.artists[node.id]._copy_color_from(
self._view.artists[source_id]
)

def _set_opacity(self, change: dict[str, Any]):
"""
Expand Down
12 changes: 12 additions & 0 deletions tests/backends/pythreejs/pythreejs_scatter3d_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import numpy as np
import pytest
import scipp as sc
from matplotlib import cycler, rc_context

from plopp.backends.pythreejs.canvas import Canvas
from plopp.backends.pythreejs.scatter3d import Scatter3d
Expand All @@ -30,6 +31,17 @@ def test_update():
assert sc.identical(scat._data, da * 2.5)


def test_update_with_different_number_of_points_preserves_default_color():
with rc_context({'axes.prop_cycle': cycler(color=['#102030'])}):
scat = Scatter3d(canvas=Canvas(), data=scatter(20), x='x', y='y', z='z')
color = scat.color[0].copy()

with rc_context({'axes.prop_cycle': cycler(color=['#708090'])}):
scat.update(scatter(30))

np.testing.assert_array_equal(scat.color, np.broadcast_to(color, (30, 3)))


def test_bounding_box():
da = scatter()
pix = 0.5
Expand Down
34 changes: 34 additions & 0 deletions tests/widgets/clip3d_test.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
import numpy as np
import pytest
import scipp as sc
from matplotlib import cycler, rc_context

from plopp import Node
from plopp.data.testing import data_array, scatter
from plopp.graphics import scatter3dfigure
from plopp.widgets import ClippingManager


def test_cut_colors_match_original_artists():
a = scatter(seed=1)
b = scatter(seed=2)
b.coords['x'] += sc.scalar(60, unit='m')
nodes = [Node(a), Node(b)]
with rc_context({'axes.prop_cycle': cycler(color=['#102030', '#405060'])}):
fig = scatter3dfigure(*nodes, x='x', y='y', z='z', cbar=False)
clip = ClippingManager(fig)

with rc_context({'axes.prop_cycle': cycler(color=['#708090', '#a0b0c0'])}):
clip.add_y_cut.click()

for source in nodes:
cut = clip._nodes[source.id]
assert np.array_equal(
fig.artists[cut.id].color[0], fig.artists[source.id].color[0]
)

npoints = [fig.artists[node.id].data.shape[0] for node in clip._nodes.values()]
ycut = clip.cuts[-1]
ycut.slider.value = [ycut.slider.min, ycut.slider.value[1]]
clip.update_state()
assert [
fig.artists[node.id].data.shape[0] for node in clip._nodes.values()
] != npoints
for source in nodes:
cut = clip._nodes[source.id]
assert np.array_equal(
fig.artists[cut.id].color[0], fig.artists[source.id].color[0]
)


@pytest.mark.parametrize('multiple_nodes', [False, True])
def test_add_remove_cuts(multiple_nodes):
a = scatter()
Expand Down
Loading