Skip to content

Settings Popups

Components

SettingsTitle(parent, text, size=None)

Bases: QLabel

A QLabel with bold formatting for use as section titles in settings dialogs.

This widget provides a consistent title appearance across all settings dialogs with optional custom font size.

Parameters:

Name Type Description Default
parent QWidget

The parent widget

required
text str

The title text to display

required
size int

Custom font size in pixels

None
Source code in trace/widgets/settings_components.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def __init__(self, parent: QWidget, text: str, size: int = None):
    """Initialize the settings title.

    Parameters
    ----------
    parent : QWidget
        The parent widget
    text : str
        The title text to display
    size : int, optional
        Custom font size in pixels
    """
    super().__init__(text=text, parent=parent)
    bold_font = QFont()
    bold_font.setBold(True)
    if size is not None:
        bold_font.setPixelSize(size)
    self.setFont(bold_font)

SettingsRowItem(label_parent, label_txt, widget)

Bases: QHBoxLayout

A horizontal layout for settings rows with label and widget.

This layout provides a consistent structure for settings rows with a label on the left, a spacer in the middle, and a widget on the right.

Parameters:

Name Type Description Default
label_parent QWidget

The parent widget for the label

required
label_txt str

The text for the label

required
widget QWidget

The widget to place on the right side

required
Source code in trace/widgets/settings_components.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def __init__(self, label_parent: QWidget, label_txt: str, widget: QWidget):
    """Initialize the settings row item.

    Parameters
    ----------
    label_parent : QWidget
        The parent widget for the label
    label_txt : str
        The text for the label
    widget : QWidget
        The widget to place on the right side
    """
    super().__init__()
    label = QLabel(label_txt, label_parent)
    self.addWidget(label)

    spacer = QSpacerItem(40, 12, QSizePolicy.Expanding, QSizePolicy.Minimum)
    self.addSpacerItem(spacer)

    self.addWidget(widget)

ComboBoxWrapper(parent, data_source, init_value=None)

Bases: QComboBox

A QComboBox wrapper that provides data mapping and custom signal emission.

This widget extends QComboBox to support data source mapping and emits the mapped value rather than the display text when selection changes.

Parameters:

Name Type Description Default
parent QWidget

The parent widget

required
data_source list, tuple, or dict

Data source for the combo box items. If list/tuple, creates a mapping to itself. If dict, uses keys as display text and values as emitted values.

required
init_value int, str, or None

Initial value to select

None
Source code in trace/widgets/settings_components.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def __init__(self, parent: QWidget, data_source: list | tuple | dict, init_value: int | str | None = None):
    """Initialize the combo box wrapper.

    Parameters
    ----------
    parent : QWidget
        The parent widget
    data_source : list, tuple, or dict
        Data source for the combo box items. If list/tuple, creates a
        mapping to itself. If dict, uses keys as display text and values
        as emitted values.
    init_value : int, str, or None, optional
        Initial value to select
    """
    super().__init__(parent)
    if isinstance(data_source, (list, tuple)):
        data_source = {v: v for v in data_source}
    self.data_source = data_source
    self.addItems(self.data_source.keys())

    if init_value is not None:
        if str(init_value) in self.data_source:
            self.setCurrentText(str(init_value))
        else:
            value_ind = list(self.data_source.values()).index(init_value)
            self.setCurrentIndex(value_ind)

    self.currentTextChanged.connect(self.clean_text_changed)

clean_text_changed(inc_text)

Handle text changes and emit the mapped value.

Parameters:

Name Type Description Default
inc_text str

The incoming text from the combo box

required
Source code in trace/widgets/settings_components.py
107
108
109
110
111
112
113
114
115
116
117
118
119
def clean_text_changed(self, inc_text: str) -> None:
    """Handle text changes and emit the mapped value.

    Parameters
    ----------
    inc_text : str
        The incoming text from the combo box
    """
    outgoing_text = inc_text
    if inc_text in self.data_source:
        outgoing_text = self.data_source[inc_text]

    self.text_changed.emit(outgoing_text)

Plot Settings

PlotSettingsModal(parent, plot)

Bases: QWidget

Modal widget for configuring plot settings including title, legend, mouse mode, autoscroll interval, time range, crosshair, appearance, and gridlines.

This widget provides a comprehensive interface for customizing the appearance and behavior of the PyDMArchiverTimePlot.

Parameters:

Name Type Description Default
parent QWidget

The parent widget

required
plot PyDMArchiverTimePlot

The plot widget to configure

