Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
341da3c
plot control in Experiment and Analysis
rozyczko Jan 26, 2026
0cedef6
added scale/bg lines to exp and analysis plots
rozyczko Jan 26, 2026
a8c88c6
Y axis label fix
rozyczko Jan 26, 2026
b981817
fixed profile lines.
rozyczko Jan 27, 2026
abd0201
code review fixes
rozyczko Jan 27, 2026
2b6c4d6
proper orso sample conversion into layers
rozyczko Feb 5, 2026
1c112b2
added checkbox for loaded orso overwrite. fixed axes reset on file load
rozyczko Feb 8, 2026
9011957
Merge branch 'improved_plots' of https://github.com/EasyScience/EasyR…
rozyczko Feb 9, 2026
77824a6
show ORSO name when available
rozyczko Feb 11, 2026
82eb209
Merge branch 'improved_plots' of https://github.com/EasyScience/EasyR…
rozyczko Feb 12, 2026
fcabcab
model prefix based on its name
rozyczko Feb 12, 2026
8ee83bd
use orso name when available
rozyczko Feb 12, 2026
5f2639d
enhance safety of ORSO sample file load
rozyczko Feb 13, 2026
471f5e1
changes after CR
rozyczko Feb 14, 2026
6e1d793
Merge branch 'improved_plots' of https://github.com/easyscience/EasyR…
rozyczko Feb 14, 2026
d3e166a
re-enable plot control widgets on Experiment and Analysis tabs
rozyczko Feb 17, 2026
250ff4a
properly reset axes in the sample charts on any param change
rozyczko Feb 23, 2026
dc5c6d7
reset axes for analysis SLD/Refl plots on parameter change.
rozyczko Feb 23, 2026
6a653f1
update the status line with correct values
rozyczko Feb 23, 2026
aa9128e
sample profile on Analysis should include resolution
rozyczko Feb 24, 2026
4c38807
refactor SldView so Sample and Analysis get the same display
rozyczko Feb 24, 2026
b6e0e6e
logging for minimizer change
rozyczko Feb 24, 2026
6ba05e9
formatting for the error column
rozyczko Feb 24, 2026
36bc64b
improved constraints check
rozyczko Feb 27, 2026
453ee5d
added chi2 display in the status bar
rozyczko Feb 27, 2026
5af1497
PR review #2
rozyczko Feb 27, 2026
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
81 changes: 81 additions & 0 deletions EasyReflectometryApp/Backends/Mock/Plotting.qml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,22 @@ QtObject {

property int modelCount: 1

// Plot mode properties
property bool plotRQ4: false
property string yMainAxisTitle: 'R(q)'
property bool xAxisLog: false
property string xAxisType: 'linear'
property bool sldXDataReversed: false
property bool scaleShown: false
property bool bkgShown: false

// Signals for plot mode changes
signal plotModeChanged()
signal axisTypeChanged()
signal sldAxisReversedChanged()
signal referenceLineVisibilityChanged()
signal samplePageDataChanged()
signal samplePageResetAxes()

function setQtChartsSerieRef(value1, value2, value3) {
console.debug(`setQtChartsSerieRef ${value1}, ${value2}, ${value3}`)
Expand Down Expand Up @@ -52,4 +67,70 @@ QtObject {
return '#0000FF'
}

// Plot mode toggle functions
function togglePlotRQ4() {
plotRQ4 = !plotRQ4
yMainAxisTitle = plotRQ4 ? 'R(q)×q⁴' : 'R(q)'
plotModeChanged()
}

function toggleXAxisType() {
xAxisLog = !xAxisLog
xAxisType = xAxisLog ? 'log' : 'linear'
axisTypeChanged()
}

function reverseSldXData() {
sldXDataReversed = !sldXDataReversed
sldAxisReversedChanged()
}

function flipScaleShown() {
scaleShown = !scaleShown
referenceLineVisibilityChanged()
}

function flipBkgShown() {
bkgShown = !bkgShown
referenceLineVisibilityChanged()
}

// Reference line data accessors (mock implementation)
function getBackgroundData() {
if (!bkgShown) return []
// Return mock horizontal line at background level
return [
{ 'x': 0.01, 'y': -7.0 },
{ 'x': 0.30, 'y': -7.0 }
]
}

function getScaleData() {
if (!scaleShown) return []
// Return mock horizontal line at scale level (log10(1.0) = 0)
return [
{ 'x': 0.01, 'y': 0.0 },
{ 'x': 0.30, 'y': 0.0 }
]
}

// Analysis-specific reference line data accessors (use sample/calculated x-range)
function getBackgroundDataForAnalysis() {
if (!bkgShown) return []
// Return mock horizontal line at background level using sample x-range
return [
{ 'x': sampleMinX, 'y': -7.0 },
{ 'x': sampleMaxX, 'y': -7.0 }
]
}

function getScaleDataForAnalysis() {
if (!scaleShown) return []
// Return mock horizontal line at scale level using sample x-range
return [
{ 'x': sampleMinX, 'y': 0.0 },
{ 'x': sampleMaxX, 'y': 0.0 }
]
}

}
39 changes: 28 additions & 11 deletions EasyReflectometryApp/Backends/Py/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .logic.calculators import Calculators as CalculatorsLogic
from .logic.experiments import Experiments as ExperimentLogic
from .logic.fitting import Fitting as FittingLogic
from .logic.helpers import get_original_name
from .logic.minimizers import Minimizers as MinimizersLogic
from .logic.parameters import Parameters as ParametersLogic
from .workers import FitterWorker
Expand Down Expand Up @@ -59,6 +60,25 @@ def _initialize_selected_experiments(self) -> None:
else:
self._selected_experiment_indices = []

def _ordered_experiments(self) -> list:
"""Return experiments as an ordered list of experiment objects.

Handles mapping-like storage without assuming contiguous integer keys.
"""
experiments = self._experiments_logic._project_lib._experiments
if not experiments:
return []

if hasattr(experiments, 'items'):
items = list(experiments.items())
try:
items.sort(key=lambda item: item[0])
except TypeError:
pass
return [experiment for _, experiment in items]

return list(experiments)

########################
## Fitting
@Property(str, notify=fittingChanged)
Expand Down Expand Up @@ -278,7 +298,7 @@ def setExperimentNameAtIndex(self, index: int, new_name: str) -> None:
def modelIndexForExperiment(self) -> int:
# return the model index for the current experiment
models = self._experiments_logic._project_lib._models
experiments = self._experiments_logic._project_lib._experiments
experiments = self._ordered_experiments()
index = self.experimentCurrentIndex
current_experiment = experiments[index] if 0 <= index < len(experiments) else None
if current_experiment is not None:
Expand All @@ -290,18 +310,19 @@ def modelIndexForExperiment(self) -> int:
def modelNamesForExperiment(self) -> list:
# return a list of model names for each experiment
mapped_models = []
experiments = self._experiments_logic._project_lib._experiments
for ind in experiments:
mapped_models.append(experiments[ind].model.name)
experiments = self._ordered_experiments()
for experiment in experiments:
name = get_original_name(experiment.model)
mapped_models.append(name)
return mapped_models

@Property('QVariantList', notify=experimentsChanged)
def modelColorsForExperiment(self) -> list:
# return a list of model colors for each experiment
mapped_models = []
experiments = self._experiments_logic._project_lib._experiments
for ind in experiments:
mapped_models.append(experiments[ind].model.color)
experiments = self._ordered_experiments()
for experiment in experiments:
mapped_models.append(experiment.model.color)
return mapped_models

@Slot(int)
Expand Down Expand Up @@ -501,14 +522,10 @@ def enabledParameters(self) -> list[dict[str]]:
if self._chached_enabled_parameters is not None:
return self._chached_enabled_parameters
enabled_parameters = []
# import time
# t0 = time.time()
for parameter in self._parameters_logic.parameters:
if not parameter['enabled']:
continue
enabled_parameters.append(parameter)
# t1 = time.time()
# print(f"Enabled parameters computation time: {t1 - t0:.4f} seconds")
self._chached_enabled_parameters = enabled_parameters
return enabled_parameters

Expand Down
74 changes: 59 additions & 15 deletions EasyReflectometryApp/Backends/Py/logic/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,41 @@ class Experiments:
def __init__(self, project_lib: ProjectLib):
self._project_lib = project_lib

def _ordered_experiment_items(self) -> list[tuple[object, object]]:
"""Return experiments as ordered ``(key, experiment)`` pairs.

Supports mapping-like storage without assuming contiguous integer keys.
"""
experiments = self._project_lib._experiments
if not experiments:
return []

if hasattr(experiments, 'items'):
items = list(experiments.items())
try:
items.sort(key=lambda item: item[0])
except TypeError:
pass
return items

return list(enumerate(experiments))

def _experiment_at_index(self, index: int):
items = self._ordered_experiment_items()
if 0 <= index < len(items):
return items[index][1]
return None

def _experiment_key_at_index(self, index: int):
items = self._ordered_experiment_items()
if 0 <= index < len(items):
return items[index][0]
return None

def available(self) -> list[str]:
experiments_name = []
try:
# get .name from self._project_lib._experiments and append to experiments_name
for ind in self._project_lib._experiments.keys():
exp = self._project_lib._experiments[ind]
for _, exp in self._ordered_experiment_items():
experiments_name.append(exp.name)
except IndexError:
pass
Expand All @@ -26,19 +55,19 @@ def set_current_index(self, new_value: int) -> None:
return False

def set_experiment_name(self, new_name: str) -> None:
exp = self._project_lib._experiments.get(self._project_lib._current_experiment_index)
exp = self._experiment_at_index(self._project_lib._current_experiment_index)
if exp:
exp.name = new_name

def set_experiment_name_at_index(self, index: int, new_name: str) -> None:
exp = self._project_lib._experiments.get(index)
exp = self._experiment_at_index(index)
if exp:
exp.name = new_name

def model_on_experiment(self, experiment_index: int = -1) -> dict:
if experiment_index == -1:
experiment_index = self._project_lib._current_experiment_index
exp = self._project_lib._experiments.get(experiment_index)
exp = self._experiment_at_index(experiment_index)
if exp:
return exp.model
return {}
Expand All @@ -50,7 +79,7 @@ def model_index_on_experiment(self) -> int:
return -1

def set_model_on_experiment(self, new_value: int) -> None:
exp = self._project_lib._experiments.get(self._project_lib._current_experiment_index)
exp = self._experiment_at_index(self._project_lib._current_experiment_index)
models = self._project_lib._models
if exp and models:
try:
Expand All @@ -66,12 +95,27 @@ def remove_experiment(self, index: int) -> None:
"""
Remove the experiment at the given index.
"""
if 0 <= index < len(self.available()):
del self._project_lib._experiments[index]
# readjust the dictionary keys for continuity
temp_experiments = self._project_lib._experiments.copy()
self._project_lib._experiments = {i: exp for i, exp in enumerate(temp_experiments.values())}
if self._project_lib._current_experiment_index >= index:
self._project_lib._current_experiment_index = max(0, self._project_lib._current_experiment_index - 1)
else:
total = len(self.available())
if not (0 <= index < total):
print(f'Experiment index {index} is out of range.')
return

experiments = self._project_lib._experiments
exp_key = self._experiment_key_at_index(index)
if exp_key is None:
print(f'Experiment index {index} is out of range.')
return

if hasattr(experiments, 'items'):
del experiments[exp_key]
else:
experiments.pop(index)

current = self._project_lib._current_experiment_index
new_total = max(0, total - 1)
if new_total == 0:
self._project_lib._current_experiment_index = 0
elif current > index:
self._project_lib._current_experiment_index = current - 1
elif current >= new_total:
self._project_lib._current_experiment_index = new_total - 1
Loading