Dashboard.rst 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. TkInter Dashboard with Editable Values
  2. ======================================
  3. Often, users would like to have interaction with certain values during the simulation.
  4. This reduces the need to run multiple simulations in which only small values need to
  5. be changed. Seeing as the simulator provides a way of interacting with TkInter, building
  6. such a dashboard is quite easy.
  7. Example Model
  8. -------------
  9. The normal :doc:`SinGen` will be slightly extended to comply to the following (more generic)
  10. formula:
  11. .. math::
  12. y(t) = A \cdot \sin(B \cdot t)
  13. The CBD model therefore becomes:
  14. .. code-block:: python
  15. from CBD.Core import CBD
  16. from CBD.lib.std import *
  17. from CBD.lib.endpoints import SignalCollectorBlock
  18. class SinGen(CBD):
  19. def __init__(self, block_name):
  20. CBD.__init__(self, block_name, input_ports=[], output_ports=[])
  21. # Create the Blocks
  22. self.addBlock(TimeBlock("time"))
  23. self.addBlock(GenericBlock("sin", block_operator=("sin")))
  24. self.addBlock(ConstantBlock("A", 1.0))
  25. self.addBlock(ConstantBlock("B", 1.0))
  26. self.addBlock(ProductBlock("amp"))
  27. self.addBlock(ProductBlock("per"))
  28. # Using a buffer, the memory won't be flooded
  29. self.addBlock(SignalCollectorBlock("plot", buffer_size=500))
  30. # Create the Connections
  31. self.addConnection("B", "per")
  32. self.addConnection("time", "per")
  33. self.addConnection("per", "sin")
  34. self.addConnection("A", "amp")
  35. self.addConnection("sin", "amp")
  36. self.addConnection("amp", "plot")
  37. The Dashboard
  38. -------------
  39. As per :doc:`LivePlot`, a TkInter window is being created and a :class:`CBD.realtime.plotting.PlotManager`
  40. is assigned to display the plot. Notice there is an additional callback to ensure the y-axis will remain
  41. in the range of :code:`[-1.0, 1.0]` if the values are smaller, but the axis may grow to a larger scope if
  42. needs be.
  43. .. code-block:: python
  44. from CBD.realtime.plotting import PlotManager, LinePlot, follow
  45. import matplotlib.pyplot as plt
  46. import tkinter as tk
  47. from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
  48. fig = plt.figure(figsize=(15, 5), dpi=100)
  49. ax = fig.add_subplot(111)
  50. ax.set_ylim((-1, 1))
  51. cbd = SinGen("SinGen")
  52. root = tk.Tk()
  53. canvas = FigureCanvasTkAgg(fig, master=root) # A Tk DrawingArea
  54. canvas.draw()
  55. canvas.get_tk_widget().grid(column=1, row=1)
  56. manager = PlotManager()
  57. manager.register("sin", cbd.find("plot")[0], (fig, ax), LinePlot())
  58. manager.connect('sin', 'update',
  59. lambda d, axis=ax: axis.set_xlim(follow(d[0], 10.0, lower_bound=0.0)))
  60. manager.connect('sin', 'update',
  61. lambda d, axis=ax: axis.set_ylim(follow(d[1], lower_lim=-1.0, upper_lim=1.0)))
  62. Next, let's provide a way of obtaining user information. We will use two TkInter :code:`Scale` objects to provide easy
  63. input fields for the variables :code:`A` (the amplitude) and :code:`B` (proportional to the period). Additionally, a
  64. :code:`Label` will show the current equation that is being plotted as additional information. The :func:`set_amplitude`
  65. and :func:`set_period` functions make use of the ability of setting a :class:`CBD.lib.std.ConstantBlock`'s value
  66. during runtime. Take a look at the corresponding documentations for more information.
  67. .. danger::
  68. Do not alter the window closing protocol of the :code:`tkinter` root! It is automatically altered to ensure all
  69. threads are closed.
  70. .. code-block:: python
  71. label = tk.Label(root, text="y = 1.00 * sin(1.00 * t)")
  72. label.grid(column=1, row=2)
  73. def set_amplitude(val):
  74. cbd.find("A")[0].setValue(float(val))
  75. update_label()
  76. def set_period(val):
  77. cbd.find("B")[0].setValue(float(val))
  78. update_label()
  79. def update_label():
  80. label["text"] = "y = {:.2f} * sin({:.2f} * t)".format(cbd.find("A")[0].getValue(),
  81. cbd.find("B")[0].getValue())
  82. amplitude = tk.Scale(root, label="Amplitude", length=1200, orient=tk.HORIZONTAL, from_=0, to=5,
  83. resolution=0.1, command=set_amplitude)
  84. amplitude.set(1.0)
  85. amplitude.grid(column=1, row=3)
  86. period = tk.Scale(root, label="Period", length=1200, orient=tk.HORIZONTAL, from_=0, to=5,
  87. resolution=0.1, command=set_period)
  88. period.set(1.0)
  89. period.grid(column=1, row=4)
  90. And that's it! All that is left to do is to run the simulation and see how the plot interacts to user input.
  91. Notice how no time constraint is set on the simulation. This will ensure there can be plenty of experimentation
  92. by the user. Also, the :class:`CBD.lib.endpoints.SignalCollectorBlock` that is used was given a buffer size of
  93. 500 datapoints. This prevents the memory being flooded with data while this simulation is running (for an infinite
  94. time).
  95. .. code-block:: python
  96. from CBD.simulator import Simulator
  97. sim = Simulator(cbd)
  98. sim.setRealTime()
  99. sim.setRealTimePlatformTk(root)
  100. sim.setDeltaT(0.1)
  101. sim.run()
  102. root.mainloop()
  103. While changing the values (especially the period), a lot of noice will appear. This is caused by the fact that
  104. every update to a slider alters a result from another function that may be at a completely different location.
  105. Lower the resolution for the scales to minimize this effect.
  106. .. figure:: ../_figures/sin-dashboard.png