required
Source code in trace/widgets/plot_settings.py
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
def __init__(self, parent: QWidget, plot: PyDMArchiverTimePlot):
    """Initialize the plot settings modal.

    Parameters
    ----------
    parent : QWidget
        The parent widget
    plot : PyDMArchiverTimePlot
        The plot widget to configure
    """
    super().__init__(parent)
    self.setWindowFlag(Qt.Popup)

    self.plot = plot
    main_layout = QVBoxLayout()
    self.setLayout(main_layout)

    title_label = SettingsTitle(self, "Plot Settings", size=14)
    main_layout.addWidget(title_label)

    self.plot_title_line_edit = QLineEdit()
    self.plot_title_line_edit.setPlaceholderText("Enter Title")
    self.plot_title_line_edit.textChanged.connect(self.plot.setPlotTitle)
    self.plot.plotItem.titleLabel.anchor((0.5, 0), (0.5, 0))  # Center title
    plot_title_row = SettingsRowItem(self, "Title", self.plot_title_line_edit)
    main_layout.addLayout(plot_title_row)

    self.legend_checkbox = QCheckBox(self)
    self.legend_checkbox.stateChanged.connect(self.set_show_legend)
    self.legend_checkbox.setChecked(True)  # legend on by default
    legend_row = SettingsRowItem(self, "Show Legend", self.legend_checkbox)
    main_layout.addLayout(legend_row)

    self.mouse_mode_combo = QComboBox(self)
    self.mouse_mode_combo.addItems(["Rect", "Pan"])
    self.mouse_mode_combo.currentTextChanged.connect(self.plot.plotItem.changeMouseMode)
    mouse_mode_row = SettingsRowItem(self, "Mouse Mode", self.mouse_mode_combo)
    main_layout.addLayout(mouse_mode_row)

    self.as_interval_spinbox = QSpinBox(self)
    self.as_interval_spinbox.setValue(5)
    self.as_interval_spinbox.setMinimum(1)
    self.as_interval_spinbox.setMaximum(60)
    self.as_interval_spinbox.setSuffix(" s")
    self.as_interval_spinbox.valueChanged.connect(self.auto_scroll_interval_change.emit)
    as_interval_row = SettingsRowItem(self, "Autoscroll Interval", self.as_interval_spinbox)
    main_layout.addLayout(as_interval_row)

    self.start_datetime = QDateTimeEdit(self)
    self.start_datetime.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
    self.start_datetime.setCalendarPopup(True)
    self.start_datetime.dateTimeChanged.connect(lambda qdt: self.set_time_axis_range((qdt, None)))
    start_dt_row = SettingsRowItem(self, "Start Time", self.start_datetime)
    main_layout.addLayout(start_dt_row)

    self.end_datetime = QDateTimeEdit(self)
    self.end_datetime.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
    self.end_datetime.setCalendarPopup(True)
    self.end_datetime.dateTimeChanged.connect(lambda qdt: self.set_time_axis_range((None, qdt)))
    end_dt_row = SettingsRowItem(self, "End Time", self.end_datetime)
    main_layout.addLayout(end_dt_row)

    self.crosshair_checkbox = QCheckBox(self)
    self.crosshair_checkbox.stateChanged.connect(self.set_crosshair)
    crosshair_row = SettingsRowItem(self, "Show Crosshair", self.crosshair_checkbox)
    main_layout.addLayout(crosshair_row)

    appearance_label = SettingsTitle(self, "Appearance")
    main_layout.addWidget(appearance_label)

    self.background_button = ColorButton(parent=self, color="white")
    self.background_button.color_changed.connect(self.plot.setBackgroundColor)
    background_row = SettingsRowItem(self, "  Background Color", self.background_button)
    main_layout.addLayout(background_row)

    self.palette_modal = CurveColorPaletteModal(self)
    self.curve_palette_button = QPushButton("Select")
    self.curve_palette_button.clicked.connect(self.palette_modal.show)
    self.palette_modal.sig_palette_changed.connect(self.sig_curve_palette_changed.emit)
    palette_row = SettingsRowItem(self, "  Curve Palette", self.curve_palette_button)
    main_layout.addLayout(palette_row)

    axis_tick_font_size_spinbox = QSpinBox(self)
    axis_tick_font_size_spinbox.setValue(12)
    axis_tick_font_size_spinbox.setSuffix(" pt")
    axis_tick_font_size_spinbox.valueChanged.connect(self.set_axis_tick_font_size)
    axis_tick_font_size_row = SettingsRowItem(self, "  Axis Tick Font Size", axis_tick_font_size_spinbox)
    main_layout.addLayout(axis_tick_font_size_row)

    self.x_grid_checkbox = QCheckBox(self)
    self.x_grid_checkbox.stateChanged.connect(self.show_x_grid)
    x_grid_row = SettingsRowItem(self, "  X Axis Gridline", self.x_grid_checkbox)
    main_layout.addLayout(x_grid_row)

    self.y_grid_checkbox = QCheckBox(self)
    self.y_grid_checkbox.stateChanged.connect(self.show_y_grid)
    y_grid_row = SettingsRowItem(self, "  All Y Axis Gridlines", self.y_grid_checkbox)
    main_layout.addLayout(y_grid_row)

    self.grid_opacity_slider = QSlider(self)
    self.grid_opacity_slider.setOrientation(Qt.Horizontal)
    self.grid_opacity_slider.setMaximum(255)
    self.grid_opacity_slider.setValue(127)
    self.grid_opacity_slider.setSingleStep(32)
    self.grid_opacity_slider.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
    self.grid_opacity_slider.valueChanged.connect(self.change_gridline_opacity)
    grid_opacity_row = SettingsRowItem(self, "  Gridline Opacity", self.grid_opacity_slider)
    main_layout.addLayout(grid_opacity_row)

    plot_viewbox = self.plot.plotItem.vb
    plot_viewbox.sigXRangeChanged.connect(self.set_axis_datetimes)
    plot_viewbox.sigRangeChangedManually.connect(lambda *_: self.set_axis_datetimes())

