diff --git a/README.md b/README.md index c0eb937d..873d42c4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [**Overview paper**](https://joss.theoj.org/papers/10.21105/joss.02498) | [**Reinforcement learning paper**](https://arxiv.org/abs/1910.09434) +| [**GEM control paper**](https://ieeexplore.ieee.org/document/10239044) | [**Quickstart**](#getting-started) | [**Install guide**](#installation) | [**Reference docs**](https://upb-lea.github.io/gym-electric-motor/) @@ -18,7 +19,8 @@ ## Overview The gym-electric-motor (GEM) package is a Python toolbox for the simulation and control of various electric motors. -It is built upon [Faram Gymnasium Environments](https://gym.openai.com/), and, therefore, can be used for both, classical control simulation and [reinforcement learning](https://github.com/upb-lea/reinforcement_learning_course_materials) experiments. It allows you to construct a typical drive train with the usual building blocks, i.e., supply voltages, converters, electric motors and load models, and obtain not only a closed-loop simulation of this physical structure, but also a rich interface for plugging in any decision making algorithm, from linear feedback control to [Deep Deterministic Policy Gradient](https://spinningup.openai.com/en/latest/algorithms/ddpg.html) agents. +It is built upon [Faram Gymnasium Environments](https://gymnasium.farama.org/), and, therefore, can be used for both, classical control simulation and [reinforcement learning](https://github.com/upb-lea/reinforcement_learning_course_materials) experiments. It allows you to construct a typical drive train with the usual building blocks, i.e., supply voltages, converters, electric motors and load models, and obtain not only a closed-loop simulation of this physical structure, but also a rich interface for plugging in any decision making algorithm, from linear feedback control to [Deep Deterministic Policy Gradient](https://spinningup.openai.com/en/latest/algorithms/ddpg.html) agents. +Since v2.0.1, gym-electric-motor control is integrated in gym-electric-motor and provide automated classical control structures for the environment of the Toolbox. ## Getting Started An easy way to get started with GEM is by playing around with the following interactive notebooks in Google Colaboratory. Most important features of GEM as well as application demonstrations are showcased, and give a kickstart for engineers in industry and academia. @@ -116,6 +118,19 @@ A white paper for the utilization of this framework within reinforcement learnin doi={10.1109/TNNLS.2020.3029573}} ``` +A white paper for the classical control approaches of gym-electric-motor control is available at [IEEE-Xplore](https://ieeexplore.ieee.org/document/10239044). Please use the following BibTeX entry for citing it: +``` +@INPROCEEDINGS{10239044, + author={Book, Felix and Traue, Arne and Schenke, Maximilian and Haucke-Korber, Barnabas and Wallscheid, Oliver}, + booktitle={2023 IEEE International Electric Machines & Drives Conference (IEMDC)}, + title={Gym-Electric-Motor (GEM) Control: An Automated Open-Source Controller Design Suite for Drives}, + year={2023}, + volume={}, + number={}, + pages={1-7}, + doi={10.1109/IEMDC55163.2023.10239044}} +``` + ### Running Unit Tests with Pytest To run the unit tests ''pytest'' is required. All tests can be found in the ''tests'' folder. diff --git a/examples/classic_controllers/classic_controllers.py b/examples/classic_controllers/classic_controllers.py index 2f39c4fb..9ade5e6b 100644 --- a/examples/classic_controllers/classic_controllers.py +++ b/examples/classic_controllers/classic_controllers.py @@ -136,7 +136,7 @@ def reference_states(environment, **controller_kwargs): def find_controller_type(environment, stages, **controller_kwargs): _stages = stages - if isinstance(environment.physical_system.unwrapped, DcMotorSystem): + if isinstance(environment.unwrapped.physical_system, DcMotorSystem): if type(stages) is list: if len(stages) > 1: if type(stages[0]) is list: @@ -413,24 +413,24 @@ def automated_gain(environment, stages, controller_type, _controllers, **control _controllers[stages_a[i][0]["controller_type"]][1] == ContinuousController ): # had to add [0] to make dict in list acessable if i == 0: - p_gain = mp["l"] / (environment.physical_system.tau * a) / u_a_lim * i_a_lim - i_gain = p_gain / (environment.physical_system.tau * a**2) + p_gain = mp["l"] / (environment.unwrapped.physical_system.tau * a) / u_a_lim * i_a_lim + i_gain = p_gain / (environment.unwrapped.physical_system.tau * a**2) if _controllers[stages_a[i][0]["controller_type"]][2] == PIDController: - d_gain = p_gain * environment.physical_system.tau + d_gain = p_gain * environment.unwrapped.physical_system.tau stages_a[i][0]["d_gain"] = stages_a[i][0].get("d_gain", d_gain) elif i == 1: - t_n = environment.physical_system.tau * a**2 + t_n = environment.unwrapped.physical_system.tau * a**2 p_gain = ( - environment.physical_system.mechanical_load.j_total + environment.unwrapped.physical_system.mechanical_load.j_total / (a * t_n) / i_a_lim * omega_lim ) i_gain = p_gain / (a * t_n) if _controllers[stages_a[i][0]["controller_type"]][2] == PIDController: - d_gain = p_gain * environment.physical_system.tau + d_gain = p_gain * environment.unwrapped.physical_system.tau stages_a[i][0]["d_gain"] = stages_a[i][0].get("d_gain", d_gain) stages_a[i][0]["p_gain"] = stages_a[i][0].get("p_gain", p_gain) # ? diff --git a/examples/classic_controllers/classic_controllers_dc_motor_example.py b/examples/classic_controllers/classic_controllers_dc_motor_example.py index 0a5a72ea..85a0ddfc 100644 --- a/examples/classic_controllers/classic_controllers_dc_motor_example.py +++ b/examples/classic_controllers/classic_controllers_dc_motor_example.py @@ -2,6 +2,7 @@ from externally_referenced_state_plot import ExternallyReferencedStatePlot import gym_electric_motor as gem +from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType from gym_electric_motor.visualization import MotorDashboard from gym_electric_motor.visualization.render_modes import RenderMode @@ -20,28 +21,27 @@ 'Finite' Discrete Action Space """ + """ motor_type = "PermExDc" control_type = "TC" action_type = "Cont" + """ - motor = action_type + "-" + control_type + "-" + motor_type + "-v0" - if motor_type in ["PermExDc", "SeriesDc"]: - states = ["omega", "torque", "i", "u"] - elif motor_type == "ShuntDc": - states = ["omega", "torque", "i_a", "i_e", "u"] - elif motor_type == "ExtExDc": - states = ["omega", "torque", "i_a", "i_e", "u_a", "u_e"] - else: - raise KeyError(motor_type + " is not available") + motor = Motor( + MotorType.PermanentlyExcitedDcMotor, + ControlType.TorqueControl, + ActionType.Continuous, + ) + # definition of the plotted variables - external_ref_plots = [ExternallyReferencedStatePlot(state) for state in states] + external_ref_plots = [ExternallyReferencedStatePlot(state) for state in motor.states()] motor_dashboard = MotorDashboard(additional_plots=external_ref_plots, render_mode=RenderMode.Figure) # initialize the gym-electric-motor environment env = gem.make( - motor, + motor.env_id(), visualization=motor_dashboard, ) """ diff --git a/examples/classic_controllers/classic_controllers_ind_motor_example.py b/examples/classic_controllers/classic_controllers_ind_motor_example.py index 25f36d42..074a1f21 100644 --- a/examples/classic_controllers/classic_controllers_ind_motor_example.py +++ b/examples/classic_controllers/classic_controllers_ind_motor_example.py @@ -1,7 +1,9 @@ +from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType from classic_controllers import Controller from externally_referenced_state_plot import ExternallyReferencedStatePlot from external_plot import ExternalPlot import gym_electric_motor as gem + from gym_electric_motor.visualization import MotorDashboard from gym_electric_motor.physical_system_wrappers import FluxObserver import numpy as np @@ -16,27 +18,33 @@ action_type: 'AbcCont' Continuous Action Space """ - + """ motor_type = "SCIM" control_type = "TC" action_type = "Cont" + """ - env_id = action_type + "-" + control_type + "-" + motor_type + "-v0" + motor = Motor( + MotorType.SquirrelCageInductionMotor, + ControlType.TorqueControl, + ActionType.Continuous, + ) # definition of the plotted variables states = ["omega", "torque", "i_sd", "i_sq", "u_sd", "u_sq"] - external_ref_plots = [ExternallyReferencedStatePlot(state) for state in states] + external_ref_plots = [ExternallyReferencedStatePlot(state) for state in motor.states()] external_plot = [ - ExternalPlot(referenced=control_type != "CC"), + ExternalPlot(referenced= ControlType.TorqueControl != "CC"), ExternalPlot(min=-np.pi, max=np.pi), ] external_ref_plots += external_plot + motor_dashboard = MotorDashboard(state_plots=("omega", "psi_abs", "psi_angle")) # initialize the gym-electric-motor environment env = gem.make( - env_id, + motor.env_id(), physical_system_wrappers=(FluxObserver(),), - visualization=MotorDashboard(state_plots=("omega", "psi_abs", "psi_angle")), + visualization=MotorDashboard(), ) """ @@ -60,4 +68,5 @@ if terminated: env.reset() controller.reset() + motor_dashboard.show_and_hold() env.close() diff --git a/examples/classic_controllers/classic_controllers_synch_motor_example.py b/examples/classic_controllers/classic_controllers_synch_motor_example.py index ff8ad73f..4a367e0a 100644 --- a/examples/classic_controllers/classic_controllers_synch_motor_example.py +++ b/examples/classic_controllers/classic_controllers_synch_motor_example.py @@ -1,3 +1,4 @@ +from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType from classic_controllers import Controller from externally_referenced_state_plot import ExternallyReferencedStatePlot import gym_electric_motor as gem @@ -16,22 +17,28 @@ action_type: 'Cont' Continuous Action Space in ABC-Coordinates 'Finite' Discrete Action Space """ - + + """ motor_type = "PMSM" control_type = "TC" action_type = "Cont" + """ - env_id = action_type + "-" + control_type + "-" + motor_type + "-v0" + motor = Motor( + MotorType.PermanentMagnetSynchronousMotor, + ControlType.TorqueControl, + ActionType.Continuous + ) # definition of the plotted variables external_ref_plots = [ - ExternallyReferencedStatePlot(state) - for state in ["omega", "torque", "i_sd", "i_sq", "u_sd", "u_sq"] + ExternallyReferencedStatePlot(state) for state in ["omega", "torque", "i_sd", "i_sq", "u_sd", "u_sq"] ] + motor_dashboard = MotorDashboard(additional_plots=external_ref_plots) # initialize the gym-electric-motor environment env = gem.make( - env_id, visualization=MotorDashboard(additional_plots=external_ref_plots) + motor.env_id(), visualization=motor_dashboard ) """ @@ -64,5 +71,6 @@ if terminated: env.reset() controller.reset() - + + motor_dashboard.show_and_hold() env.close() diff --git a/examples/classic_controllers/custom_classic_controllers_dc_motor_example.py b/examples/classic_controllers/custom_classic_controllers_dc_motor_example.py index d22a6688..af6b414f 100644 --- a/examples/classic_controllers/custom_classic_controllers_dc_motor_example.py +++ b/examples/classic_controllers/custom_classic_controllers_dc_motor_example.py @@ -1,7 +1,9 @@ from classic_controllers import Controller from externally_referenced_state_plot import ExternallyReferencedStatePlot import gym_electric_motor as gem +from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType from gym_electric_motor.visualization import MotorDashboard +from gym_electric_motor.visualization.render_modes import RenderMode if __name__ == "__main__": """ @@ -19,29 +21,27 @@ """ # following manual controller design addresses an ExtExDc. Other motor types require different controller stages + """ motor_type = "ExtExDc" control_type = "CC" action_type = "Cont" + """ - motor = action_type + "-" + control_type + "-" + motor_type + "-v0" + motor = Motor(MotorType.ExternallyExcitedDcMotor, + ControlType.CurrentControl, + ActionType.Continuous,) - if motor_type in ["PermExDc", "SeriesDc"]: - states = ["omega", "torque", "i", "u"] - elif motor_type == "ShuntDc": - states = ["omega", "torque", "i_a", "i_e", "u"] - elif motor_type == "ExtExDc": - states = ["omega", "torque", "i_a", "i_e", "u_a", "u_e"] - else: - raise KeyError(motor_type + " is not available") # definition of the plotted variables - external_ref_plots = [ExternallyReferencedStatePlot(state) for state in states] + external_ref_plots = [ExternallyReferencedStatePlot(state) for state in motor.states()] + + motor_dashboard = MotorDashboard(additional_plots=external_ref_plots, render_mode=RenderMode.Figure) # initialize the gym-electric-motor environment env = gem.make( - motor, - visualization=MotorDashboard(additional_plots=external_ref_plots), - render_mode="figure", + motor.env_id(), + visualization=motor_dashboard + ) """ @@ -92,4 +92,5 @@ env.reset() controller.reset() + motor_dashboard.show_and_hold() env.close() diff --git a/examples/classic_controllers/custom_classic_controllers_ind_motor_example.py b/examples/classic_controllers/custom_classic_controllers_ind_motor_example.py index 50a4c3a3..135403f5 100644 --- a/examples/classic_controllers/custom_classic_controllers_ind_motor_example.py +++ b/examples/classic_controllers/custom_classic_controllers_ind_motor_example.py @@ -1,3 +1,4 @@ +from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType from classic_controllers import Controller from externally_referenced_state_plot import ExternallyReferencedStatePlot from external_plot import ExternalPlot @@ -15,25 +16,34 @@ action_type: 'Cont' Continuous Action Space """ - + + """ motor_type = "SCIM" control_type = "SC" action_type = "Cont" + """ - env_id = action_type + "-" + control_type + "-" + motor_type + "-v0" + motor = Motor( + MotorType.SquirrelCageInductionMotor, + ControlType.SpeedControl, + ActionType.Continuous, + ) # definition of the plotted variables states = ["omega", "torque", "i_sd", "i_sq", "u_sd", "u_sq"] external_ref_plots = [ExternallyReferencedStatePlot(state) for state in states] external_plot = [ - ExternalPlot(referenced=control_type != "CC"), + ExternalPlot(referenced=ControlType.SpeedControl != "CC"), ExternalPlot(min=-np.pi, max=np.pi), ] external_ref_plots += external_plot + motor_dashboard = MotorDashboard(additional_plots=external_ref_plots) + # initialize the gym-electric-motor environment env = gem.make( - env_id, visualization=MotorDashboard(additional_plots=external_ref_plots) + motor.env_id(), + visualization=motor_dashboard ) """ @@ -67,4 +77,6 @@ if terminated: env.reset() controller.reset() + + motor_dashboard.show_and_hold() env.close() diff --git a/examples/classic_controllers/custom_classic_controllers_synch_motor_example.py b/examples/classic_controllers/custom_classic_controllers_synch_motor_example.py index 293d0655..f1332d5b 100644 --- a/examples/classic_controllers/custom_classic_controllers_synch_motor_example.py +++ b/examples/classic_controllers/custom_classic_controllers_synch_motor_example.py @@ -1,3 +1,4 @@ +from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType from classic_controllers import Controller from externally_referenced_state_plot import ExternallyReferencedStatePlot import gym_electric_motor as gem @@ -16,15 +17,18 @@ action_type: 'Cont' Continuous Action Space in ABC-Coordinates 'Finite' Discrete Action Space """ - + """ motor_type = "PMSM" control_type = "SC" action_type = "Cont" + """ - env_id = action_type + "-" + control_type + "-" + motor_type + "-v0" + motor = Motor(MotorType.PermanentMagnetSynchronousMotor, + ControlType.SpeedControl, + ActionType.Continuous) # definition of the motor parameters - psi_p = 0 if motor_type == "SynRM" else 45e-3 + psi_p = 0 if "SynRM" in motor.env_id() else 45e-3 limit_values = dict(omega=12e3 * np.pi / 30, torque=100, i=280, u=320) nominal_values = dict( omega=10e3 * np.pi / 30, torque=95.0, i=240, epsilon=np.pi, u=300 @@ -39,9 +43,10 @@ for state in ["omega", "torque", "i_sd", "i_sq", "u_sd", "u_sq"] ] + motor_dashboard = MotorDashboard(additional_plots=external_ref_plots) # initialize the gym-electric-motor environment env = gem.make( - env_id, + motor.env_id(), visualization=MotorDashboard(additional_plots=external_ref_plots), motor=dict( limit_values=limit_values, @@ -140,5 +145,5 @@ if terminated: env.reset() controller.reset() - + env.close() diff --git a/examples/environment_features/external_speed_profile.py b/examples/environment_features/external_speed_profile.py index a5f0dc72..9e7f43aa 100644 --- a/examples/environment_features/external_speed_profile.py +++ b/examples/environment_features/external_speed_profile.py @@ -2,6 +2,8 @@ import gym_electric_motor as gem from gym_electric_motor import reference_generators as rg from gym_electric_motor.visualization import MotorDashboard +from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType +from gym_electric_motor.physical_systems.solvers import ScipySolveIvpSolver import time from scipy import signal @@ -67,11 +69,19 @@ # inital value is given by bias of the profile sampling_time = 1e-4 +#define the motor env object +motor = Motor( + motor_type=MotorType.SeriesDc, + control_type=ControlType.CurrentControl, + action_type=ActionType.Continuous, +) + + if __name__ == "__main__": # Create the environment env = gem.make( - "Cont-CC-SeriesDc-v0", - ode_solver="scipy.solve_ivp", + motor.env_id(), + ode_solver=ScipySolveIvpSolver(), tau=sampling_time, reference_generator=const_switch_gen, visualization=MotorDashboard(state_plots=["omega", "i"], reward_plot=True), diff --git a/examples/environment_features/scim_ideal_grid_simulation.py b/examples/environment_features/scim_ideal_grid_simulation.py index a0688ab9..e3d20c29 100644 --- a/examples/environment_features/scim_ideal_grid_simulation.py +++ b/examples/environment_features/scim_ideal_grid_simulation.py @@ -6,6 +6,8 @@ import numpy as np import gym_electric_motor as gem +from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType +from gym_electric_motor.physical_systems.solvers import ScipyOdeSolver import matplotlib.pyplot as plt @@ -31,17 +33,18 @@ def grid_voltage(t): return grid_voltage +motor = Motor(motor_type=MotorType.SquirrelCageInductionMotor, control_type=ControlType.CurrentControl, action_type=ActionType.Continuous) # Create the environment env = gem.make( # Choose the squirrel cage induction motor (SCIM) with continuous-control-set - "Cont-CC-SCIM-v0", + motor.env_id(), # load=gem.physical_systems.PolynomialStaticLoad( dict(a=0.0, b=0.0, c=0.0, j_load=1e-6) ), # Define the numerical solver for the simulation - ode_solver="scipy.ode", + ode_solver=ScipyOdeSolver(), # Define which state variables are to be monitored concerning limit violations # "()" means, that limit violation will not necessitate an env.reset() constraints=(), @@ -53,7 +56,7 @@ def grid_voltage(t): limits = env.physical_system.limits # reset the environment such that the simulation can be started -(state, reference) = env.reset() +(state, reference),_ = env.reset() # We define these arrays in order to save our simulation results in them # Initial state and initial time are directly inserted diff --git a/examples/environment_features/userdefined_initialization.py b/examples/environment_features/userdefined_initialization.py index 07ddf9ee..f427a7fe 100644 --- a/examples/environment_features/userdefined_initialization.py +++ b/examples/environment_features/userdefined_initialization.py @@ -1,6 +1,8 @@ import gym_electric_motor as gem from gym_electric_motor import reference_generators as rg from gym_electric_motor.visualization import MotorDashboard +from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType +from gym_electric_motor.physical_systems.solvers import ScipySolveIvpSolver import time """ @@ -39,15 +41,17 @@ # initializer for a specific speed load_init = {"states": {"omega": 20}} +motor = Motor(motor_type=MotorType.SeriesDc, control_type=ControlType.CurrentControl, action_type=ActionType.Continuous) + if __name__ == "__main__": env = gem.make( - "Cont-CC-SeriesDc-v0", + motor.env_id(), visualization=MotorDashboard(state_plots=["omega", "i"]), motor=dict( motor_parameter=dict(j_rotor=0.001), motor_initializer=gaussian_init ), load=dict(j_load=0.001, load_initializer=uniform_init), - ode_solver="scipy.solve_ivp", + ode_solver=ScipySolveIvpSolver(), reference_generator=rg.SwitchedReferenceGenerator( sub_generators=[ rg.SinusoidalReferenceGenerator(reference_state="omega"), @@ -63,7 +67,7 @@ cum_rew = 0 for j in range(10): - state, reference = env.reset() + (state, reference), _ = env.reset() # Print the initial states: denorm_state = state * env.limits diff --git a/examples/model_predictive_controllers/pmsm_mpc_dq_current_control.ipynb b/examples/model_predictive_controllers/pmsm_mpc_dq_current_control.ipynb index a7222e84..36dcd2e4 100644 --- a/examples/model_predictive_controllers/pmsm_mpc_dq_current_control.ipynb +++ b/examples/model_predictive_controllers/pmsm_mpc_dq_current_control.ipynb @@ -32,7 +32,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -42,7 +42,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -52,10 +52,12 @@ "from gekko import GEKKO\n", "\n", "import gym_electric_motor as gem\n", + "from gym_electric_motor.envs.motors import ActionType, ControlType, Motor, MotorType\n", "from gym_electric_motor.physical_systems import ConstantSpeedLoad\n", "from gym_electric_motor.reference_generators import MultipleReferenceGenerator, SwitchedReferenceGenerator, \\\n", " TriangularReferenceGenerator, SinusoidalReferenceGenerator, StepReferenceGenerator\n", - "from gym_electric_motor.visualization.motor_dashboard import MotorDashboard" + "from gym_electric_motor.visualization.motor_dashboard import MotorDashboard\n", + "from gym_electric_motor.physical_systems.solvers import ScipySolveIvpSolver" ] }, { @@ -67,7 +69,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -95,7 +97,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -253,7 +255,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -271,7 +273,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -308,46 +310,21 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n fig.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
');\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('