Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
50 changes: 28 additions & 22 deletions flow360/plugins/report/report_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
is_flow360_unit,
unyt_quantity,
)
from flow360.exceptions import Flow360ValidationError
from flow360.log import log
from flow360.plugins.report.report_context import ReportContext
from flow360.plugins.report.utils import (
Expand Down Expand Up @@ -1417,32 +1418,37 @@ class Chart2D(BaseChart2D):

@pd.model_validator(mode="after")
def _handle_deprecated_include_exclude(self):
if (self.include is not None) or (self.exclude is not None):
if not isinstance(self.x, DataItem):
self.x = DataItem(data=self.x, include=self.include, exclude=self.exclude)
else:
self.x.include = self.include
self.x.exclude = self.exclude
include = self.include
exclude = self.exclude
if (include is not None) or (exclude is not None):
self.include = None
self.exclude = None
self.x = self._overload_include_exclude(include, exclude, self.x)
if isinstance(self.y, List):
new_y = []
for y in self.y:
if not isinstance(self.y, DataItem):
new_y.append(
DataItem(data=self.y, include=self.include, exclude=self.exclude)
)
else:
y.include = self.include
y.exclude = self.exclude
new_y.append(y)
self.y = new_y
new_value = []
for data_variable in self.y:
new_value.append(
self._overload_include_exclude(include, exclude, data_variable)
)
self.y = new_value
else:
if not isinstance(self.y, DataItem):
self.y = DataItem(data=self.y, include=self.include, exclude=self.exclude)
else:
self.y.include = self.include
self.y.exclude = self.exclude
self.y = self._overload_include_exclude(include, exclude, self.y)
return self

@classmethod
def _overload_include_exclude(cls, include, exclude, data_variable):
if isinstance(data_variable, Delta):
raise Flow360ValidationError(
"Delta can not be used with exclude/include options. "
+ "Specify the Delta data using DataItem."
)
if not isinstance(data_variable, DataItem):
data_variable = DataItem(data=data_variable, include=include, exclude=exclude)
else:
data_variable.include = include
data_variable.exclude = exclude
return data_variable

def get_requirements(self):
"""
Returns requirements for this item.
Expand Down
68 changes: 35 additions & 33 deletions flow360/plugins/report/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ def _validate_operations(cls, values):
def _check_for_averages(self):
components = split_path(self.data)
if "averages" in components:
self.operations.insert(0, Average(fraction=0.1))
self.operations.append(Average(fraction=0.1))
components.remove("averages")
self.data = "/".join(components)
return self
Expand Down Expand Up @@ -830,7 +830,9 @@ def __str__(self):

# pylint: disable=too-few-public-methods
class Tabulary(Tabular):
"""The `tabulary` package works better than the existing pylatex implementations so this includes it in pylatex"""
"""
The `tabulary` package works better than the existing pylatex implementations so this includes it in pylatex
"""

packages = [Package("tabulary")]

Expand All @@ -854,41 +856,41 @@ def generate_colorbar_from_image(
is_log_scale=False,
): # pylint: disable=too-many-arguments,too-many-locals
"""
Generate a color bar image from an existing PNG file with ticks and labels.
Generate a color bar image from an existing PNG file with ticks and labels.

This function reads a colormap from a provided PNG file (a horizontal strip of
colors), and overlays ticks and labels according to the specified value limits
and scale type (linear or logarithmic).
'
For a linear scale, matplotlib automatically chooses the number and format of
ticks. For a log scale, a twin axis is used for proper tick placement without
distorting the color distribution.
This function reads a colormap from a provided PNG file (a horizontal strip of
colors), and overlays ticks and labels according to the specified value limits
and scale type (linear or logarithmic).

Parameters
----------
image_filename : str
Path to the colormap PNG file (horizontal strip).
limits : tuple of float
A tuple (min_value, max_value) specifying the data range.
If `is_log_scale` is True, `max_value` must be greater than 0.
field_name : str
The field name for the label.
output_filename : str
The output filename for the resulting image with ticks.
height_px : int
The height in pixels for the color bar image.
is_log_scale : bool
If True, use a log scale axis for ticks and minor ticks.
For a linear scale, matplotlib automatically chooses the number and format of
ticks. For a log scale, a twin axis is used for proper tick placement without
distorting the color distribution.

Returns
-------
None
The resulting image is saved to `output_filename`.
Parameters
----------
image_filename : str
Path to the colormap PNG file (horizontal strip).
limits : tuple of float
A tuple (min_value, max_value) specifying the data range.
If `is_log_scale` is True, `max_value` must be greater than 0.
field_name : str
The field name for the label.
output_filename : str
The output filename for the resulting image with ticks.
height_px : int
The height in pixels for the color bar image.
is_log_scale : bool
If True, use a log scale axis for ticks and minor ticks.

Returns
-------
None
The resulting image is saved to `output_filename`.

Notes
-----
- On a log scale, the main colorbar axis remains linear to avoid deforming
the color distribution. A twin axis is used solely for log-scale labeling.
Notes
-----
- On a log scale, the main colorbar axis remains linear to avoid deforming
the color distribution. A twin axis is used solely for log-scale labeling.
"""

img = Image.open(image_filename)
Expand Down
36 changes: 36 additions & 0 deletions tests/report/test_report_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from flow360.component.resource_base import local_metadata_builder
from flow360.component.utils import LocalResourceCache
from flow360.component.volume_mesh import VolumeMeshMetaV2, VolumeMeshV2
from flow360.exceptions import Flow360ValidationError
from flow360.plugins.report.report import ReportTemplate
from flow360.plugins.report.report_context import ReportContext
from flow360.plugins.report.report_doc import ReportDoc
Expand Down Expand Up @@ -1471,3 +1472,38 @@ def test_include_exclude(here, cases):

with pytest.raises(AttributeError):
plot_model = chart.get_data(cases=cases[:2], context=context)

with pytest.raises(Flow360ValidationError):
chart = Chart2D(
x=Delta(data="surface_forces/averages/totalCD"),
y="surface_forces/averages/totalCL",
section_title="CL/CD",
fig_name="clcd",
include=["blk-1/BODY"],
)


def test_in_path_averages(here, cases):
dataitem = DataItem(
data="total_forces/averages/CL",
operations=[Expression(expr="CL*beta")],
variables=[Variable(name="beta", data="params/operating_condition/beta")],
)

assert dataitem.operations[0] == Expression(expr="CL*beta")
assert dataitem.operations[1] == Average(fraction=0.1)

cl_beta = dataitem.calculate(case=cases[1], cases=cases)

load_data = pd.read_csv(
os.path.join(here, "..", "data", cases[1].id, "results", "total_forces_v2.csv"),
skipinitialspace=True,
)
to_avg = round(len(load_data) * 0.1)

cl_beta_expected = np.average(load_data["CL"].iloc[-to_avg:]) * 2

assert dataitem.operations[2] == Average(fraction=0.1)
assert dataitem.operations[1] == Expression(expr="CL*beta")

assert np.allclose(cl_beta, cl_beta_expected)