From df51e1c508b8226faaef233759ed1599fcf6b913 Mon Sep 17 00:00:00 2001 From: jaseg Date: Thu, 18 Mar 2021 08:37:07 +0100 Subject: [PATCH] Small updates to paper and notebook --- doc/paper/rotohsm_paper.pdf | Bin 1190616 -> 1191965 bytes doc/paper/rotohsm_paper.tex | 2 + doc/paper/rotohsm_tech_report.pdf | Bin 111459 -> 112307 bytes .../Accelerometer Data Analysis.ipynb | 4648 ++++++++++++++++- 4 files changed, 4384 insertions(+), 266 deletions(-) diff --git a/doc/paper/rotohsm_paper.pdf b/doc/paper/rotohsm_paper.pdf index f0ad0b66c41accfd83c8cf85816dd8334a591fd9..2a52783d8ee998fd17fe7169d3279de2d6fa0392 100644 GIT binary patch delta 16728 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 b3tXvr(>H1fN%L45SQr^_sj9mAyKw;kxlE5f delta 15401 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 ab!BBuo8GmJQI6Zd%+i=kRn^tsjSBz=CgiFB diff --git a/prototype/sensor-analysis/Accelerometer Data Analysis.ipynb b/prototype/sensor-analysis/Accelerometer Data Analysis.ipynb index b7cf70d..23f31b8 100644 --- a/prototype/sensor-analysis/Accelerometer Data Analysis.ipynb +++ b/prototype/sensor-analysis/Accelerometer Data Analysis.ipynb @@ -45,41 +45,18 @@ }, { "cell_type": "code", - "execution_count": 706, - "metadata": {}, + "execution_count": 836, + "metadata": { + "scrolled": false + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Last run was ID #33 with 3178 packets total, 125 distinct over 99.94346904754639s\n" + "Loading run #28 with 4918 packets total, 178 distinct over 161.5061011314392s\n" ] - } - ], - "source": [ - "num_packets, = db.execute('SELECT COUNT(*) FROM packets WHERE run_id=?', (last_run,)).fetchone()\n", - "num_packets_distinct, = db.execute('SELECT COUNT(*) FROM (SELECT DISTINCT data FROM packets WHERE run_id=?)', (last_run,)).fetchone()\n", - "timespan_start, timespan_end = db.execute('SELECT MIN(timestamp_us)/1e6, MAX(timestamp_us)/1e6 FROM packets WHERE run_id=?', (last_run,)).fetchone()\n", - "timespan = timespan_end - timespan_start\n", - "print(f'Last run was ID #{last_run} with {num_packets} packets total, {num_packets_distinct} distinct over {timespan}s')" - ] - }, - { - "cell_type": "code", - "execution_count": 707, - "metadata": {}, - "outputs": [], - "source": [ - "timestamps = db.execute('SELECT timestamp_us/1e6 FROM packets WHERE run_id=? ORDER BY timestamp_us', (last_run,)).fetchall()\n", - "timestamps = [ ts - timespan_start for ts, in timestamps ]\n", - "deltas = [ b-a for a, b in zip(timestamps[:-1], timestamps[1:]) ]" - ] - }, - { - "cell_type": "code", - "execution_count": 708, - "metadata": {}, - "outputs": [ + }, { "data": { "application/javascript": [ @@ -1041,7 +1018,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1050,134 +1027,108 @@ "metadata": {}, "output_type": "display_data" }, - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 708, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "fig, ax = plt.subplots()\n", - "ax.plot(deltas)" - ] - }, - { - "cell_type": "code", - "execution_count": 709, - "metadata": { - "scrolled": true - }, - "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Packet length: 40\n" + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 611 ... 813\n" ] } ], - "source": [ - "packet_lengths = db.execute('SELECT LENGTH(data) FROM packets WHERE run_id=? GROUP BY LENGTH(data)', (last_run,)).fetchall()\n", - "assert len(packet_lengths) == 1\n", - "packet_len, = packet_lengths[0]\n", - "print('Packet length:', packet_len)" - ] - }, - { - "cell_type": "code", - "execution_count": 710, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Very approximate lower bound on baudrate: 129032.25806451612 bd\n" - ] - } - ], - "source": [ - "#approx_baudrate = 1.0 / (np.mean([ x for x in deltas if x < interval*0.02]) / (packet_len*10))\n", - "approx_baudrate = 1.0 / (0.0031 / (packet_len*10))\n", - "print(f'Very approximate lower bound on baudrate: {approx_baudrate} bd')" - ] - }, - { - "cell_type": "code", - "execution_count": 711, - "metadata": {}, - "outputs": [], "source": [ "def decode_packet(packet):\n", " seq, *data, _crc = struct.unpack(' 1:\n", + " # In test_run.sqlite3 run 2 this happens to coincide with the time I intentionally bumped the rotor... ?\n", + " warnings.warn(f'BUG: Duplicate sequence number {seq} for {len(set(le_packets))} payloads!')\n", + " print('BUG: Duplicate sequence number')\n", + " print('Sequence number:', seq)\n", + " for seq, data in set(le_packets):\n", + " print(' ', data)\n", + " \n", + " seqs = list(by_seq)\n", + " print(f'Sequence number range: {min(seqs)} ... {max(seqs)}')\n", + " \n", + " # FIXME this is only approximate, doesn't consider sequence numbers properly!!!\n", + " # Negate values: Our sensor is mounted such that -X points outwards,\n", + " # so by negating we get larger centrifugal force -> higher value\n", + " y = np.array([ -val for (_seq, values), *_rest in by_seq.values() for val in values[:8] ])\n", + " t = np.arange(0, len(y)) / sampling_rate / 60\n", + " if plot:\n", + " plot_measurements(ax2, t, y)\n", + " return t, y, packet_delays, packet_timestamps\n", + " \n", + "load_run(28);" ] }, { "cell_type": "code", - "execution_count": 712, + "execution_count": 1019, "metadata": { "scrolled": false }, - "outputs": [], - "source": [ - "# group packets by sequence number\n", - "by_seq = { k: list(g) for k, g in itertools.groupby(packets, key=lambda x: x[0]) }\n", - "for seq, le_packets in by_seq.items():\n", - " # make sure we only ever have one version of a packet with a particular sequence number (no CRC collisions)\n", - " if len(set(le_packets)) > 1:\n", - " # In test_run.sqlite3 run 2 this happens to coincide with the time I intentionally bumped the rotor... ?\n", - " warnings.warn(f'BUG: Duplicate sequence number {seq} for {len(set(le_packets))} payloads!')\n", - " print('BUG: Duplicate sequence number')\n", - " print('Sequence number:', seq)\n", - " for seq, data in set(le_packets):\n", - " print(' ', data)" - ] - }, - { - "cell_type": "code", - "execution_count": 713, - "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Sequence number range: 35 ... 161\n" + "Loading run #40 with 4762 packets total, 682 distinct over 572.8108429908752s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 739 ... 1457\n" ] - } - ], - "source": [ - "seqs = list(by_seq)\n", - "print(f'Sequence number range: {min(seqs)} ... {max(seqs)}')" - ] - }, - { - "cell_type": "code", - "execution_count": 714, - "metadata": {}, - "outputs": [], - "source": [ - "# FIXME this is only approximate, doesn't consider sequence numbers properly!!!\n", - "# Negate values: Our sensor is mounted such that -X points outwards,\n", - "# so by negating we get larger centrifugal force -> higher value\n", - "reassembled_values = np.array([ -val for (_seq, values), *_rest in by_seq.values() for val in values[:8] ])" - ] - }, - { - "cell_type": "code", - "execution_count": 715, - "metadata": { - "scrolled": false - }, - "outputs": [ + }, { "data": { "application/javascript": [ @@ -2139,7 +2090,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2152,76 +2103,68 @@ "name": "stdout", "output_type": "stream", "text": [ - "Found sensor offset: 1.00 g / 9.81 m/s^2\n", + "Found sensor offset: 0.57 g / 5.55 m/s^2\n", "\n", - "Centrifugal acceleration at 6.49 Hz:\n", + "Centrifugal acceleration at 3.12 Hz:\n", " Theory: 2.16 g / 21.14 m/s^2\n", - " Measurement: 55.57 g / 544.9912192549019 m/s^2\n", - " Rel. Error: -96.12 %\n", - " Abs. Error: -53.42 g / -523.85 m/s^2\n", + " Measurement: 1.90 g / 18.586194313627637 m/s^2\n", + " Rel. Error: 13.72 %\n", + " Abs. Error: 0.26 g / 2.55 m/s^2\n", "\n", - "Centrifugal acceleration at 6.49 Hz:\n", + "Centrifugal acceleration at 5.55 Hz:\n", " Theory: 6.82 g / 66.88 m/s^2\n", - " Measurement: -0.58 g / -5.674407202881151 m/s^2\n", - " Rel. Error: -1278.66 %\n", - " Abs. Error: 7.40 g / 72.56 m/s^2\n", + " Measurement: 6.60 g / 64.67817413725541 m/s^2\n", + " Rel. Error: 3.41 %\n", + " Abs. Error: 0.22 g / 2.20 m/s^2\n", "\n", - "Centrifugal acceleration at 6.49 Hz:\n", + "Centrifugal acceleration at 8.20 Hz:\n", " Theory: 14.89 g / 146.00 m/s^2\n", - " Measurement: nan g / nan m/s^2\n", - " Rel. Error: nan %\n", - " Abs. Error: nan g / nan m/s^2\n", + " Measurement: 14.82 g / 145.35641444809548 m/s^2\n", + " Rel. Error: 0.44 %\n", + " Abs. Error: 0.07 g / 0.64 m/s^2\n", "\n", - "Centrifugal acceleration at 6.49 Hz:\n", + "Centrifugal acceleration at 10.20 Hz:\n", " Theory: 23.04 g / 225.90 m/s^2\n", - " Measurement: nan g / nan m/s^2\n", - " Rel. Error: nan %\n", - " Abs. Error: nan g / nan m/s^2\n", + " Measurement: 23.11 g / 226.6766272456559 m/s^2\n", + " Rel. Error: -0.34 %\n", + " Abs. Error: -0.08 g / -0.77 m/s^2\n", "\n", - "Centrifugal acceleration at 6.49 Hz:\n", + "Centrifugal acceleration at 12.50 Hz:\n", " Theory: 34.60 g / 339.27 m/s^2\n", - " Measurement: nan g / nan m/s^2\n", - " Rel. Error: nan %\n", - " Abs. Error: nan g / nan m/s^2\n", + " Measurement: 34.80 g / 341.26606334638393 m/s^2\n", + " Rel. Error: -0.59 %\n", + " Abs. Error: -0.20 g / -2.00 m/s^2\n", "\n", - "Centrifugal acceleration at 6.49 Hz:\n", + "Centrifugal acceleration at 15.60 Hz:\n", " Theory: 53.88 g / 528.41 m/s^2\n", - " Measurement: nan g / nan m/s^2\n", - " Rel. Error: nan %\n", - " Abs. Error: nan g / nan m/s^2\n", + " Measurement: 51.75 g / 507.51233891199473 m/s^2\n", + " Rel. Error: 4.12 %\n", + " Abs. Error: 2.13 g / 20.90 m/s^2\n", "\n", - "Centrifugal acceleration at 6.49 Hz:\n", + "Centrifugal acceleration at 19.20 Hz:\n", " Theory: 81.62 g / 800.43 m/s^2\n", - " Measurement: nan g / nan m/s^2\n", - " Rel. Error: nan %\n", - " Abs. Error: nan g / nan m/s^2\n", + " Measurement: 82.09 g / 805.0345119987545 m/s^2\n", + " Rel. Error: -0.57 %\n", + " Abs. Error: -0.47 g / -4.60 m/s^2\n", "\n", - "Centrifugal acceleration at 6.49 Hz:\n", + "Centrifugal acceleration at 11.60 Hz:\n", " Theory: 29.79 g / 292.17 m/s^2\n", - " Measurement: nan g / nan m/s^2\n", - " Rel. Error: nan %\n", - " Abs. Error: nan g / nan m/s^2\n", + " Measurement: 29.71 g / 291.36536349393447 m/s^2\n", + " Rel. Error: 0.28 %\n", + " Abs. Error: 0.08 g / 0.81 m/s^2\n", "\n", "Centrifugal acceleration at 6.49 Hz:\n", " Theory: 9.33 g / 91.46 m/s^2\n", - " Measurement: nan g / nan m/s^2\n", - " Rel. Error: nan %\n", - " Abs. Error: nan g / nan m/s^2\n", + " Measurement: 9.21 g / 90.28568973827844 m/s^2\n", + " Rel. Error: 1.30 %\n", + " Abs. Error: 0.12 g / 1.17 m/s^2\n", "\n" ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - ":46: RuntimeWarning: Mean of empty slice.\n", - " ivl_avg = (reassembled_values / mems_lsb_per_g)[idx].mean()\n", - "/usr/lib/python3.9/site-packages/numpy/core/_methods.py:188: RuntimeWarning: invalid value encountered in double_scalars\n", - " ret = ret.dtype.type(ret / rcount)\n" - ] } ], "source": [ + "t, y, _1, _2 = load_run(40, plot=False)\n", + "\n", "sampling_rate = 10 # sps, set in firmware\n", "mems_lsb_per_g = 68 # LSBs per 1g for our accelerometer\n", "\n", @@ -2230,28 +2173,13 @@ "\n", "fig, ax = plt.subplots()\n", "#ax.axvspan(ivl_start/60/sampling_rate, ivl_end/60/sampling_rate, color='orange', alpha=0.5)\n", - "\n", - "ax.grid()\n", - "\n", - "ts = np.arange(0, len(reassembled_values)) / sampling_rate / 60\n", - "ax.plot(ts, reassembled_values / mems_lsb_per_g, color='darkblue', alpha=0.2)\n", - "#ax.plot(ts, scipy.signal.savgol_filter(reassembled_values / mems_lsb_per_g, 21, 2) )\n", - "sos = scipy.signal.butter(8, 0.5, 'lp', fs=10, output='sos')\n", - "filtered = scipy.signal.sosfiltfilt(sos, reassembled_values / mems_lsb_per_g)\n", - "ax.plot(ts, filtered, color='darkblue')\n", + " \n", + "plot_measurements(ax, t, y)\n", "\n", "g = 9.8066\n", "g_to_ms = lambda x: x * g\n", "ms_to_g = lambda x: x / g\n", "\n", - "ax.set_ylabel(r'$a\\; [g]$')\n", - "secax_y = ax.secondary_yaxis(\n", - " 'right', functions=(g_to_ms, ms_to_g))\n", - "secax_y.set_ylabel(r'$a\\; [ms^{-1}]$')\n", - "\n", - "formatter = ticker.FuncFormatter(lambda tick, _pos: f'{int(tick):02d}:{tick*60%60:02.0f}')\n", - "ax.xaxis.set_major_formatter(formatter)\n", - "\n", "r_mems = 55e-3 # radius of our sensor from the axis of rotation in m\n", "le_data = [(0, 50, 3.12), (1,50,5.55), (2,40, 8.2), (3, 30, 10.2), (4,15, 12.5), (5,10, 15.6),\n", " (6,10, 19.2), (7,11, 11.6), (8,15, 6.49)]\n", @@ -2263,11 +2191,12 @@ " omegan = 2*np.pi*f_actual # angular velocity\n", " acc = omegan**2 * r_mems # m/s^2\n", " acc_theory.append(acc / g)\n", + " ax.axvspan(ts_abs-ivl_w/2, ts_abs+ivl_w/2, zorder=1, color='red', alpha=0.1)\n", " \n", " ts_abs = ts_m + ts_s/60\n", " ivl_w = 0.5\n", - " idx = (ts_abs - ivl_w/2 < ts) & (ts < ts_abs + ivl_w/2)\n", - " ivl_avg = (reassembled_values / mems_lsb_per_g)[idx].mean()\n", + " idx = (ts_abs - ivl_w/2 < t) & (t < ts_abs + ivl_w/2)\n", + " ivl_avg = (y / mems_lsb_per_g)[idx].mean()\n", " acc_meas.append(ivl_avg)\n", "\n", "# Calculate offset correction. The offset is due to manufacturing imperfections inherent to the device.\n", @@ -2290,7 +2219,7 @@ "print(f'Found sensor offset: {sensor_offx:.2f} g / {sensor_offx*g:.2f} m/s^2')\n", "print()\n", "\n", - "for theory, meas, interval in zip(acc_theory, acc_meas, interval_speeds):\n", + "for theory, meas, interval, (_1, _2, f_actual) in zip(acc_theory, acc_meas, interval_speeds, le_data):\n", " ax.axhline(theory - sensor_offx, color='orange', alpha=1, zorder=1)\n", " meas += sensor_offx\n", " \n", @@ -2304,7 +2233,7 @@ }, { "cell_type": "code", - "execution_count": 716, + "execution_count": 1022, "metadata": {}, "outputs": [ { @@ -3268,7 +3197,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3277,72 +3206,47 @@ "metadata": {}, "output_type": "display_data" }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Largest peak at 4.31 Hz / 259 rpm\n", - "Mixing product 1 at 5.69 Hz / 341 rpm\n", - "Mixing product 2 at 14.31 Hz / 859 rpm\n", - "Mixing product 3 at 15.69 Hz / 941 rpm\n", - "Mixing product 4 at 24.31 Hz / 1459 rpm\n", - "Mixing product 5 at 25.69 Hz / 1541 rpm\n", - "Mixing product 6 at 34.31 Hz / 2059 rpm\n" - ] - }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 716, + "execution_count": 1022, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "tsa = np.array(timestamps)\n", - "#s_min, s_max = 70, 120\n", - "s_min, s_max = 40, 90\n", - "speed_idx = (tsa > s_min) & (tsa < s_max)\n", - "\n", - "ts = np.arange(0, len(reassembled_values)) / sampling_rate\n", - "fft_idx = (ts > s_min) & (ts < s_max)\n", - "\n", - "N = fft_idx.sum()\n", - "T = 1/sampling_rate\n", - "x = np.linspace(0.0, N*T, N)\n", - "y = reassembled_values[fft_idx] / mems_lsb_per_g # cut out beginning and that time we tapped the thing\n", - "y *= scipy.signal.windows.blackmanharris(len(y))\n", - "yf = scipy.fftpack.fft(y)\n", - "xf = np.linspace(0.0, 1/(2*T), N//2)\n", - "mag = 2/N * np.abs(yf[:N//2])\n", - "\n", "fig, ax = plt.subplots()\n", + "\n", + "freqs = np.array([ f_actual for (_1, _2, f_actual) in le_data ])\n", + "\n", + "acc_theory_sorted = [ acc for _f, acc in sorted(zip(freqs, acc_theory)) ]\n", + "acc_meas_sorted = [ acc for _f, acc in sorted(zip(freqs, acc_meas)) ]\n", + "freqs = sorted(freqs)\n", + "\n", + "ax.plot(freqs, acc_theory_sorted, label='Theory')\n", + "ax.plot(freqs, acc_meas_sorted, label='Measurements')\n", + " \n", "ax.grid()\n", - "ax.plot(xf, mag)\n", - "ax.set_ylabel('Magnitude [g]')\n", - "ax.set_xlabel('Frequency [Hz]')\n", + "ax.set_xlabel('$f\\;[Hz]$')\n", + "ax2 = ax.twiny()\n", + "x1, x2 = ax.get_xlim()\n", + "ax2.set_xlim((x1*60, x2*60))\n", + "ax2.set_xlabel('$f\\;[rpm]$')\n", + "ax.set_ylabel('$a\\;[g]$')\n", + "ax3 = ax.twinx()\n", + "y1, y2 = ax.get_ylim()\n", + "ax3.set_ylim(y1*g, y2*g)\n", + "ax3.set_ylabel('$a\\;[ms^-1]$')\n", "\n", - "peaks, _ = scipy.signal.find_peaks(mag, height=.1, distance=1/T)\n", - "assert peaks.any()\n", - "\n", - "peak_data = sorted([ (-mag[idx], xf[idx]) for idx in peaks ])\n", - "largest_peak_f = peak_data[0][1]\n", - "print(f'Largest peak at {largest_peak_f:.2f} Hz / {largest_peak_f * 60:.0f} rpm')\n", - "for i in range(1,4):\n", - " mix1 = i*sampling_rate - largest_peak_f\n", - " mix2 = i*sampling_rate + largest_peak_f\n", - " print(f'Mixing product {2*i-1} at {mix1:.2f} Hz / {mix1 * 60:.0f} rpm')\n", - " print(f'Mixing product {2*i} at {mix2:.2f} Hz / {mix2 * 60:.0f} rpm')\n", - "\n", - "ax.axvline(largest_peak_f, color='orange')" + "ax.legend()" ] }, { "cell_type": "code", - "execution_count": 717, + "execution_count": 957, "metadata": { "scrolled": false }, @@ -3351,10 +3255,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "interval: -0.06357678079289661\n", - "scores: [0.0005478345240435783, 0.003917745560629984, 0.00382343194410793, 0.006241784699377939, 0.0074863442856081385, 0.008964752044997151, 0.008813512329706083, 0.008780553883453538, 0.008530974440876703]\n", - "argmin: 1\n", - "Average speed of rotation: 15.73 Hz / 944 rpm\n" + "Loading run #33 with 3178 packets total, 125 distinct over 99.94346904754639s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 35 ... 161\n" ] }, { @@ -4318,7 +4222,2043 @@ { "data": { "text/html": [ - "" + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Largest peak at 4.33 Hz / 260 rpm\n", + "Mixing product 1 at 5.67 Hz / 340 rpm\n", + "Mixing product 2 at 14.33 Hz / 860 rpm\n", + "Mixing product 3 at 15.67 Hz / 940 rpm\n", + "Mixing product 4 at 24.33 Hz / 1460 rpm\n", + "Mixing product 5 at 25.67 Hz / 1540 rpm\n", + "Mixing product 6 at 34.33 Hz / 2060 rpm\n" + ] + }, + { + "data": { + "text/plain": [ + "4.3273542600896855" + ] + }, + "execution_count": 957, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def estimate_freq_fft(t, y, interval):\n", + " s_min, s_max = interval\n", + " fft_idx = (t*60 > s_min) & (t*60 < s_max)\n", + "\n", + " N = fft_idx.sum()\n", + " T = 1/sampling_rate\n", + " x = np.linspace(0.0, N*T, N)\n", + " y = y[fft_idx] / mems_lsb_per_g # cut out beginning and that time we tapped the thing\n", + " y *= scipy.signal.windows.blackmanharris(len(y))\n", + " yf = scipy.fftpack.fft(y)\n", + " xf = np.linspace(0.0, 1/(2*T), N//2)\n", + " mag = 2/N * np.abs(yf[:N//2])\n", + "\n", + " fig, ax = plt.subplots()\n", + " ax.grid()\n", + " ax.plot(xf, mag)\n", + " ax.set_ylabel('Magnitude [g]')\n", + " ax.set_xlabel('Frequency [Hz]')\n", + "\n", + " peaks, _ = scipy.signal.find_peaks(mag, height=.1, distance=1/T)\n", + " assert peaks.any()\n", + "\n", + " peak_data = sorted([ (-mag[idx], xf[idx]) for idx in peaks ])\n", + " largest_peak_f = peak_data[0][1]\n", + " print(f'Largest peak at {largest_peak_f:.2f} Hz / {largest_peak_f * 60:.0f} rpm')\n", + " for i in range(1,4):\n", + " mix1 = i*sampling_rate - largest_peak_f\n", + " mix2 = i*sampling_rate + largest_peak_f\n", + " print(f'Mixing product {2*i-1} at {mix1:.2f} Hz / {mix1 * 60:.0f} rpm')\n", + " print(f'Mixing product {2*i} at {mix2:.2f} Hz / {mix2 * 60:.0f} rpm')\n", + "\n", + " ax.axvline(largest_peak_f, color='orange')\n", + " return largest_peak_f\n", + " \n", + "t, y, packet_delays, packet_times = load_run(33, plot=False)\n", + "estimate_freq_fft(t, y, (30, 75))" + ] + }, + { + "cell_type": "code", + "execution_count": 958, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading run #33 with 3178 packets total, 125 distinct over 99.94346904754639s\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\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(\n", + " '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", + "\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 = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(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 (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.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 = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\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 toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\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", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\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", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 35 ... 161\n" + ] + } + ], + "source": [ + "load_run(33, plot=True);" + ] + }, + { + "cell_type": "code", + "execution_count": 959, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\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(\n", + " '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", + "\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 = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(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 (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.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 = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\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 toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\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", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\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", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" ], "text/plain": [ "" @@ -4330,21 +6270,1159 @@ { "data": { "text/plain": [ - "[]" + "[]" ] }, - "execution_count": 717, + "execution_count": 959, "metadata": {}, "output_type": "execute_result" } ], + "source": [ + "fig, ax = plt.subplots()\n", + "x = np.linspace(0, 1, 100000)\n", + "y = 1 - (1 + np.tanh((x - 0.05)/0.05 * np.pi))/2\n", + "ax.plot(x, y)" + ] + }, + { + "cell_type": "code", + "execution_count": 1000, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\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(\n", + " '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", + "\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 = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(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 (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.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 = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\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 toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\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", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\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", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " ===== Run 29: =====\n", + "Loading run #29 with 2387 packets total, 206 distinct over 194.01968002319336s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 827 ... 1071\n", + "interval: 0.10719954325502887\n", + "scores: [0.0005558591029225243, 0.007937380545979303, -0.034688942841427636, 0.009128952125600649, 0.013406031099251599, 0.013555935298953624, 0.017543481008845643, 0.017016779430178704, 0.01657709203008989]\n", + "argmin: 3\n", + "min score -0.034688942841427636 0.006114450132147767\n", + "Average speed of rotation: 3.11 Hz / 187 rpm\n", + "\n", + " f_meas = 3.10 Hz; f_est = 3.11 Hz\n", + " Δabs = 0.01 Hz; Δrel = +0.3 %\n", + "\n", + " ===== Run 28: =====\n", + "Loading run #28 with 4918 packets total, 178 distinct over 161.5061011314392s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 611 ... 813\n", + "interval: 0.11439170940941061\n", + "scores: [-0.020534204449181033, -0.03948834332572777, 0.008701765576176747, 0.013951642455491747, 0.014883935285322263, 0.013919464173338898, 0.013597797966206103, 0.01283288815134905, 0.012482191795223349]\n", + "argmin: 2\n", + "min score -0.03948834332572777 0.003962460736427981\n", + "Average speed of rotation: 4.37 Hz / 262 rpm\n", + "\n", + " f_meas = 4.35 Hz; f_est = 4.37 Hz\n", + " Δabs = 0.02 Hz; Δrel = +0.5 %\n", + "\n", + " ===== Run 37: =====\n", + "Loading run #37 with 3070 packets total, 144 distinct over 117.43898510932922s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 326 ... 473\n", + "interval: 0.14865745248471265\n", + "scores: [-0.03883825341506492, 0.005310783081280478, 0.008339889910074037, 0.012661676804576305, 0.012709861511978487, 0.013085760738242736, 0.012322417269530055, 0.01163783853233394, 0.011025320714842681]\n", + "argmin: 1\n", + "min score -0.03883825341506492 -0.19352513539180108\n", + "Average speed of rotation: 6.73 Hz / 404 rpm\n", + "\n", + " f_meas = 6.67 Hz; f_est = 6.73 Hz\n", + " Δabs = 0.06 Hz; Δrel = +0.9 %\n", + "\n", + " ===== Run 30: =====\n", + "Loading run #30 with 2208 packets total, 198 distinct over 166.86816382408142s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 1079 ... 1288\n", + "interval: 0.14157585656540028\n", + "scores: [-0.027368924365523392, 0.0016046408793783343, 6.757317985323834e-05, 0.010033377011136338, 0.014202212278658977, 0.01690264817464519, 0.016602774129523788, 0.016489898206424946, 0.01562597856556622]\n", + "argmin: 1\n", + "min score -0.027368924365523392 -0.1252939758810193\n", + "Average speed of rotation: 7.06 Hz / 424 rpm\n", + "\n", + " f_meas = 7.04 Hz; f_est = 7.06 Hz\n", + " Δabs = 0.02 Hz; Δrel = +0.3 %\n", + "\n", + " ===== Run 31: =====\n", + "Loading run #31 with 2630 packets total, 157 distinct over 126.19650292396545s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 1316 ... 1474\n", + "interval: 0.13094824532824215\n", + "scores: [-0.03704675515805195, 0.006079322222570231, 0.006225731403203891, 0.007881091442639427, 0.010594872980404482, 0.01181994722739787, 0.011899512695214984, 0.012162509479493641, 0.011532909467429079]\n", + "argmin: 1\n", + "min score -0.03704675515805195 -0.19455134377560843\n", + "Average speed of rotation: 7.64 Hz / 458 rpm\n", + "\n", + " f_meas = 7.57 Hz; f_est = 7.64 Hz\n", + " Δabs = 0.07 Hz; Δrel = +0.9 %\n", + "\n", + " ===== Run 34: =====\n", + "Loading run #34 with 2714 packets total, 121 distinct over 97.30304408073425s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 165 ... 287\n", + "interval: 0.11474772130686393\n", + "scores: [-0.030846569646590577, -0.017939041958299002, 0.007585993694854823, 0.013487796252691883, 0.01286325763359304, 0.012345763316803614, 0.011619541945226932, 0.010974011837158768, 0.010396432266781988]\n", + "argmin: 1\n", + "min score -0.030846569646590577 -0.09545980709610284\n", + "Average speed of rotation: 8.71 Hz / 523 rpm\n", + "\n", + " f_meas = 8.62 Hz; f_est = 8.71 Hz\n", + " Δabs = 0.09 Hz; Δrel = +1.1 %\n", + "\n", + " ===== Run 38: =====\n", + "Loading run #38 with 2317 packets total, 122 distinct over 103.3755898475647s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 475 ... 604\n", + "interval: 0.10489854346020394\n", + "scores: [-0.029808429081348096, -0.022247814228522737, 0.007496451366422777, 0.012847680228334563, 0.012120698169116668, 0.01148795434824668, 0.0108151380653151, 0.010214297061686485, 0.009676702479492458]\n", + "argmin: 1\n", + "min score -0.029808429081348096 -0.08125594859438061\n", + "Average speed of rotation: 9.53 Hz / 572 rpm\n", + "\n", + " f_meas = 9.43 Hz; f_est = 9.53 Hz\n", + " Δabs = 0.10 Hz; Δrel = +1.1 %\n", + "\n", + " ===== Run 32: =====\n", + "Loading run #32 with 2643 packets total, 118 distinct over 98.81923913955688s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 3 ... 1583\n", + "interval: 0.09441178519979387\n", + "scores: [-0.013690197808912317, -0.008608471942849465, 0.00431677808670242, 0.007819524539326253, 0.007824114618254871, 0.007696014525605405, 0.007352130944008647, 0.007048798098339794, 0.006677808724742963]\n", + "argmin: 1\n", + "min score -0.013690197808912317 -0.038159149663930074\n", + "Average speed of rotation: 10.59 Hz / 636 rpm\n", + "\n", + " f_meas = 10.40 Hz; f_est = 10.59 Hz\n", + " Δabs = 0.19 Hz; Δrel = +1.8 %\n", + "\n", + " ===== Run 39: =====\n", + "Loading run #39 with 1429 packets total, 129 distinct over 104.99669194221497s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 606 ... 737\n", + "interval: -0.009543333906374466\n", + "scores: [0.00016699475726220442, 0.00015527347855214557, 0.0006494893315487613, 0.00013309155304469622, 0.0008010865133540054, 0.0010090902176491685, 0.001234839163051902, 0.00010351565236809708, 0.0014523737093152392]\n", + "argmin: 8\n", + "min score 0.00010351565236809708 0.0013446078569502677\n", + "Average speed of rotation: 13.10 Hz / 786 rpm\n", + "\n", + " f_meas = 13.10 Hz; f_est = 13.10 Hz\n", + " Δabs = -0.00 Hz; Δrel = -0.0 %\n", + "\n", + " ===== Run 33: =====\n", + "Loading run #33 with 3178 packets total, 125 distinct over 99.94346904754639s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 35 ... 161\n", + "interval: 0.09687252539136751\n", + "scores: [-0.004649921628008072, -0.012634045995330379, 0.0037816457952707543, 0.0017246285208280352, 0.009875890071400597, 0.011254914748918448, 0.012497966748572316, 0.013301204816616601, 0.012601141405215724]\n", + "argmin: 2\n", + "min score -0.012634045995330379 0.013042674363101692\n", + "Average speed of rotation: 5.16 Hz / 310 rpm\n", + "\n", + " f_meas = 16.10 Hz; f_est = 5.16 Hz\n", + " Δabs = -10.94 Hz; Δrel = -67.9 %\n" + ] + } + ], "source": [ "def calc_rspeed_deltas(target_deltas):\n", " target_deltas = np.array(target_deltas)\n", - " target_deltas = target_deltas[0:-int(len(target_deltas)*0.1)]\n", - " target_deltas = target_deltas[target_deltas > 1/30]\n", + " target_deltas = target_deltas[target_deltas > 1/50]\n", + " target_deltas = target_deltas[0:int(len(target_deltas)*0.9)]\n", " def fun(x):\n", - " return np.sqrt(np.mean([ ((val + 0.5*x[0]) % x[0] - 0.5*x[0])**2 for val in target_deltas ]))\n", + " rms = np.sqrt(np.mean([ ((val + 0.5*x[0]) % x[0] - 0.5*x[0])**2 for val in target_deltas ]))\n", + " #matches = (target_deltas > 0.5*x[0]).mean()\n", + " matches1 = ((target_deltas > (0.9*x[0])) & (target_deltas < (1.1*x[0]))).mean()\n", + " matches2 = ((target_deltas > (1.9*x[0])) & (target_deltas < (2.1*x[0]))).mean()\n", + " #matchesh= ((target_deltas > (0.45*x[0])) & (target_deltas < (0.55*x[0]))).mean()\n", + " #matches3 = ((target_deltas > (2.9*x[0])) & (target_deltas < (3.1*x[0]))).mean()\n", + " #penalty = 1 - (1 + np.tanh((matches1 - 0.05)/0.05 * np.pi))/2\n", + " return rms - 0.5*matches1 - 0.25*matches2 #+ 0.125*matches3 #+ 0.1*penalty\n", "\n", " #def accept(x_old, x_new, **kwargs):\n", " # return 1/30 < x_new[0] and x_new[0] < 1\n", @@ -4357,17 +7435,1055 @@ " print('scores:', scores)\n", " argmin = np.argmin(scores)+1\n", " print('argmin:', argmin)\n", + " print('min score', min(scores), fun([interval*0.5]))\n", " interval = np.abs(interval) * argmin\n", " print(f'Average speed of rotation: {1/interval:.2f} Hz / {60 / interval:.0f} rpm')\n", " return interval\n", "\n", - "target_deltas = sorted(np.array(deltas)[speed_idx[:-1]])\n", - "interval = calc_rspeed_deltas(target_deltas)\n", + "def estimate_freq_delay(delays, times, interval, ax=None):\n", + " s_min, s_max = interval\n", + " tsa = np.array(times)\n", + " idx = (tsa > s_min) & (tsa < s_max)\n", + "\n", + " target_deltas = sorted(np.array(delays)[idx[:-1]])\n", + " interval = calc_rspeed_deltas(target_deltas)\n", + " \n", + " if ax is not None:\n", + " ax.grid()\n", + " for i in range(1, int(max(target_deltas)//interval)):\n", + " ax.axhline(i*interval, color='orange')\n", + " ax.plot(target_deltas)\n", + " return 1/interval\n", + " \n", + "run_spans = {\n", + " 28: (4.35, 70, 120),\n", + " 29: (3.10, 70, 120),\n", + " 30: (7.04, 40, 120),\n", + " 31: (7.57, 30, 100),\n", + " 32: (10.4, 40, 90),\n", + " 33: (16.1, 40, 75),\n", + " 34: (8.62, 20, 80),\n", + " 37: (6.67, 20, 90),\n", + " 38: (9.43, 20, 80),\n", + " 39: (13.1, 20, 85),\n", + " }\n", + "\n", + "deltas = []\n", + "fig, axs = plt.subplots(5, 2, figsize=(9.5, 14))\n", + "for (run_id, (freq_meas, t_start, t_end)), ax in zip(sorted(run_spans.items(), key=lambda x: x[1][0]), axs.flatten()):\n", + " \n", + " print()\n", + " print(f' ===== Run {run_id}: =====')\n", + " t, y, packet_delay, packet_times = load_run(run_id, plot=False)\n", + " freq_est = estimate_freq_delay(packet_delay, packet_times, (t_start, t_end), ax=ax)\n", + " ax.set_title(f'Run {run_id} @ $f_{{meas}} = {freq_meas:02.2f} Hz$ / $f_{{est}} = {freq_est:02.2f} Hz$')\n", + " print()\n", + " print(f' f_meas = {freq_meas:02.2f} Hz; f_est = {freq_est:02.2f} Hz')\n", + " delta_abs = freq_est - freq_meas\n", + " delta_rel = freq_est/freq_meas-1\n", + " deltas.append((delta_abs, delta_rel))\n", + " print(f' Δabs = {delta_abs:02.2f} Hz; Δrel = {delta_rel*100:+.1f} %')\n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 995, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\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(\n", + " '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", + "\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 = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(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 (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.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 = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\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 toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\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", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\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", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Text(0.5, 0, '$f\\\\;[Hz]$')" + ] + }, + "execution_count": 995, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ "fig, ax = plt.subplots()\n", + "#freqs = sorted([ f for f, _1, _2 in run_spans.values() ])\n", + "#ax.plot(freqs[:-1], [ delta_abs for delta_abs, _delta_rel in deltas[:-1] ])\n", + "#ax.set_ylabel('$\\Delta_{abs}\\;[Hz]$')\n", + "\n", + "#ax = ax.twinx()\n", "ax.grid()\n", - "for i in range(int(max(target_deltas)//interval)):\n", - " ax.axhline(i*interval, color='orange')\n", - "ax.plot(target_deltas)" + "ax.plot(freqs[:-1], [ delta_rel*100 for _delta_abs, delta_rel in deltas[:-1] ])#, color='orange')\n", + "ax.set_ylabel('$\\Delta_{rel}\\;[\\%]$')\n", + "\n", + "ax.set_xlabel('$f\\;[Hz]$')" ] }, {