auto_scroll_interval property

Get the autoscroll interval in milliseconds.

x_grid_visible property

Check if X-axis gridlines are visible.

gridline_opacity property

Get the current gridline opacity value (0-255).

show()

Show the modal positioned relative to its parent widget.

Source code in trace/widgets/plot_settings.py
173
174
175
176
177
178
def show(self):
    """Show the modal positioned relative to its parent widget."""
    parent_pos = self.parent().rect().bottomRight()
    global_pos = self.parent().mapToGlobal(parent_pos)
    self.move(global_pos)
    super().show()

set_show_legend(state)

Set the legend visibility based on checkbox state.

Parameters:

Name Type Description Default
state int or CheckState

The checkbox state

required
Source code in trace/widgets/plot_settings.py
180
181
182
183
184
185
186
187
188
189
190
191
@Slot(int)
@Slot(Qt.CheckState)
def set_show_legend(self, state: int | Qt.CheckState) -> None:
    """Set the legend visibility based on checkbox state.

    Parameters
    ----------
    state : int or Qt.CheckState
        The checkbox state
    """
    checked = Qt.CheckState(state) == Qt.Checked
    self.plot.setShowLegend(checked)

set_axis_tick_font_size(size)

Set the font size for all axis tick labels.

Parameters:

Name Type Description Default
size int

The font size in pixels

required
Source code in trace/widgets/plot_settings.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
@Slot(int)
def set_axis_tick_font_size(self, size: int) -> None:
    """Set the font size for all axis tick labels.

    Parameters
    ----------
    size : int
        The font size in pixels
    """
    font = QFont()
    font.setPixelSize(size)

    all_axes = self.plot.plotItem.getAxes()
    for axis in all_axes:
        axis.setStyle(tickFont=font)

set_time_axis_range(raw_range=(None, None))

PyQT Slot to set the plot's X-Axis range. This slot should be triggered on QDateTimeEdit value change.

Parameters:

Name Type Description Default
raw_range tuple[QDateTime, QDateTime]

Takes in a tuple of 2 values, where one is a QDateTime and the other is None. The positioning changes either the plot's min or max range value. By default (None, None)

(None, None)
Source code in trace/widgets/plot_settings.py
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
@Slot(object)
def set_time_axis_range(self, raw_range: tuple[QDateTime, QDateTime] = (None, None)) -> None:
    """PyQT Slot to set the plot's X-Axis range. This slot should be
    triggered on QDateTimeEdit value change.

    Parameters
    ----------
    raw_range : tuple[QDateTime, QDateTime], optional
        Takes in a tuple of 2 values, where one is a QDateTime and
        the other is None. The positioning changes either the plot's
        min or max range value. By default (None, None)
    """
    # Disable Autoscroll if enabled
    # self.ui.cursor_scale_btn.click()
    self.disable_autoscroll.emit()

    proc_range = [None, None]
    for ind, val in enumerate(raw_range):
        # Values that are QDateTime are converted to a float timestamp
        if isinstance(val, QDateTime):
            proc_range[ind] = val.toSecsSinceEpoch()
        # Values that are None use the existing range value
        elif not val:
            proc_range[ind] = self.plot.getXAxis().range[ind]
    proc_range.sort()

    logger.debug(f"Setting plot's X-Axis range to {proc_range}")
    self.plot.plotItem.vb.blockSignals(True)
    self.plot.plotItem.setXRange(*proc_range, padding=0)
    self.plot.plotItem.vb.blockSignals(False)

set_crosshair(state)

Enable or disable the crosshair on the plot.

Parameters:

Name Type Description Default
state int or CheckState

The checkbox state

