from typing import List, Dict
from functools import partial
import matplotlib as mpl
from qcodes.plots.qcmatplotlib import MatPlot
from silq.tools.notebook_tools import *
import pyperclip
from time import time
import numpy as np
import logging
from qcodes.instrument.parameter import _BaseParameter
from qcodes.station import Station
from qcodes.data.data_set import DataSet
from qcodes.data.data_array import DataArray
__all__ = ['PlotAction', 'SetGates', 'MeasureSingle', 'MoveGates',
'SwitchPlotIdx', 'InteractivePlot', 'SliderPlot', 'CalibrationPlot',
'DCPlot', 'ScanningPlot', 'TracePlot', 'DCSweepPlot']
logger = logging.getLogger(__name__)
[docs]class PlotAction:
"""Interactive key/button action for ``MatPlot``
A PlotAction can be attached to an `InteractivePlot`, adding some sort of
interactivity, e.g. change parameter value when pressing a key button.
Parameters:
plot: Plot object
timeout: Seconds before plot action is deactivated. Only relevant if the
``enable_key`` differs from the actual key/button actions.
enable_key: String to enable plot action.
enabled (bool): Plot action is enabled.
"""
enable_key = None
def __init__(self,
plot: MatPlot,
timeout: int = None,
enable_key: str = None):
self.timeout = timeout
self.t_enable_key_pressed = None
if enable_key is not None:
self.enable_key = enable_key
self.plot = plot
@property
def enabled(self):
if self.enable_key is None:
# No enable key specified, so always enabled
return True
elif self.t_enable_key_pressed is None:
# Enable key never pressed, so disabled
return False
elif self.timeout is None:
# Enable key pressed, and no timeout, so enabled
return True
else:
# Depends on if last enable_key press was within timeout seconds
return time() - self.t_enable_key_pressed < self.timeout
[docs] def key_press(self, event):
"""Handle Matplotlib key press.
This enables PlotAction if key press is ``enable_key``
"""
if event.key == self.enable_key:
logger.debug(f'Enabling action {self}')
self.t_enable_key_pressed = time()
[docs] def handle_code(self,
code: str,
copy: bool = False,
execute: bool = False,
new_cell: bool = True):
"""Handle code, either executing it or copying it to clipbard
Args:
code: Python code to handle.
copy: Copy to clipboard.
execute: Execute code, only relevant if ``new_cell`` is True.
new_cell: Create new cell below current one and add code.
"""
if copy:
logger.debug('Copying code to clipboard')
pyperclip.copy(code)
if new_cell:
logger.debug(f'Adding code to new cell below, execute: {execute}')
create_cell(code, execute=execute, location='below')
[docs]class SetGates(PlotAction):
"""Set gates when button pressed in MatPlot, enabled with ``alt + g``.
Only works for 2D plots.
Parameters:
plot: Plot object
timeout: Seconds before plot action is deactivated. Only relevant if the
``enable_key`` differs from the actual key/button actions.
enable_key: String to enable plot action.
enabled (bool): Plot action is enabled.
"""
enable_key = 'alt+g'
[docs]class MeasureSingle(PlotAction):
"""Measure parameter at clicked gate vales, enabled with ``alt + s``
Upon button click, a new cell below current one is created, in which gates
are set to clicked values, and a qc.Measure is performed for measure_param.
Only works for 2D plots.
Parameters:
plot: Plot object
timeout: Seconds before plot action is deactivated. Only relevant if the
``enable_key`` differs from the actual key/button actions.
enable_key: String to enable plot action.
enabled (bool): Plot action is enabled."""
enable_key = 'alt+s'
[docs]class MoveGates(PlotAction):
"""Increase/decrease gates when pressing alt + {arrow}, enabled with alt+m.
Alt + up/down moves the y-gate.
Alt + left/right moves the x-gate.
Alt + +/- increases/decreases step size.
Parameters:
plot: Plot object
timeout: Seconds before plot action is deactivated. Only relevant if the
``enable_key`` differs from the actual key/button actions.
enable_key: String to enable plot action.
enabled (bool): Plot action is enabled.
delta (float): Step size.
"""
enable_key = 'alt+m'
delta = 0.001 # step to move when pressing a key
[docs] def key_press(self, event):
super().key_press(event)
if event.key == self.key:
if self.plot.point is None:
self.plot.point = self.plot[0].plot(self.plot.x_gate(),
self.plot.y_gate(), 'ob', )[0]
else:
self.plot.point.set_xdata(self.plot.x_gate())
self.plot.point.set_ydata(self.plot.y_gate())
elif event.key in ['alt+up', 'alt+down']:
val = self.plot.y_gate()
delta = self.delta * (1 if event.key == 'alt+up' else -1)
self.plot.y_gate(val + delta)
self.plot.point.set_ydata(val + delta)
elif event.key in ['alt+left', 'alt+right']:
val = self.plot.x_gate()
delta = self.delta * (1 if event.key == 'alt+right' else -1)
self.plot.x_gate(val + delta)
self.plot.point.set_xdata(val + delta)
elif event.key in ['alt++', 'alt+=']:
self.delta /= 1.5
elif event.key == 'alt+-':
self.delta *= 1.5
else:
pass
class TuneCompensation(PlotAction):
enable_key = 'alt+c'
def __init__(self, *args, **kwargs):
""" A tool to plot a line of compensated plunging/emptying on a DC scan.
Key commands:
'alt+c' : enables this tool
'alt+<arrow_key>' : move the central (read) position of your line in that respective direction
'alt+<+ or ->' : increase/decrease the compensation angle with the horizontal
'alt+<8 or 2>' : increase/decrease the empty depth
'alt+<6 or 4>' : increase/decrease the plunge depth
'alt+5' : reset all parameters to their default values
"""
super().__init__(*args, **kwargs)
self.default_empty_depth = 10e-3
self.default_plunge_depth = -10e-3
self.default_theta = -45 # degrees
self.default_y_read = np.nanmean(self.plot.data_set.DC_voltage.set_arrays[0])
self.default_x_read = np.nanmean(self.plot.data_set.DC_voltage.set_arrays[1][0])
self.plot_feats = []
self.delta_v = 1e-3
self.delta_t = 1
self.initialize_parameters()
def initialize_parameters(self):
self.plunge_depth = self.default_plunge_depth
self.empty_depth = self.default_empty_depth
self.theta = self.default_theta
self.x_read = self.default_x_read
self.y_read = self.default_y_read
def key_press(self, event):
super().key_press(event)
if event.key in ['alt+up', 'alt+down', 'alt+left', 'alt+right', 'alt+8',
'alt+6', 'alt+4', 'alt+2', 'alt+5', 'alt++', 'alt+-']:
# Tune compensation
if event.key == 'alt++':
self.theta += self.delta_t
elif event.key == 'alt+-':
self.theta -= self.delta_t
# Tune read position
elif event.key == 'alt+up':
self.y_read += self.delta_v
elif event.key == 'alt+down':
self.y_read -= self.delta_v
elif event.key == 'alt+right':
self.x_read += self.delta_v
elif event.key == 'alt+left':
self.x_read -= self.delta_v
# Tune empty depth
elif event.key == 'alt+8':
self.empty_depth += self.delta_v
elif event.key == 'alt+2':
self.empty_depth -= self.delta_v
# Tune plunge depth
elif event.key == 'alt+6':
self.plunge_depth += self.delta_v
elif event.key == 'alt+4':
self.plunge_depth -= self.delta_v
# Reset
elif event.key == 'alt+5':
self.initialize_parameters()
self.draw_features()
def draw_features(self):
# Remove old features before drawing new ones
for plot_feat in self.plot_feats:
f = plot_feat.pop(0)
f.remove()
del f
set_vals = self.plot.data_set.DC_voltage.set_arrays[1][0]
# Calculate compensation factor
self.compensation = np.tan(np.deg2rad(self.theta))
read_pt = self.plot[0].plot(self.x_read, self.y_read, 'ro',
markeredgewidth=2, markerfacecolor='None')
empty_pt = self.plot[0].plot(self.x_read + self.empty_depth / np.sqrt(1 + 1 / self.compensation ** 2),
self.y_read + np.sign(self.compensation) *
self.empty_depth / np.sqrt(self.compensation ** 2 + 1),
'go', markeredgewidth=2, markerfacecolor='None')
plunge_pt = self.plot[0].plot(self.x_read + self.plunge_depth / np.sqrt(1 + 1 / self.compensation ** 2),
self.y_read + np.sign(self.compensation) *
self.plunge_depth / np.sqrt(self.compensation ** 2 + 1),
'yo', markeredgewidth=2, markerfacecolor='None')
comp_line = self.plot[0].plot(set_vals,
(set_vals - self.x_read) / self.compensation + self.y_read, 'c')
self.plot_feats = [read_pt, empty_pt, plunge_pt, comp_line]
self.plot.update()
[docs]class SwitchPlotIdx(PlotAction):
"""Change plot index when pressing ``alt+{arrow}``, used with `SliderPlot`.
Alt + left/right changes first plot index.
Alt + up/down changes second plot index (if two sliders).
Parameters:
plot: Plot object
timeout: Seconds before plot action is deactivated. Only relevant if the
``enable_key`` differs from the actual key/button actions.
enable_key: String to enable plot action.
enabled (bool): Plot action is enabled."""
[docs] def key_press(self, event):
super().key_press(event)
plot_idx = list(self.plot.plot_idx)
if event.key in ['alt+left', 'alt+right']:
set_vals = self.plot.set_vals[0]
if event.key == 'alt+left':
plot_idx[0] = max(plot_idx[0] - 1, 0)
else:
plot_idx[0] = min(plot_idx[0] + 1, len(set_vals) - 1)
self.plot.plot_idx = tuple(plot_idx)
self.plot.update_slider(0)
elif event.key in ['alt+down', 'alt+up'] and len(plot_idx) > 1:
set_vals = self.plot.set_vals[1]
if event.key == 'alt+down':
plot_idx[1] = max(plot_idx[1] - 1, 0)
else:
plot_idx[1] = min(plot_idx[1] + 1, len(set_vals) - 1)
self.plot.plot_idx = tuple(plot_idx)
self.plot.update_slider(1)
[docs]class InteractivePlot(MatPlot):
"""Base class for ``MatPlot`` plots adding interactivity.
The QCoDeS ``MatPlot``, which uses ``matplotlib``, can be interactive, and
respond to key/button presses. This subclass of MatPlot enables such
interactivity by adding `PlotAction` to the plot. Each `PlotAction` can
respond to specific key presses or button clicks.
Parameters:
*args: args passed to ``MatPlot``.
actions: `PlotAction` list to use for plot.
timeout: Timeout for any action to be disabled.
**kwargs: kwargs passed to ``MatPlot``.
"""
def __init__(self,
*args,
actions: List[PlotAction] = (),
timeout: int = 600, **kwargs):
super().__init__(*args, **kwargs)
self.station = Station.default
self.layout = getattr(self.station, 'layout', None)
self.actions = actions
# setting timeout sets it for all actions
self.timeout = timeout
# cid is used to connect specific functions to event handlers
self.cid = {}
self._event_key = None
self._event_button = None
self.connect_event('key_press_event', self.handle_key_press)
self.connect_event('button_press_event', self.handle_button_press)
@property
def timeout(self):
return self._timeout
@timeout.setter
def timeout(self, timeout):
self._timeout = timeout
for action in self.actions:
action.timeout = timeout
[docs] def load_data_array(self, data_array: DataArray):
"""Retrieve properties of a ``DataArray``, such as set arrays and labels.
Args:
data_array: DataArray to extract
Returns:
Dict[str, Any]:
set_arrays (List[DataArray]): List of set arrays.
labels: Labels of set arrays
gates: Gates of set arrays, None if not in ``Station``.
"""
set_arrays = data_array.set_arrays
labels = []
gates = []
for set_array in data_array.set_arrays:
labels.append(set_array.name)
gates.append(getattr(self.station, set_array.name, None))
return {'set_arrays': set_arrays,
'labels': labels,
'gates': gates}
[docs] def connect_event(self,
event: str,
action: PlotAction):
"""Attach PlotAction to a specific event.
Args:
event: matplotlib event (e.g. key_press_event, button_press_event)
action: PlotAction to attach
"""
if event in self.cid:
self.fig.canvas.mpl_disconnect(self.cid[event])
cid = self.fig.canvas.mpl_connect(event, action)
self.cid[event] = cid
[docs] def handle_key_press(self, event):
"""Handle key press event, forwarding to relevant `PlotAction`
The relevant PlotActions are those that are either enabled, or whose
``enable_key`` match the key press event.
"""
self._event_key = event
logger.debug(f'Key pressed: {event.key}')
try:
for action in self.actions:
if action.enabled or event.key == action.enable_key:
action.key_press(event=event)
except Exception as e:
logger.error(f'key press: {e}')
[docs]class SliderPlot(InteractivePlot):
"""Slide through 1D/2D images of a ``DataArray`` with more dimensions.
Parameters:
data_array: Multidimensional ``DataArray`` to display.
ndim: Plotting dimension (1 or 2)
**kwargs: Additional kwargs to `InteractivePlot` and ``MatPlot``.
"""
def __init__(self, data_array, ndim=2, **kwargs):
self.ndim = ndim
self.data_array = data_array
super().__init__(actions=[SwitchPlotIdx(self)], **kwargs)
self.fig.tight_layout(rect=[0, 0.15, 1, 0.95])
results = self.load_data_array(data_array)
self.set_arrays = results['set_arrays']
self.num_sliders = len(self.set_arrays) - self.ndim
self.plot_idx = tuple(0 for _ in self.set_arrays[:-self.ndim])
self.add(self.data_array[self.plot_idx], **self.plot_kwargs)
# Add sliders
self.slideraxes = [self.fig.add_axes([0.13, 0.02 + 0.04*k, 0.6, 0.05],
facecolor='yellow')
for k in range(self.num_sliders)]
self.sliders = []
self.set_vals = []
for k, sliderax in enumerate(self.slideraxes):
set_idx = -self.ndim - (k + 1)
set_vals = self.set_arrays[set_idx]
if set_vals.ndim == 2:
# Make more general for > 2D
set_vals = set_vals[0]
slider = mpl.widgets.Slider(ax=sliderax,
label=self.set_arrays[set_idx].name,
valmin=np.nanmin(set_vals),
valmax=np.nanmax(set_vals),
valinit=set_vals[0])
self.set_vals.append(set_vals)
slider.on_changed(partial(self.update_slider, k))
slider.drawon = False
self.sliders.append(slider)
@property
def plot_kwargs(self):
if self.ndim == 1:
return {'x': self.set_arrays[-1][self.plot_idx][0],
'xlabel': self.set_arrays[-1].label,
'ylabel': self.data_array.label,
'xunit': self.set_arrays[-1].unit,
'yunit': self.data_array.unit,}
elif self.ndim == 2:
return {'x': self.set_arrays[-1][self.plot_idx][0],
'y': self.set_arrays[-2][self.plot_idx],
'xlabel': self.set_arrays[-1].label,
'ylabel': self.set_arrays[-2].label,
'zlabel': self.data_array.label,
'xunit': self.set_arrays[-1].unit,
'yunit': self.set_arrays[-2].unit,
'zunit': self.data_array.unit}
else:
raise NotImplementedError(f'{self.ndim} dims not supported')
[docs] def update_slider(self, idx, value=None):
if value is None:
value = self.set_vals[idx][self.plot_idx[idx]]
self.sliders[idx].set_val(value)
elif value == self.set_vals[idx][self.plot_idx[idx]]:
self.update()
else:
# Check if value is one of the set values
logger.debug(f'Updating slider {idx} to {value}')
slider_idx = np.nanargmin(abs(self.set_vals[idx] - value))
self.plot_idx = tuple(val if k != idx else slider_idx
for k, val in enumerate(self.plot_idx))
value = self.set_vals[idx][self.plot_idx[idx]]
self.sliders[idx].set_val(value)
[docs] def update(self):
# Update plot
self[0].clear()
self[0].add(self.data_array[self.plot_idx], **self.plot_kwargs)
[docs]class CalibrationPlot(InteractivePlot):
"""Interactive plot for 2D calibrations, move gates and measure at points.
The 2D calibration scan must contain a ``Parameter`` that returns the
contrast. Pressing ``alt + m`` adds a dot on the colorplot, which can be
moved by holding ``alt`` and pressing an arrow key. the contrast can then
be measured at the dot by pressing ``alt + s``.
Args:
data_set: Calibration 2D scan data set.
**kwargs: Additional kwargs to `InteractivePlot` and ``MatPlot``.
samples_measure (int): Samples to use when measuring at a single point.
"""
measure_parameter = 'adiabatic_ESR'
samples_measure = 200
def __init__(self, data_set: DataSet, **kwargs):
self.data_set = data_set
if 'voltage_difference' in data_set.arrays:
super().__init__(data_set.contrast, data_set.dark_counts,
data_set.voltage_difference,
**kwargs)
else:
super().__init__(data_set.contrast, data_set.dark_counts, **kwargs)
results = self.load_data_array(self.data_set.contrast)
self.y_gate, self.x_gate = results['gates']
self.y_label, self.x_label = results['labels']
self.actions = [SetGates(self), MeasureSingle(self), MoveGates(self)]
[docs]class DCPlot(InteractivePlot):
"""Interactive plot for a 2D DC scan, For easy moving gates on 2D plot.
Args:
data_set: 2D DC scan ``DataSet``.
**kwargs: Additional kwargs for `InteractivePlot` and ``MatPlot``.
"""
def __init__(self, data_set: DataSet, **kwargs):
self.data_set = data_set
super().__init__(data_set.DC_voltage, **kwargs)
results = self.load_data_array(data_set.DC_voltage)
self.y_gate, self.x_gate = results['gates']
self.y_label, self.x_label = results['labels']
self.actions = [SetGates(self), MoveGates(self), TuneCompensation(self)]
[docs]class ScanningPlot(InteractivePlot):
"""Base class for interactive plots to repeatedly measure and refresh plot.
Args:
parameter: Parameter to measure and plot.
interval: Measuring and updating interval.
auto_start: Start refreshing once initialized. If False, refreshing can
be started by calling `ScanningPlot.start`.
**kwargs: Additional kwargs to `InteractivePlot` and ``Matplot``.
"""
# AcquisitionParameter type
def __init__(self,
parameter: _BaseParameter,
interval: float = 0.01,
auto_start: bool = False,
**kwargs):
super().__init__(**kwargs)
self.update_idx = 0
self.update_start_idx = 1
self.t_start = None
self.timer = self.fig.canvas.new_timer(interval=interval * 1000)
self.timer.add_callback(self.scan)
self.connect_event('close_event', self.stop)
self.parameter = parameter
self.parameter.continuous = auto_start
if auto_start:
self.parameter.setup(start=False)
self.scan(initialize=True, stop=(not auto_start))
if auto_start:
# Already started during acquire
self.start(setup=False)
@property
def interval(self):
return self.timer.interval / 1000
@interval.setter
def interval(self, interval):
if hasattr(self, 'timer'):
self.timer.interval = interval * 1000
@property
def update_interval(self):
if self.update_idx > 0:
return (time() - self.t_start) / (self.update_idx -
self.update_start_idx)
[docs] def start(self,
setup: bool = True,
start: bool = True):
"""Start measuring and refreshing plot
Args:
setup: Setup `AcquisitionParameter`
start: Start instruments, only used if ``setup`` is True.
"""
self.parameter.continuous = True
if setup:
self.parameter.setup(start=start)
self.timer.start()
self.update_idx = 0
[docs] def stop(self, *args):
"""Stop measuring and refreshing plot.
Timer is stopped.
Args:
*args: Unused args passed if method is called as a callback
"""
logger.debug('Stopped')
self.timer.stop()
self.layout.stop()
self.parameter.continuous = False
[docs] def scan(self, initialize=False, stop=False):
"""Perform single meeasurement and update plot.
Repeatedly called by timer.
Args:
initialize: True if this method is called during initialization.
stop: Stop instruments after acquisition.
"""
if self.update_idx == self.update_start_idx:
self.t_start = time()
self.parameter()
if stop:
self.layout.stop()
self.update_plot(initialize=initialize)
self.update_idx += 1
[docs]class TracePlot(ScanningPlot):
"""Interactive plot that repeatedly measures pulse sequence and plots trace
Args:
parameter: `TraceParameter` whose pulse sequence to measure.
**kwargs: Additional kwargs to `InteractivePlot` and ``MatPlot``.
"""
# TraceParameter type
def __init__(self, parameter: _BaseParameter, **kwargs):
subplots = kwargs.pop('subplots', 1)
average_mode = getattr(parameter, 'average_mode', 'none')
if parameter.samples > 1 and average_mode == 'none':
subplots = (len(self.layout.acquisition_channels()), 1)
else:
subplots = 1
super().__init__(parameter, subplots=subplots, **kwargs)
# self.actions = [MoveGates(self)]
[docs] def update_plot(self, initialize: bool = False):
"""Update plot with new trace
Args:
initialize: Method called during initialization.
"""
for k, name in enumerate(self.parameter.names):
result = self.parameter.results[name]
if initialize:
setpoints = self.parameter.setpoints[k]
setpoint_names = self.parameter.setpoint_names[k]
setpoint_units = self.parameter.setpoint_units[k]
name = self.parameter.names[k]
unit = self.parameter.units[k]
if len(setpoints) == 2:
# import pdb; pdb.set_trace()
self[k].add(result, x=setpoints[1], y=setpoints[0],
xlabel=setpoint_names[1],
ylabel=setpoint_names[0],
xunit=setpoint_units[1],
yunit=setpoint_units[0],
zlabel=name,
zunit=unit)
self[k].y_label, self.x_label = setpoint_names
if hasattr(self.station, self.x_label) and \
hasattr(self.station, self.y_label):
self[k].x_gate = getattr(self.station, self.x_label)
self[k].y_gate = getattr(self.station, self.y_label)
self[k].plot([self.x_gate.get_latest()],
[self.y_gate.get_latest()], 'ob', ms=5)
else:
print(f'adding plot for {name}')
# import pdb; pdb.set_trace()
self.add(result[0], x=setpoints[0],
xlabel=setpoint_names[0],
ylabel=name,
xunit=setpoint_units[0],
yunit=unit)
else:
result_config = self.traces[k]['config']
if 'z' in result_config:
result_config['z'] = result
else:
result_config['y'] = result
super().update_plot()
[docs]class DCSweepPlot(ScanningPlot):
"""Refreshing 2D DC plot using `DCSweepParameter` for fast 2D DC scanning.
Args:
parameter: `DCSweepParameter` for fast 2D DC scanning.
gate_mapping: Mapping of gate names, for plot labels.
**kwargs: Additional kwargs to `InteractivePlot` and ``MatPlot``.
"""
gate_mapping = {}
point_color = 'r'
# DCSweepParameter type
def __init__(self,
parameter: _BaseParameter,
gate_mapping: Dict[str, str] = None,
**kwargs):
if gate_mapping is not None:
self.gate_mapping = gate_mapping
if parameter.trace_pulse.enabled:
subplots = (2, 1)
kwargs['gridspec_kw'] = {'height_ratios': [2, 1]}
kwargs['figsize'] = kwargs.get('figsize', (6.5, 6))
else:
subplots = 1
self.point = None
super().__init__(parameter, subplots=subplots, **kwargs)
if parameter.trace_pulse.enabled:
self[1].set_ylim(-0.1, 1.3)
self.actions = [MoveGates(self)]
[docs] def update_plot(self, initialize=False):
"""Update plot with new 2D DC scan.
Args:
initialize: Method called during initialization.
"""
for k, name in enumerate(self.parameter.names):
result = self.parameter.results[name]
if initialize:
setpoints = self.parameter.setpoints[k]
setpoint_names = self.parameter.setpoint_names[k]
setpoint_units = self.parameter.setpoint_units[k]
name = self.parameter.names[k]
unit = self.parameter.units[k]
if len(setpoints) == 2:
self[k].add(result, x=setpoints[1], y=setpoints[0],
xlabel=setpoint_names[1],
ylabel=setpoint_names[0],
xunit=setpoint_units[1],
yunit=setpoint_units[0],
zlabel=name,
zunit=unit)
self.x_label = self.gate_mapping.get(setpoint_names[1],
setpoint_names[1])
self.y_label = self.gate_mapping.get(setpoint_names[0],
setpoint_names[0])
if hasattr(self.station, self.x_label) and \
hasattr(self.station, self.y_label):
self.x_gate = getattr(self.station, self.x_label)
self.y_gate = getattr(self.station, self.y_label)
self.point = self[k].plot(self.x_gate.get_latest(),
self.y_gate.get_latest(),
'o'+self.point_color, ms=5)[0]
else:
self[k].add(result, x=setpoints[0],
xlabel=setpoint_names[0],
ylabel=name,
xunit=setpoint_units[0],
yunit=unit)
else:
result_config = self.traces[k]['config']
if 'z' in result_config:
result_config['x'] = self.parameter.setpoints[k][1]
result_config['y'] = self.parameter.setpoints[k][0]
result_config['z'] = result
if self.point is not None:
self.point.set_xdata(self.x_gate.get_latest())
self.point.set_ydata(self.y_gate.get_latest())
else:
result_config['y'] = result
super().update_plot()