The SchemaWidget is a Java GUI component created for the GSI and FAIR control system. It is meant to be used as a flexible and reusable widget for displaying the accelerators and their state, allowing interactions with the accelerators' sections and other schema elements, displaying beam lines along the accelerator etc.
The SchemaWidget can be used to display and interact with arbitrary "schemas"
that are build from sections
. Each subsection is a straight line and a section is defined as a list of subsections. (The widget is therefore not limited to displaying schemas of accelerators, though that is the main planned use case.)
Additional elements can be added in layers
above or below the main schema layer. Layers for labels, beams (general beam lines), buttons, and sections are provided as part of the SchemaWidget API.
The SchemaWidget's view can be configured to allow zooming, rotating and panning
, and base values
for those viewing parameters can be specified to which the view can be easily reset.
As of 01/2017:
Internally, the SchemaWidget uses two coordinate systems:
- The "schema coordinates": The coordinate system in which the contents of the SchemaWidget are provided. The x values increase to the right, the y values increase downwards (!).
- The "screen coordinates": The pixel coordinates of a point on the screen. The x values increase to the right, the y values increase downwards. When the schema is rendered onto the screen, each screen point describes a schema point.
When the SchemaWidget API takes points or other information from the user, the documentation should note in which coordinate system the data is required to be.
When is the schema coordinate system relevant for API users?
API users place content in the SchemaWidget using the schema coordinate system. They may also want to center the widget's view on a specific point in schema coordinates ("schema point"), etc.
When is the screen coordinate system relevant for API users?
API users may for example want to zoom the SchemaWidget to a specific point in screen coordinates ("screen point"), usually the position of the mouse cursor. However, they can access that behaviour by simply using the pre-defined ViewManipulationSchemaMouseListener.
Otherwise, the screen coordinate system should not be relevant to API users, as the picking of nodes in the SchemaWidget is handled by JavaFX.
If API users should feel the need to convert between the schema coordinate system and the screen coordinate system directly, they can access relevant methods via the ScreenSchemaCoordinateConverter interface in the view package that is implemented by the SchemaWidget. When using SchemaMouseListeners to listen for mouse interactions with the widget, the listeners receive JavaFX mouse events that contain the screen coordinates of each mouse event. The associated schema coordinates are provided to the listeners simultaneously.
see SchemaWidget Accelerator Schema
The class SchemaWidget is the central access point to all widget functionalities. It is also a JavaFX node.
The application embedding the SchemaWidget therefore creates an instance of SchemaWidget and adds it to the JavaFX Scene Graph, or references SchemaWidget in an JavaFX FXML file.
The widget internally creates one view module that provides the view manipulation methods, that is, the zooming, panning and rotation of the widget's contents. The SchemaWidget implements the necessary interfaces for accessing those view manipulation methods.
The widget also manages a list of SchemaLayers which contain the displayed contents of the widget. A central internal SectionSchemaLayer that is always contained in the layer list allows the SchemaWidget to display sections out of the box. The SchemaWidget implements the necessary interfaces for defining sections and interacting with them. Other layers (including additional SectionSchemaLayers) can be added above or below the central layer by the API user.
Layers are represented by the SchemaLayer interface. The basic implementation SchemaLayerImpl provides some common functionality, and it is suggested that new layer implementations extend the SchemaLayerImpl class.
Layers for labels, beams (general beam lines), buttons, and sections are provided as part of the SchemaWidget API.
Layers that only display some information, but do not support mouse interactions with their elements, should set their mouse transparency to true using the corresponding method of SchemaLayer(Impl).
Internally, the SchemaWidget informs the layers added by API users about any changes to the scale or rotation of the SchemaWidget's view. This allows the layers to adapt the size or orientation of their contents accordingly. For example, the LabelSchemaLayer can be configured to make sure its contents stay horizontal even when the SchemaWidget's view is rotated.
The SectionSchemaLayer is a subclass of SchemaLayerImpl and displays sections. Sections are mouse-interactive (can react to the mouse entering, exiting and clicking them) and can be focused and selected.
Please note that the picture above is simplified. Internally, the SectionSchemaLayer delegates the handling of various issues (for example the selection of sections or handling mouse interactions) to internal helper classes. These implementation details were left out as only the general idea of how this layer works is described here.
Section Data Model
The sections are defined with data classes residing in the Maven artifact lsa-domain-gsi
: SchemaSections are modeled as consisting of one or more SchemaSubsections, with a subsection being a straight line going from a start to an end point. Consecutive subsections often end and start on the same point, appearing as connected, but this is not necessary: Gaps between subsections belonging to a section are possible. Sections contained in the same model each must have a unique id and name.
The sections are stored in a SectionModel that provides methods for retrieving sections.
The SectionModel interface specifies that each model a SectionSchemaLayer receives is immutable and does not change over its lifetime (which means that no sections are altered, added or removed from the model once it has been given to a SectionSchemaLayer or the SchemaWidget). If that contract is broken, the results are unspecified. SectionModel data should therefore always be immutable.
Each SectionSchemaLayer has one SectionModel. It is possible to replace the model at any time. This will reset all the rendering information (see below) for the sections of the previous model and create new rendering information for the sections of the new model.
In GSI terms, a section could be mapped for example to an LSA accelerator zone.
For each section (identified by a unique id) the SectionSchemaLayer stores rendering information: for example, if the section should be rendered as focused and/or selected, if a custom renderer has been set for a section, the position of the section's label, the JavaFX objects currently being used to visualize the section, etc.. The wrapper object bundling this information is called SectionRenderingInfo and only used internally.
SectionRenderers create the components making up the visualization of a section, including its label and tooltip. The renderer also receives JavaFX properties containing information that may affect the rendering, for example about the selection or focus state of a section, the position of the section's label, or the scale and rotation of the SchemaWidget's view. The renderer is responsible for applying JavaFX bindings or listeners to those properties to automatically adapt the visualization of the section to those states.
Note: Watch out for memory leaks! A non-weak listener on a property will remain in memory even when the section visualization it is supposed to update has already been garbage collected.
SectionRenderer is an interface, allowing for different implementations creating different visualizations of sections. A basic implementation of this interface called SectionRendererImpl is provided with the API and offers various rendering options.
The SectionSchemaLayer has a default SectionRenderer that is used to render all sections, except for those that have a custom renderer set for them that will be used instead.
The ButtonSchemaLayer is a subclass of SchemaLayerImpl and can display various kinds of JavaFX buttons that extend ButtonBase
The layer makes sure its buttons stay horizontal even when the SchemaWidget's view is rotated to keep the button text legible. The layer can be configured to keep the buttons at a specified visual size even when the SchemaWidget's scaled.
The LabelSchemaLayer is a subclass of SchemaLayerImpl and can display non-interactive textual labels.
The layer can be configured to keep the labels at a specified visual size even when the SchemaWidget's scaled.
The BeamSchemaLayer is a subclass of SchemaLayerImpl and displays non-interactive beams: paths travelling along sections.
The beams are modelled as follows:
The BeamSchemaLayer displays ColoredBeams that consist of a non-null color and a non-null SchemaBeam. A SchemaBeam contains one or more PartialSchemaBeam in a specified order. A PartialSchemaBeam contains one or more SchemaSections in a specified order.It is important to note that the sections within a PartialSchemaBeam will be visually connected, but the PartialSchemaBeams within a SchemaBeam will not be connected. (This is important when imaging an accelerator ring with its injection and extraction lines as three partial beams: one leading to a ring of sections, a ring of sections, and one leading away from the ring. While the sections within each partial beam should be visually connected, a visual connection between the partial beams would be problematic in case of the ring: It has the same start and end point, and connecting that point with the end of the injection partial beam or the start of the extraction partial beam would probably lead to ugly lines crossing through the ring.)
Mapping the concept of SchemaBeams to GSI terms: PartialSchemaBeams most closely resemble OperDB "Strahlwege", while SchemaBeams resemble OperDB "Strahlwegketten". It is more difficult to map them to LSA concepts: PartialSchemaBeams could be particle transfers that contain multiple accelerator zones, but those may not exist. It is possible that creating SchemaBeams from LSA data objects requires some conversions/special solutions.
The SchemaBeams, their PartialSchemaBeams and the ColoredBeams are assumed to be immutable. If that contract is broken, the results are unspecified. SchemaBeam data should therefore always be immutable.
BeamRenderers create the components making up the visualization of a ColoredBeam. They receive information about the position of a beam among the displayed beams that contain a certain section via a BeamPositionForSectionProvider.
!BeamRenderer is an interface, so different implementations can provide different visualizations of beams if desired.
The API provides a renderer implementation that visualizes beams as colored lines that are parallelly stacked in the order in which they were displayed when more than one beam travels along the same section.
All ColoredBeams of one BeamSchemaLayer are rendered by the same BeamRenderer.
Base Values & Zooming, Panning, Rotating
The schema displayed by the SchemaWidget can be zoomed, panned and rotated to allow for an optimal visualization.
Basic options for the widget's viewing parameters can be specified via the SchemaWidgetViewConfig interface implemented by the SchemaWidget: "base values" (values the view uses for displaying the schema whenever it is reset (for example when it is first started)), an allowed range for the zoom level, and the zoom factor.
- base rotation: Initial rotation of the schema in degrees.
- base zoom level: Initial zoom level of the schema. May be null, in which case the view tries to fully display all the widget contents as large as possible within the available screen space (if possible, as the allowed zoom level range may prevent an ideal scaling).
- base center schema point: Point in schema coordinates on which the widget should be centered.
- base viewport: A rectangular (though not necessarily axis parallel) area in schema coordinates that should be fitted entirely into the available screen space (if possible, as the allowed zoom level range may prevent an ideal scaling). If this option is set, base zoom level and base center schema point are ignored as the canvas determines the ideal zoom level and center point from the requested base viewport. Use this option to make sure that the widget always displays the same area.
- min and max zoom level: The minimum and maximum allowed values for the zoom level.
- zoom factor: By what factor should the schema be scaled when the zoom level is increased by 1? (Typically, 2 or sqrt(2) are sensible values.)
Relevant methods to interactively manipulate the viewing parameters by zooming, panning and rotating, are provided via the SchemaWidgetViewManipulation interface implemented by the SchemaWidget. To allow the manipulation of the widget's view by mouse interactions (for example zooming the widget when scrolling), a pre-defined ViewManipulationSchemaMouseListener can be registered on the widget.
To undo all interactive manipulations, the widget provides a method to reset the view to the base values specified via the config interface mentioned above.
The widget view ignores all calls to the manipulation methods before it has painted itself for the first time. There is a small configurable delay before the widget tries to do that first painting of itself to allow the GUI framework to finish resizing the widget node.
Zoom Factor, Zoom Level, Scale
- zoom factor: By what factor should the schema be scaled when the zoom level is increased by 1? (typically, 2 or sqrt(2) are sensible values)
- zoom level: Describes how much the canvas was zoomed.
- scale: computed from the zoom factor and the scale - SchemaWidgetViewImpl implements it as
scale = (zoomFactor^zoomLevel) / SCALE_DIVISOR with
SCALE_DIVISOR being 10.
The same scale can be achieved by different combinations of zoom factor and zoom level and will always represent the same magnitude of scaling.
The same zoom level can describe different magnitudes of scaling, depending on the zoom factor that is used.
The reason for using SCALE_DIVISOR is that we can easily get a scale progression like 0.1, 0.2, 0.4, 0.8, etc.
Like most GUI frameworks including JavaFX, any methods defined in the SchemaWidget API that influence the UI state should be accessed from only one thread, the JavaFX Application Thread
. The SchemaWidget API is therefore explicitly not
thread-safe. Accessing methods that change UI state from any other thread than the JavaFX Application Thread may lead to exceptions or unspecified behaviour. However, methods that do not change UI state, like getters, are generally safe
to call from other threads.
The constructor of the SchemaWidget can be called from any thread.
To keep the widget always working on just one thread, but allow the API client to provide the widget with data from databases (possibly long running operation) etc., the API client should retrieve the data and create a data structure (for example a SectionModel) on a background thread and then provide the resulting, now immutable model to the classes of the SchemaWidget API on the JavaFX Application Thread.
see Trouble Shooting
see Usage Examples
The SchemaWidget should usually run with only minimal stuttering during drags or rotations. Making the widget very large or displaying very many schema elements may lead to increased stuttering.
One situation that may cause the UI to lag/hang for a moment is changing the renderer of many sections simultaneously, for example by changing the default section renderer for all displayed sections. This is because many JavaFX objects are created completely anew by the new renderer. If this causes unacceptable lags/hangs, it might be preferable to implement a custom section renderer that is never replaced, but provides the means to switch between different visualizations of sections: Changing the configuration of existing objects is much faster than replacing these objects with new ones.
The first concept for the SchemaWidget and a first prototype was developed by Anneke Walter during her bachelor thesis (January - July 2013). The thesis
of the thesis are linked here for the sake of completeness.
Please note that some concepts described in the thesis have been changed/improved later, so the thesis does
not reflect the current status of the widget API's concept!!
- 20 Feb 2014