required
Source code in trace/widgets/plot_settings.py
240
241
242
243
244
245
246
247
248
249
250
251
@Slot(int)
@Slot(Qt.CheckState)
def set_crosshair(self, state: int | Qt.CheckState) -> None:
    """Enable or disable the crosshair on the plot.

    Parameters
    ----------
    state : int or Qt.CheckState
        The checkbox state
    """
    checked = Qt.CheckState(state) == Qt.Checked
    self.plot.enableCrosshair(checked, 100, 100)

set_axis_datetimes(_=None, time_range=None)

Slot used to update the QDateTimeEdits on the Axis tab. This slot is called when the plot's X-Axis range changes values.

Parameters:

Name Type Description Default
_ ViewBox

The ViewBox on which the range is changing. This is unused

None
time_range Tuple[float, float]

The new range values for the QDateTimeEdits, by default None

None
Source code in trace/widgets/plot_settings.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
@Slot(object, object)
def set_axis_datetimes(self, _: ViewBox = None, time_range: tuple[float, float] = None) -> None:
    """Slot used to update the QDateTimeEdits on the Axis tab. This
    slot is called when the plot's X-Axis range changes values.

    Parameters
    ----------
    _ : ViewBox, optional
        The ViewBox on which the range is changing. This is unused
    time_range : Tuple[float, float], optional
        The new range values for the QDateTimeEdits, by default None
    """
    if not time_range:
        time_range = self.plot.getXAxis().range
    if min(time_range) <= 0:
        return

    time_range = [datetime.fromtimestamp(f) for f in time_range]

    edits = (self.start_datetime, self.end_datetime)
    for ind, qdt in enumerate(edits):
        if qdt.hasFocus():
            continue
        qdt.blockSignals(True)
        qdt.setDateTime(QDateTime(time_range[ind]))
        qdt.blockSignals(False)

show_x_grid(state)

Show or hide the X-Axis gridlines.

Parameters:

Name Type Description Default
state int or CheckState

The checkbox state

required
Source code in trace/widgets/plot_settings.py
280
281
282
283
284
285
286
287
288
289
290
291
292
@Slot(int)
@Slot(Qt.CheckState)
def show_x_grid(self, state: int | Qt.CheckState) -> None:
    """Show or hide the X-Axis gridlines.

    Parameters
    ----------
    state : int or Qt.CheckState
        The checkbox state
    """
    visible = Qt.CheckState(state) == Qt.Checked
    opacity = self.gridline_opacity
    self.set_plot_gridlines(visible, opacity)

show_y_grid(state)

Show or hide all Y-axis gridlines.

Parameters:

Name Type Description Default
state int or CheckState

The checkbox state

required
Source code in trace/widgets/plot_settings.py
294
295
296
297
298
299
300
301
302
303
304
305
@Slot(int)
@Slot(Qt.CheckState)
def show_y_grid(self, state: int | Qt.CheckState) -> None:
    """Show or hide all Y-axis gridlines.

    Parameters
    ----------
    state : int or Qt.CheckState
        The checkbox state
    """
    visible = Qt.CheckState(state) == Qt.Checked
    self.set_all_y_axis_gridlines.emit(visible)

change_gridline_opacity(opacity)

Change the opacity of the gridlines for both X and Y axes.

Parameters:

Name Type Description Default
opacity int

The opacity value (0-255)

required
Source code in trace/widgets/plot_settings.py
307
308
309
310
311
312
313
314
315
316
317
@Slot(int)
def change_gridline_opacity(self, opacity: int):
    """Change the opacity of the gridlines for both X and Y axes.

    Parameters
    ----------
    opacity : int
        The opacity value (0-255)
    """
    visible = self.x_grid_visible
    self.set_plot_gridlines(visible, opacity)

set_plot_gridlines(visible, opacity)

Set the plot's gridlines visibility and opacity for both X and Y axes.

Parameters:

Name Type Description Default
visible bool

Whether gridlines should be visible

required
opacity int

The opacity value (0-255)

required
Source code in trace/widgets/plot_settings.py
319
320
321
322
323
324
325
326
327
328
329
330
331
def set_plot_gridlines(self, visible: bool, opacity: int):
    """Set the plot's gridlines visibility and opacity for both X and Y axes.

    Parameters
    ----------
    visible : bool
        Whether gridlines should be visible
    opacity : int
        The opacity value (0-255)
    """
    normalized_opacity = opacity / 255
    self.plot.setShowXGrid(visible, normalized_opacity)
    self.grid_alpha_change.emit(opacity)

plot_setup(config)

Configure the plot settings from a configuration dictionary.

This method reads configuration values and updates the corresponding widgets, which will emit signals to update the plot.

Parameters:

Name Type Description Default
config dict

Configuration dictionary containing plot settings

