The architecture of the application loosely follows the Model-View-Controller (MVC) software architectural pattern (as shwon below). The difference between the developed architecture and the traditional MVC pattern is the update bypass from the Controller to the View, enabling updating some aspects of the view that are not related to the model.
The UML Diagram displayed above presents the most important aspects of the Model component of the MVC architecture. Each Frame stores its time interval under the form of two timestamps and a reference to an Object and to a Transformation. The object is essentially a set of ObjectStates which represent the geometry (vertices list) of the spatiotemporal phenomenon at a certain timestamp within the frame’s time interval. The Transformation is abstract and conceptually establishes a relationship of inheritance with each of the concrete transformations (e.g., Visualization Prototype rotation) and enables extracting common logic across transformations into the parent class. Furthermore, it allows supporting more transformations in the future without drastically changing the remaining architecture
The application's entry point is located in main.js
. There are three types of entry points:
Loader.LoadFramesDemo
: Loads hardcoded frames from a Javascript file which uses the current API to construct the frames. This entry point is generally used for unit testing. Example:src/js/demos/demo1.js
.Loader.LoadFramesDemoJson
: Loads frames from a JSON file with a similar schema to the one used for server communication. The difference is that this entry point also supports one level of child frames. This entry point is generally used for user testing. Example:src/js/demos/datasets/dataset1.json
.Loader.LoadDataset
: Retrieves an initial set of frames from the object change identification and quantification server. This entry point is used for connecting the application with a server that supplies spatiotemporal events. The dataset whose events are requested is given bySettings.instance.datasetKey
and the server URL can be edited inloader.js
(SERVER_URL
).
-
Create a visual metaphor
The visual metaphors are defined intransformation.js
. To create a new transformation, create a new class that derives from theTransformation
class.class MyNewTransformation extends Transformation { // ... }
The following methods must be implemented:
getName()
: Returns aString
with the name of the transformation.getDetails()
: Returns aString
with a verbose description of the transformation.getOverlayDetails()
: Returns aString
with a condensed description of the transformation to be used in the overlay.setupScene(scene, object)
: Defines the visual metaphor of the transformation. It receives a Three.js scene and an Object (object.js
). When implementing this function, the base class's method must be called (super.setupScene(scene, object)
) before any other logic. Moreover, the methodssetupSceneCamera()
andsetupGrid()
must be called at the end of the visual metaphor logic (in the aforementioned order).
The logic of the new transformation's visual contents must be placed between these blocks and should begin by setting up the object's states. The previously implemented methods can be used towards this goal (setupOnionSkinning()
,setupInitialFinalStates()
, orsetupInitialState()
) which internally all use_setupObjectStates(states)
with the given object states. This process can be customized by overriding the_getColor(i, nStates)
and_getOpacity(i, nStates)
which define the colour and opacity of the i th state.
Afterwards, visual variables can be added to the scene to further represent the transformation. In situations where these variables are placed outside of the scene's bounding box, the bounding box should be expanded using thesceneBoundingBox.expandBy*
family of methods, available through Three.js.
-
Add new settings
New transformation might need additional settings, either for customizing visual logic or for parameterization.
To add new settings:-
Add a new entry in
initSettings()
insettings.js
this.settings["s-my-setting-name"] = new Setting(defaultValue, arrayOfDependentGroups, updateCallbackFunction, updateHTMLCallbackFunction);
When a user modifies the settings through the modal, the
updateCallbackFunction
is called. This method should return whether or not the value was changed. If the value was changed, every group inarrayOfDependentGroups
is notified and its callback is executed (only once per update). For visual variable settings, the dependent group should be scenes while the group for parameterization settings should be set to events. TheupdateHTMLCallbackFunction
callback is called when the modal is opened to update its values. -
Add a new entry to the modal in
#settings-form
inindex.html
<label for="s-my-setting-name">My Setting Name</label> <input type="my-type" id="s-my-setting-name">
The new setting can be retrieved using
SettingsManager.instance.getSettingValue("s-my-setting-name")
. Keep in mind that this value should be cached whenever possible (e.g., insetupScene
) to avoid multiple accesses. -
-
Request and parse events
-
Request events: to request events for the new transformation, the parameterization settings introduced by the user must be sent to the server. To do this, add them to the
parameters
Javascript object inside theSendRequest
function inloader.js
. -
Parse events: to parse events for the new transformation, add a new case inside the
ParseEvents
function inloader.js
by pushing a newMyNewTransformation
object to thetransformations
array for that frame.
-
-
controller.js
: Controller module of the Model-View-Controller architecture. Its main responsibility is to handle user interaction with the narrative, namely traversing through the storyboard, clicking on frames to retrieve more details, jumping to a certain point in the timeline, and other user experience events.
The file contains a Controller class which, as a singleton, is accessible viaController.instance
. Its most important data structure isvisibleFramesInfo
which is used to manage the narrative state in terms of timeline progress and zoomed frames. This data structure is an array of Javascript objects where each entry corresponds to a storyboard row. Each object contains astart
property which is the first frame of the currently being viewed frames. It defaults to 0 for the narrative to start at the beginning and is incremented/decremented each time the user clicks on the right/left arrow. Each entry also contains azoomedFromFrame
property which corresponds to the frame from the previous row that got zoomed (is set to -1 for the first row). -
storyboard.js
: Model module of the Model-View-Controller architecture. Its main responsibility is to store the frames in theframes
member variable of theStoryboard
singleton class (accessible throughStoryboard.instance
). Theframes
property is an array ofFrame
objects (frame.js
). EachFrame
object also contains an array ofFrame
objects as children (childFrames
member variable) which corresponds to the frames retrieved when a frame is zoomed. -
renderer.js
: View module of the Model-View-Controller architecture. Its main responsibility is to render the frames, HTML elements, and other visual elements. It renders the frames using thevisibleFrames
data structure which is updated each time theController
updates the previously describedvisibleFramesInfo
data structure. ThevisibleFrames
is an array of Javascript objects per storyboard row. Each entry contains an array ofFrame
objects (frameObjects
) which are the frames that are currently being shown.
The renderer re-writes the HTML elements when thevisibleFramesInfo
variable is updated. In the render loop, the renderer iterates the visible frames and renders them to a WebGL fullscreen canvas. Additionally, the hierarchy visual cues are rendered to a 2D fullscreen canvas.