From 32d4e63e8cca34268a1a01ba336c7631ee139c06 Mon Sep 17 00:00:00 2001 From: Aalto QCD labs <-> Date: Thu, 5 May 2022 18:43:05 +0300 Subject: [PATCH 01/14] Implement basic functionality for uploading waveforms --- .../drivers/Tektronix/AFG3000.py | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py b/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py index 9ddcd2d40..247738b69 100644 --- a/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py +++ b/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py @@ -1,10 +1,16 @@ -from typing import Any, Tuple +from typing import Any, Tuple, List +import numpy as np from qcodes import VisaInstrument import qcodes.utils.validators as vals from qcodes.utils.helpers import create_on_off_val_mapping + +MIN_WAVEFORM_LENGTH = 2 +MAX_WAVEFORM_LENGTH = 131072 + + class AFG3000(VisaInstrument): """Qcodes driver for Tektronix AFG3000 series arbitrary function generator. @@ -621,6 +627,57 @@ def synchronize_phase(self, src: int) -> None: self.log.info('Synchronizing CH1 and CH2 phase.') self.write(f'SOURce{src}:PHASe:INITiate') + def reset_edit_memory(self, points: int = 1000): + """ + Reset the contents of the edit memory (EMEM), and set its size to + `points`. + + Each point will be initialized with the value 8191, which corresponds + to zero amplitude. + """ + if (points < MIN_WAVEFORM_LENGTH or + points > MAX_WAVEFORM_LENGTH): + raise ValueErorr(f"Trying to reset edit memory with invalid length: {points}") + + self.write(f"DATA:DEFINE EMEM,{points}") + + def upload_waveform(self, waveform: List[float], memory: int): + """ + Upload a waveform to the editable memory (EMEM), and then copy it to the + USER1, USER2, USER3 or USER4 memory. + + Args: + waveform: list of points containing the waveform data, + containing values from -1 to 1. + memory: The USER# memory where to to store the waveform, from 1 to 4. + """ + if (len(waveform) < MIN_WAVEFORM_LENGTH or + len(waveform) > MAX_WAVEFORM_LENGTH): + raise ValueErorr(f"Invalid waveform length: {len(waveform)}") + + if memory not in [1, 2, 3, 4]: + raise ValueErorr(f"Invalid value for memory: '{memory}'") + + self.reset_edit_memory(len(waveform)) + + # convert waveform to two-byte integer values in the range 0..16382 (= 2**14-2) + wf_codes = ((np.array(waveform) + 1) * 0.5 * (2**14-2)).astype(np.uint16) + n_bytes = wf_codes.nbytes + n_digits_in_n_bytes = len(str(n_bytes)) + + # write data to the editable memory + self.visa_handle.write_binary_values( + f"DATA:DATA EMEM,#{n_digits_in_n_bytes}{n_bytes}", + wf_codes, + datatype="H", # unsigned short (16 bits) + is_big_endian=True, # the AFG expects data in big endian order + header_fmt="empty", # do not prefix data with header + ) + + # copy data from editable memory to USER. + self.write(f"DATA:COPY USER{memory},EMEM") + + class AFG3252(AFG3000): pass From 4b770f02d8f83a6720b3112ed1a5ddc1a70e5c7e Mon Sep 17 00:00:00 2001 From: Aalto QCD labs <-> Date: Thu, 5 May 2022 20:15:21 +0300 Subject: [PATCH 02/14] Attempt at readin back waveform data from AFG This doesn't completely work. For some reason, after the query, the AFG keeps spitting out data (self.visa_handle.read_raw() returns some data), and I couldn't figure out what to do with it based on the manual. So this is mostly a curiosity that I will revert. --- .../drivers/Tektronix/AFG3000.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py b/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py index 247738b69..a08b55c8a 100644 --- a/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py +++ b/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py @@ -561,6 +561,15 @@ def __init__(self, name: str, address: str, **kwargs: Any): vals=vals.Enum('INTernal', 'INT', 'EXTernal', 'EXT') ) + self.add_parameter( + name='ememory_points', + label='Number of points in the editable memory', + get_cmd='DATA:POINts? EMEM', + get_parser=int, + set_cmd='DATA:POINts EMEM, {}', + vals=vals.Ints(MIN_WAVEFORM_LENGTH, MAX_WAVEFORM_LENGTH), + ) + # Trigger parameters self.add_parameter( name='trigger_slope', @@ -677,6 +686,31 @@ def upload_waveform(self, waveform: List[float], memory: int): # copy data from editable memory to USER. self.write(f"DATA:COPY USER{memory},EMEM") + def read_waveform(self, memory: int) -> np.ndarray: + """ + Copy the waveform stored in one of the user memories to the edit memory + and read the waveform data from the device. + """ + if memory not in [1, 2, 3, 4]: + raise ValueErorr(f"Invalid value for memory: '{memory}'") + + # copy the requested memory contents to the edit memory + self.write(f"DATA:COPY EMEM,USER{memory}") + + self.write("DATA:DATA? EMEM") + self.visa_handle.read_bytes(1) # the first character is '#', skip it + # how many digits does the size of the data have? + n_digits_in_n_bytes = int(self.visa_handle.read_bytes(1)) + # the actual size of the data in bytes + n_bytes = int(self.visa_handle.read_bytes(n_digits_in_n_bytes)) + # actually read the data + wf_bytes = self.visa_handle.read_bytes(n_bytes) + + # convert bytes to numbers and apply appropriate scaling + # ">u2" means "two-byte unsigned integer in big endian format" + wf_codes = np.frombuffer(wf_bytes, dtype=">u2") + return (wf_codes * 1.0 / (2**14-2) - 0.5) * 2 + class AFG3252(AFG3000): pass From c5e0c4a6011361aa02eb7a6f9d44c9ba68dfcffc Mon Sep 17 00:00:00 2001 From: Aalto QCD labs <-> Date: Thu, 5 May 2022 20:17:02 +0300 Subject: [PATCH 03/14] Revert "Attempt at readin back waveform data from AFG" This reverts commit 0dbf56bed84ed957075f89829fe1a6c7d60190ad. --- .../drivers/Tektronix/AFG3000.py | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py b/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py index a08b55c8a..247738b69 100644 --- a/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py +++ b/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py @@ -561,15 +561,6 @@ def __init__(self, name: str, address: str, **kwargs: Any): vals=vals.Enum('INTernal', 'INT', 'EXTernal', 'EXT') ) - self.add_parameter( - name='ememory_points', - label='Number of points in the editable memory', - get_cmd='DATA:POINts? EMEM', - get_parser=int, - set_cmd='DATA:POINts EMEM, {}', - vals=vals.Ints(MIN_WAVEFORM_LENGTH, MAX_WAVEFORM_LENGTH), - ) - # Trigger parameters self.add_parameter( name='trigger_slope', @@ -686,31 +677,6 @@ def upload_waveform(self, waveform: List[float], memory: int): # copy data from editable memory to USER. self.write(f"DATA:COPY USER{memory},EMEM") - def read_waveform(self, memory: int) -> np.ndarray: - """ - Copy the waveform stored in one of the user memories to the edit memory - and read the waveform data from the device. - """ - if memory not in [1, 2, 3, 4]: - raise ValueErorr(f"Invalid value for memory: '{memory}'") - - # copy the requested memory contents to the edit memory - self.write(f"DATA:COPY EMEM,USER{memory}") - - self.write("DATA:DATA? EMEM") - self.visa_handle.read_bytes(1) # the first character is '#', skip it - # how many digits does the size of the data have? - n_digits_in_n_bytes = int(self.visa_handle.read_bytes(1)) - # the actual size of the data in bytes - n_bytes = int(self.visa_handle.read_bytes(n_digits_in_n_bytes)) - # actually read the data - wf_bytes = self.visa_handle.read_bytes(n_bytes) - - # convert bytes to numbers and apply appropriate scaling - # ">u2" means "two-byte unsigned integer in big endian format" - wf_codes = np.frombuffer(wf_bytes, dtype=">u2") - return (wf_codes * 1.0 / (2**14-2) - 0.5) * 2 - class AFG3252(AFG3000): pass From 6b0b313505ceca3a114de21bb03bee1962e19462 Mon Sep 17 00:00:00 2001 From: Aalto QCD labs <-> Date: Thu, 5 May 2022 20:40:01 +0300 Subject: [PATCH 04/14] Use built-in prefix functionality instead of calculating it manually --- qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py b/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py index 247738b69..012a20589 100644 --- a/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py +++ b/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py @@ -662,16 +662,14 @@ def upload_waveform(self, waveform: List[float], memory: int): # convert waveform to two-byte integer values in the range 0..16382 (= 2**14-2) wf_codes = ((np.array(waveform) + 1) * 0.5 * (2**14-2)).astype(np.uint16) - n_bytes = wf_codes.nbytes - n_digits_in_n_bytes = len(str(n_bytes)) # write data to the editable memory self.visa_handle.write_binary_values( - f"DATA:DATA EMEM,#{n_digits_in_n_bytes}{n_bytes}", + f"DATA:DATA EMEM,", wf_codes, datatype="H", # unsigned short (16 bits) is_big_endian=True, # the AFG expects data in big endian order - header_fmt="empty", # do not prefix data with header + header_fmt="ieee", ) # copy data from editable memory to USER. From f511193c5c8f8c826225f290b449dbcb7cd71b71 Mon Sep 17 00:00:00 2001 From: Aalto QCD labs <-> Date: Fri, 6 May 2022 13:10:24 +0300 Subject: [PATCH 05/14] Add note on voltage levels in docstring --- qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py b/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py index 012a20589..0a19d3cdb 100644 --- a/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py +++ b/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py @@ -646,6 +646,11 @@ def upload_waveform(self, waveform: List[float], memory: int): Upload a waveform to the editable memory (EMEM), and then copy it to the USER1, USER2, USER3 or USER4 memory. + The waveform data should contain values in the range -1..1. Note that + the actual voltage values that are output will depend on the values of + the `voltage_low1/2` and `voltage_high1/2` parameters; -1 will be + mapped to `voltage_low` and +1 to `voltage_high`. + Args: waveform: list of points containing the waveform data, containing values from -1 to 1. From 3439a01ce164773ea2cc23683ef3525302a2d7d3 Mon Sep 17 00:00:00 2001 From: Aalto QCD labs <-> Date: Fri, 6 May 2022 15:34:23 +0300 Subject: [PATCH 06/14] Change waveform data range to be 0..1 This is easier to understand if the voltage_low/hi is asymmetric. --- qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py b/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py index 0a19d3cdb..9e531099f 100644 --- a/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py +++ b/qcodes_contrib_drivers/drivers/Tektronix/AFG3000.py @@ -646,14 +646,14 @@ def upload_waveform(self, waveform: List[float], memory: int): Upload a waveform to the editable memory (EMEM), and then copy it to the USER1, USER2, USER3 or USER4 memory. - The waveform data should contain values in the range -1..1. Note that + The waveform data should contain values in the range 0..1. Note that the actual voltage values that are output will depend on the values of - the `voltage_low1/2` and `voltage_high1/2` parameters; -1 will be - mapped to `voltage_low` and +1 to `voltage_high`. + the `voltage_low1/2` and `voltage_high1/2` parameters; 0 will be + mapped to `voltage_low` and 1 to `voltage_high`. Args: waveform: list of points containing the waveform data, - containing values from -1 to 1. + containing values from 0 to 1. memory: The USER# memory where to to store the waveform, from 1 to 4. """ if (len(waveform) < MIN_WAVEFORM_LENGTH or @@ -666,7 +666,7 @@ def upload_waveform(self, waveform: List[float], memory: int): self.reset_edit_memory(len(waveform)) # convert waveform to two-byte integer values in the range 0..16382 (= 2**14-2) - wf_codes = ((np.array(waveform) + 1) * 0.5 * (2**14-2)).astype(np.uint16) + wf_codes = (np.array(waveform) * (2**14-2)).astype(np.uint16) # write data to the editable memory self.visa_handle.write_binary_values( From 729e71fde480ec8e685817074188dcc3016c2003 Mon Sep 17 00:00:00 2001 From: Aalto QCD labs <-> Date: Tue, 24 May 2022 12:39:21 +0300 Subject: [PATCH 07/14] Add example notebook for AFG3000 with arbitrary waveform output --- docs/examples/Tektronix_AFG3000_series.ipynb | 1177 ++++++++++++++++++ 1 file changed, 1177 insertions(+) create mode 100644 docs/examples/Tektronix_AFG3000_series.ipynb diff --git a/docs/examples/Tektronix_AFG3000_series.ipynb b/docs/examples/Tektronix_AFG3000_series.ipynb new file mode 100644 index 000000000..c848f714a --- /dev/null +++ b/docs/examples/Tektronix_AFG3000_series.ipynb @@ -0,0 +1,1177 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tektronix AFG3000 series arbitrary function generator" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import numpy as np\n", + "\n", + "from qcodes_contrib_drivers.drivers.Tektronix.AFG3000 import AFG3000" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connected to: TEKTRONIX AFG3252 (serial:C010219, firmware:SCPI:99.0 FV:3.2.4) in 3.94s\n" + ] + } + ], + "source": [ + "afg = AFG3000(\"AFG\", address=\"TCPIP0::10.0.100.108::inst0::INSTR\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AFG:\n", + "\tparameter value\n", + "--------------------------------------------------------------------------------\n", + "IDN :\t{'vendor': 'TEKTRONIX', 'model': 'AFG3252', 'serial...\n", + "am_depth1 :\t50 (%)\n", + "am_depth2 :\t50 (%)\n", + "am_internal_efile1 :\t\"\" \n", + "am_internal_efile2 :\t\"\" \n", + "am_internal_freq1 :\t10000 (Hz)\n", + "am_internal_freq2 :\t10000 (Hz)\n", + "am_internal_function1 :\tSIN \n", + "am_internal_function2 :\tSIN \n", + "am_internal_source1 :\tINT \n", + "am_internal_source2 :\tINT \n", + "am_state1 :\tFalse \n", + "am_state2 :\tFalse \n", + "burst_mode1 :\tTRIG \n", + "burst_mode2 :\tTRIG \n", + "burst_ncycles1 :\t1 \n", + "burst_ncycles2 :\t5 \n", + "burst_state1 :\tTrue \n", + "burst_state2 :\tFalse \n", + "burst_tdelay1 :\t0 (s)\n", + "burst_tdelay2 :\t0 (s)\n", + "center_freq1 :\t5.5e+05 (Hz)\n", + "center_freq2 :\t5.5e+05 (Hz)\n", + "combine1 :\t\"\" \n", + "combine2 :\t\"\" \n", + "fm_deviation1 :\t1e+06 (Hz)\n", + "fm_deviation2 :\t1e+06 (Hz)\n", + "fm_internal_efile1 :\t\"\" \n", + "fm_internal_efile2 :\t\"\" \n", + "fm_internal_freq1 :\t10000 (Hz)\n", + "fm_internal_freq2 :\t10000 (Hz)\n", + "fm_internal_function1 :\tSIN \n", + "fm_internal_function2 :\tSIN \n", + "fm_internal_source1 :\tINT \n", + "fm_internal_source2 :\tINT \n", + "fm_state1 :\tFalse \n", + "fm_state2 :\tFalse \n", + "freq_concurrent1 :\tFalse \n", + "freq_concurrent2 :\tFalse \n", + "freq_cw1 :\t10000 (Hz)\n", + "freq_cw2 :\t1e+06 (Hz)\n", + "freq_mode1 :\tCW \n", + "freq_mode2 :\tCW \n", + "freq_span1 :\t9e+05 (Hz)\n", + "freq_span2 :\t9e+05 (Hz)\n", + "freq_start1 :\t1e+05 (Hz)\n", + "freq_start2 :\t1e+05 (Hz)\n", + "freq_stop1 :\t1e+06 (Hz)\n", + "freq_stop2 :\t1e+06 (Hz)\n", + "fsk_freq1 :\t1e+06 (Hz)\n", + "fsk_freq2 :\t1e+06 (Hz)\n", + "fsk_internal_rate1 :\t50 (Hz)\n", + "fsk_internal_rate2 :\t50 (Hz)\n", + "fsk_source1 :\tINT \n", + "fsk_source2 :\tINT \n", + "fsk_state1 :\tFalse \n", + "fsk_state2 :\tFalse \n", + "function_efile1 :\t\"\" \n", + "function_efile2 :\t\"\" \n", + "function_ramp_symmetry1 :\t50 (%)\n", + "function_ramp_symmetry2 :\t50 (%)\n", + "function_shape1 :\tUSER \n", + "function_shape2 :\tSIN \n", + "impedance_output1 :\t50 (Ohm)\n", + "impedance_output2 :\t50 (Ohm)\n", + "noise_level3 :\t10 (%)\n", + "noise_level4 :\t10 (%)\n", + "phase1 :\t0 (degrees)\n", + "phase2 :\t0 (degrees)\n", + "pm_deviation1 :\t1.5708 (degrees)\n", + "pm_deviation2 :\t1.5708 (degrees)\n", + "pm_internal_efile1 :\t\"\" \n", + "pm_internal_efile2 :\t\"\" \n", + "pm_internal_freq1 :\t10000 (Hz)\n", + "pm_internal_freq2 :\t10000 (Hz)\n", + "pm_internal_function1 :\tSIN \n", + "pm_internal_function2 :\tSIN \n", + "pm_internal_source1 :\tINT \n", + "pm_internal_source2 :\tINT \n", + "pm_state1 :\tFalse \n", + "pm_state2 :\tFalse \n", + "polarity_output1 :\tNORM \n", + "polarity_output2 :\tNORM \n", + "pulse_delay1 :\t0 (s)\n", + "pulse_delay2 :\t0 (s)\n", + "pulse_duty_cycle1 :\t50 (%)\n", + "pulse_duty_cycle2 :\t50 (%)\n", + "pulse_hold1 :\tDUTY \n", + "pulse_hold2 :\tDUTY \n", + "pulse_period1 :\t1e-06 (s)\n", + "pulse_period2 :\t1e-06 (s)\n", + "pulse_trans_lead1 :\t2.5e-09 (s)\n", + "pulse_trans_lead2 :\t2.5e-09 (s)\n", + "pulse_trans_trail1 :\t2.5e-09 (s)\n", + "pulse_trans_trail2 :\t2.5e-09 (s)\n", + "pulse_width1 :\t5e-07 (s)\n", + "pulse_width2 :\t5e-07 (s)\n", + "pwm_duty_deviation1 :\t5 (%)\n", + "pwm_duty_deviation2 :\t5 (%)\n", + "pwm_internal_efile1 :\t\"\" \n", + "pwm_internal_efile2 :\t\"\" \n", + "pwm_internal_freq1 :\t10000 (Hz)\n", + "pwm_internal_freq2 :\t10000 (Hz)\n", + "pwm_internal_function1 :\tSIN \n", + "pwm_internal_function2 :\tSIN \n", + "pwm_internal_source1 :\tINT \n", + "pwm_internal_source2 :\tINT \n", + "pwm_state1 :\tFalse \n", + "pwm_state2 :\tFalse \n", + "ref_osc_source :\tINT \n", + "state_output1 :\tTrue \n", + "state_output2 :\tFalse \n", + "sweep_hold_time1 :\t0 (s)\n", + "sweep_hold_time2 :\t0 (s)\n", + "sweep_mode1 :\tAUTO \n", + "sweep_mode2 :\tAUTO \n", + "sweep_return_time1 :\t0.001 (s)\n", + "sweep_return_time2 :\t0.001 (s)\n", + "sweep_spacing1 :\tLIN \n", + "sweep_spacing2 :\tLIN \n", + "sweep_time1 :\t0.01 (s)\n", + "sweep_time2 :\t0.01 (s)\n", + "timeout :\t20 (s)\n", + "trigger_mode :\tTRIG \n", + "trigger_slope :\tPOS \n", + "trigger_source :\tTIM \n", + "trigger_timer :\t0.001 (s)\n", + "voltage_amplitude1 :\t1 \n", + "voltage_amplitude2 :\t1 \n", + "voltage_concurrent1 :\tFalse \n", + "voltage_concurrent2 :\tFalse \n", + "voltage_high1 :\t0.5 (V)\n", + "voltage_high2 :\t0.5 (V)\n", + "voltage_limit_high1 :\t1 (V)\n", + "voltage_limit_high2 :\t5 (V)\n", + "voltage_limit_low1 :\t-1 (V)\n", + "voltage_limit_low2 :\t-5 (V)\n", + "voltage_low1 :\t-0.5 (V)\n", + "voltage_low2 :\t-0.5 (V)\n", + "voltage_offset1 :\t0 (V)\n", + "voltage_offset2 :\t0 (V)\n", + "voltage_unit1 :\tVPP \n", + "voltage_unit2 :\tVPP \n" + ] + } + ], + "source": [ + "afg.print_readable_snapshot(update=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Upload an arbitrary waveform to the AFG and output it\n", + "\n", + "Note that the waveform data values are in the range 0..1, but the actual voltages output by the AFG depend on the values of the `afg.voltage_low1/2` and `afg.voltage_high1/2` parameters." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the waveform we're going to upload. Note that the values of the time axis don't really mean anything; the duration of the waveform is determined by the `afg.freq_cw1/2` parameters (see below)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " fig.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
');\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('