required
Source code in trace/widgets/plot_settings.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
@Slot(dict)
def plot_setup(self, config: dict):
    """Configure the plot settings from a configuration dictionary.

    This method reads configuration values and updates the corresponding
    widgets, which will emit signals to update the plot.

    Parameters
    ----------
    config : dict
        Configuration dictionary containing plot settings
    """
    if "title" in config:
        self.plot_title_line_edit.setText(str(config["title"]))
    if "legend" in config:
        self.legend_checkbox.setChecked(bool(config["legend"]))
    if "mouseMode" in config:
        mouse_mode_index = int(config["mouseMode"] / 3)
        self.mouse_mode_combo.setCurrentIndex(mouse_mode_index)
    if "refreshInterval" in config:
        self.as_interval_spinbox.setValue(int(config["refreshInterval"] / 1000))
    if "crosshair" in config:
        self.crosshair_checkbox.setChecked(bool(config["crosshair"]))
    if "backgroundColor" in config:
        self.background_button.color = QColor(config["backgroundColor"])
    if "xGrid" in config:
        self.x_grid_checkbox.setChecked(bool(config["xGrid"]))
    if "yGrid" in config:
        self.y_grid_checkbox.setChecked(bool(config["yGrid"]))
    if "gridOpacity" in config:
        self.grid_opacity_slider.setValue(int(config["gridOpacity"]))

Axis Settings

AxisSettingsModal(parent, plot, axis)

Bases: QWidget

Modal widget for configuring individual axis settings including orientation, log mode, and gridline visibility.

This widget provides an interface for customizing the appearance and behavior of a single axis on the plot.

Parameters:

Name Type Description Default
parent QWidget

The parent widget

required
plot PyDMArchiverTimePlot

The plot widget containing the axis

required
axis BasePlotAxisItem

The axis to configure

required
Source code in trace/widgets/axis_settings.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def __init__(self, parent: QWidget, plot: PyDMArchiverTimePlot, axis: BasePlotAxisItem):
    """Initialize the axis settings modal.

    Parameters
    ----------
    parent : QWidget
        The parent widget
    plot : PyDMArchiverTimePlot
        The plot widget containing the axis
    axis : BasePlotAxisItem
        The axis to configure
    """
    super().__init__(parent)
    self.setWindowFlag(Qt.Popup)

    self.plot = plot
    self.axis = axis
    main_layout = QVBoxLayout()
    self.setLayout(main_layout)

    title_label = SettingsTitle(self, "Axis Settings", size=14)
    main_layout.addWidget(title_label)

    orientation_combo = QComboBox(self)
    orientation_combo.addItems(["Left", "Right"])
    orientation_combo.currentTextChanged.connect(self.set_axis_orientation)
    orientation_combo.setCurrentText("Right" if self.axis.orientation == "right" else "Left")
    orientation_row = SettingsRowItem(self, "Orientation", orientation_combo)
    main_layout.addLayout(orientation_row)

    log_checkbox = QCheckBox(self)
    log_checkbox.setChecked(self.axis.log_mode)
    log_checkbox.stateChanged.connect(self.set_axis_log_mode)
    log_mode_row = SettingsRowItem(self, "Log Mode", log_checkbox)
    main_layout.addLayout(log_mode_row)

    self.grid_checkbox = QCheckBox(self)
    self.grid_checkbox.setChecked(bool(self.axis.grid))
    self.grid_checkbox.stateChanged.connect(self.show_grid)
    y_grid_row = SettingsRowItem(self, "Y Axis Gridline", self.grid_checkbox)
    main_layout.addLayout(y_grid_row)

    self.palette_modal = CurveColorPaletteModal(self)
    self.curve_palette_button = QPushButton("Select")
    self.curve_palette_button.clicked.connect(self.palette_modal.show)
    self.palette_modal.sig_palette_changed.connect(self.sig_curve_palette_changed.emit)
    palette_row = SettingsRowItem(self, "Curve Palette", self.curve_palette_button)
    main_layout.addLayout(palette_row)

    self.trace_display = self.parent()
    while self.trace_display is not None and not isinstance(self.trace_display, Display):
        self.trace_display = self.trace_display.parent()
    if self.trace_display is not None:
        self.trace_display.gridline_opacity_change.connect(self.change_gridline_opacity)
        self.trace_display.set_all_y_axis_gridlines.connect(self.grid_checkbox.setChecked)

grid_visible property

Check if gridlines are visible for this axis.

show()

Show the modal positioned relative to its parent widget.

Source code in trace/widgets/axis_settings.py
83
84
85
86
87
88
def show(self) -> None:
    """Show the modal positioned relative to its parent widget."""
    parent_pos = self.parent().rect().bottomRight()
    global_pos = self.parent().mapToGlobal(parent_pos)
    self.move(global_pos)
    super().show()

set_axis_orientation(orientation)

Set the axis orientation (Left or Right).

Parameters:

Name Type Description Default
orientation str

The orientation string ("Left" or "Right")

required
Source code in trace/widgets/axis_settings.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
@Slot(str)
def set_axis_orientation(self, orientation: str) -> None:
    """Set the axis orientation (Left or Right).

    Parameters
    ----------
    orientation : str
        The orientation string ("Left" or "Right")
    """
    if orientation not in ["Left", "Right"]:
        return
    self.axis.orientation = orientation.lower()
    self.plot.plotItem.rebuildLayout()
    if self.axis.isVisible():
        self.axis.show()

set_axis_log_mode(state)

Enable or disable logarithmic scale for the axis.

Parameters:

Name Type Description Default
state int or CheckState

The checkbox state

required
Source code in trace/widgets/axis_settings.py
106
107
108
109
110
111
112
113
114
115
116
117
@Slot(int)
@Slot(Qt.CheckState)
def set_axis_log_mode(self, state: int | Qt.CheckState) -> None:
    """Enable or disable logarithmic scale for the axis.

    Parameters
    ----------
    state : int or Qt.CheckState
        The checkbox state
    """
    checked = Qt.CheckState(state) == Qt.Checked
    self.axis.log_mode = checked

show_grid(state)

Show or hide gridlines for the axis.

Parameters:

Name Type Description Default
state int or CheckState

The checkbox state

required
Source code in trace/widgets/axis_settings.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
@Slot(int)
@Slot(Qt.CheckState)
def show_grid(self, state: int | Qt.CheckState) -> None:
    """Show or hide gridlines for the axis.

    Parameters
    ----------
    state : int or Qt.CheckState
        The checkbox state
    """
    checked = Qt.CheckState(state) == Qt.Checked
    if not checked:
        self.axis.setGrid(False)
    else:
        try:
            opacity = self.trace_display.gridline_opacity
        except AttributeError:
            logger.debug("No trace display found, defaulting to full opacity")
            opacity = 255
        self.axis.setGrid(opacity)

change_gridline_opacity(opacity)

Change the opacity of gridlines for this axis.

Parameters:

Name Type Description Default
opacity int

The opacity value (0-255)

required
Source code in trace/widgets/axis_settings.py
140
141
142
143
144
145
146
147
148
149
150
151
@Slot(int)
def change_gridline_opacity(self, opacity: int) -> None:
    """Change the opacity of gridlines for this axis.

    Parameters
    ----------
    opacity : int
        The opacity value (0-255)
    """
    if not self.grid_visible:
        return
    self.axis.setGrid(opacity)

Curve Settings

CurveSettingsModal(parent, plot, curve)

Bases: QWidget

Modal widget for configuring individual curve settings including name, color, data bins, live/archive connections, line properties, and symbol properties.

This widget provides a comprehensive interface for customizing the appearance and behavior of a single curve on the plot.

Parameters:

Name Type Description Default
parent QWidget

The parent widget

required
plot PyDMArchiverTimePlot

The plot widget containing the curve

required
curve TimePlotCurveItem

The curve to configure

required
Source code in trace/widgets/curve_settings.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def __init__(self, parent: QWidget, plot: PyDMArchiverTimePlot, curve: TimePlotCurveItem):
    """Initialize the curve settings modal.

    Parameters
    ----------
    parent : QWidget
        The parent widget
    plot : PyDMArchiverTimePlot
        The plot widget containing the curve
    curve : TimePlotCurveItem
        The curve to configure
    """
    super().__init__(parent)
    self.setWindowFlag(Qt.Popup)

    self.legend = plot._legend
    self.curve = curve
    main_layout = QVBoxLayout()
    self.setLayout(main_layout)

    title_label = SettingsTitle(self, "Curve Settings", size=14)
    main_layout.addWidget(title_label)

    name_edit = QLineEdit(curve.name(), self)
    name_edit.editingFinished.connect(self.set_curve_name)
    name_row = SettingsRowItem(self, "Curve Name", name_edit)
    main_layout.addLayout(name_row)

    color_button = ColorButton(parent=self, color=curve.color_string)
    color_button.color_changed.connect(self.set_curve_color)
    color_row = SettingsRowItem(self, "Color", color_button)
    main_layout.addLayout(color_row)

    self.bin_count_line_edit = None
    if hasattr(curve, "setOptimizedDataBins"):
        self.bin_count_line_edit = bin_count_line_edit = QLineEdit()
        bin_count_line_edit.setMaximumWidth(65)
        bin_count_line_edit.returnPressed.connect(self.set_curve_data_bins)
        optimized_bin_count = SettingsRowItem(self, "Optimized bin count", bin_count_line_edit)
        bin_count = curve.optimized_data_bins
        if not bin_count:
            bin_count = plot.optimized_data_bins
        bin_count_line_edit.setPlaceholderText(str(bin_count))
        main_layout.addLayout(optimized_bin_count)

    self.live_toggle = QCheckBox("")
    self.live_toggle.setCheckState(Qt.Checked if self.curve.liveData else Qt.Unchecked)
    self.live_toggle.stateChanged.connect(self.set_live_data_connection)
    live_toggle_row = SettingsRowItem(self, "Connect to Live", self.live_toggle)
    main_layout.addLayout(live_toggle_row)

    self.archive_toggle = QCheckBox("")
    self.archive_toggle.setCheckState(Qt.Checked if self.curve.use_archive_data else Qt.Unchecked)
    self.archive_toggle.stateChanged.connect(self.set_archive_data_connection)
    archive_toggle_row = SettingsRowItem(self, "Connect to Archive", self.archive_toggle)
    main_layout.addLayout(archive_toggle_row)

    line_title_label = SettingsTitle(self, "Line")
    main_layout.addWidget(line_title_label)

    init_curve_type = "Step" if curve.stepMode in ["left", "right", "center"] else "Direct"
    type_combo = ComboBoxWrapper(self, {"Direct": None, "Step": "right"}, init_curve_type)
    type_combo.text_changed.connect(self.set_curve_type)
    type_row = SettingsRowItem(self, "  Type", type_combo)
    main_layout.addLayout(type_row)

    style_combo = ComboBoxWrapper(self, TimePlotCurveItem.lines, curve.lineStyle)
    style_combo.text_changed.connect(self.set_curve_style)
    style_row = SettingsRowItem(self, "  Style", style_combo)
    main_layout.addLayout(style_row)

    width_options = {f"{i}px": i for i in range(1, 6)}
    width_combo = ComboBoxWrapper(self, width_options, curve.lineWidth)
    width_combo.text_changed.connect(self.set_curve_width)
    width_row = SettingsRowItem(self, "  Width", width_combo)
    main_layout.addLayout(width_row)

    extension_option = QCheckBox(self)
    extension_option.stateChanged.connect(self.set_extension_option)
    extension_option_row = SettingsRowItem(self, "  Line Extension", extension_option)
    main_layout.addLayout(extension_option_row)

    symbol_title_label = SettingsTitle(self, "Symbol")
    main_layout.addWidget(symbol_title_label)

    shape_combo = ComboBoxWrapper(self, TimePlotCurveItem.symbols, curve.symbol)
    shape_combo.text_changed.connect(self.set_symbol_shape)
    shape_row = SettingsRowItem(self, "  Shape", shape_combo)
    main_layout.addLayout(shape_row)

    size_options = {f"{i}px": i for i in range(5, 26, 5)}
    size_combo = ComboBoxWrapper(self, size_options, curve.symbolSize)
    size_combo.text_changed.connect(self.set_symbol_size)
    size_row = SettingsRowItem(self, "  Size", size_combo)
    main_layout.addLayout(size_row)

set_curve_data_bins()

Set the optimized data bins for the curve based on user input.

Validates the input and updates the curve's bin count if valid. Shows visual feedback for invalid input.

Source code in trace/widgets/curve_settings.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
def set_curve_data_bins(self) -> None:
    """Set the optimized data bins for the curve based on user input.

    Validates the input and updates the curve's bin count if valid.
    Shows visual feedback for invalid input.
    """
    n_bins = self.bin_count_line_edit.text()
    if not n_bins.isdigit() or int(n_bins) < 1:
        self.bin_count_line_edit.setStyleSheet("border: 2px solid #d32f2f")
        logger.warning("Invalid bin count entered. Please enter a postive integer.")
        return
    else:
        self.bin_count_line_edit.setStyleSheet("")
    try:
        n_bins = int(n_bins)
        self.curve.setOptimizedDataBins(n_bins)
        self.bin_count_line_edit.setPlaceholderText(str(n_bins))
    except (AttributeError, ValueError) as e:
        logger.warning(f"Unable to set data bins: {e}")

set_live_data_connection(state)

Enable or disable live data connection for the curve.

Parameters:

Name Type Description Default
state CheckState

The checkbox state

required
Source code in trace/widgets/curve_settings.py
137
138
139
140
141
142
143
144
145
def set_live_data_connection(self, state: Qt.CheckState) -> None:
    """Enable or disable live data connection for the curve.

    Parameters
    ----------
    state : Qt.CheckState
        The checkbox state
    """
    self.curve.liveData = state == Qt.Checked

set_archive_data_connection(state)

Enable or disable archive data connection for the curve.

Parameters:

Name Type Description Default
state CheckState

The checkbox state

required
Source code in trace/widgets/curve_settings.py
147
148
149
150
151
152
153
154
155
def set_archive_data_connection(self, state: Qt.CheckState) -> None:
    """Enable or disable archive data connection for the curve.

    Parameters
    ----------
    state : Qt.CheckState
        The checkbox state
    """
    self.curve.use_archive_data = state == Qt.Checked

show()

Show the modal positioned relative to its parent widget.

Source code in trace/widgets/curve_settings.py
157
158
159
160
161
162
163
164
165
166
167
def show(self) -> None:
    """Show the modal positioned relative to its parent widget."""
    # Reset Bin Count LineEdit if it exists
    if self.bin_count_line_edit:
        self.bin_count_line_edit.setStyleSheet("")
        self.bin_count_line_edit.setText("")

    parent_pos = self.parent().rect().bottomRight()
    global_pos = self.parent().mapToGlobal(parent_pos)
    self.move(global_pos)
    super().show()

set_curve_name()

Set the curve name based on user input.

If the name is empty, reverts to the original name. Updates both the curve data and legend label.

Source code in trace/widgets/curve_settings.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
@Slot()
def set_curve_name(self) -> None:
    """Set the curve name based on user input.

    If the name is empty, reverts to the original name.
    Updates both the curve data and legend label.
    """
    sender = self.sender()
    name = sender.text()

    if not name:
        sender.blockSignals(True)
        sender.setText(self.curve.name())
        sender.blockSignals(False)
    elif name != self.curve.name():
        legend_label = self.legend.getLabel(self.curve)
        legend_label.setText(name)

        x, y = self.curve.getData()
        self.curve.setData(name=name, x=x, y=y)

set_curve_color(color)

Set the curve color and emit the color changed signal.

Parameters:

Name Type Description Default
color QColor

The new color for the curve

required
Source code in trace/widgets/curve_settings.py
190
191
192
193
194
195
196
197
198
199
200
@Slot(QColor)
def set_curve_color(self, color: QColor) -> None:
    """Set the curve color and emit the color changed signal.

    Parameters
    ----------
    color : QColor
        The new color for the curve
    """
    self.curve.color = color
    self.color_changed.emit(color)

set_curve_type(curve_type=None)

Set the curve step mode (Direct or Step).

Parameters:

Name Type Description Default
curve_type str or None

The step mode type, or None for direct plotting

None
Source code in trace/widgets/curve_settings.py
202
203
204
205
206
207
208
209
210
211
@Slot(object)
def set_curve_type(self, curve_type: str | None = None) -> None:
    """Set the curve step mode (Direct or Step).

    Parameters
    ----------
    curve_type : str or None
        The step mode type, or None for direct plotting
    """
    self.curve.stepMode = curve_type

set_curve_style(style)

Set the line style for the curve.

Parameters:

Name Type Description Default
style int

The line style index

required
Source code in trace/widgets/curve_settings.py
213
214
215
216
217
218
219
220
221
222
@Slot(object)
def set_curve_style(self, style: int) -> None:
    """Set the line style for the curve.

    Parameters
    ----------
    style : int
        The line style index
    """
    self.curve.lineStyle = style

set_curve_width(width)

Set the line width for the curve.

Parameters:

Name Type Description Default
width int

The line width in pixels

required
Source code in trace/widgets/curve_settings.py
224
225
226
227
228
229
230
231
232
233
@Slot(object)
def set_curve_width(self, width: int) -> None:
    """Set the line width for the curve.

    Parameters
    ----------
    width : int
        The line width in pixels
    """
    self.curve.lineWidth = width

set_extension_option(state)

Enable or disable line extension for the curve.

Parameters:

Name Type Description Default
state int or CheckState

The checkbox state

required
Source code in trace/widgets/curve_settings.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
@Slot(int)
@Slot(Qt.CheckState)
def set_extension_option(self, state: int | Qt.CheckState) -> None:
    """Enable or disable line extension for the curve.

    Parameters
    ----------
    state : int or Qt.CheckState
        The checkbox state
    """
    enable = Qt.CheckState(state) == Qt.Checked

    self.curve.show_extension_line = enable
    self.curve.getViewBox().addItem(self.curve._extension_line)
    self.curve.redrawCurve()

set_symbol_shape(shape)

Set the symbol shape for the curve.

Parameters:

Name Type Description Default
shape str

The symbol shape name

required
Source code in trace/widgets/curve_settings.py
251
252
253
254
255
256
257
258
259
260
@Slot(object)
def set_symbol_shape(self, shape: str) -> None:
    """Set the symbol shape for the curve.

    Parameters
    ----------
    shape : str
        The symbol shape name
    """
    self.curve.symbol = shape

set_symbol_size(size)

Set the symbol size for the curve.

Parameters:

Name Type Description Default
size int

The symbol size in pixels

required
Source code in trace/widgets/curve_settings.py
262
263
264
265
266
267
268
269
270
271
@Slot(object)
def set_symbol_size(self, size: int) -> None:
    """Set the symbol size for the curve.

    Parameters
    ----------
    size : int
        The symbol size in pixels
    """
    self.curve.symbolSize = size