-
Notifications
You must be signed in to change notification settings - Fork 5
Component Implementation Guide
Each Genotet component needs to support implementation of 4 classes - view, loader, controller and renderer.
We use a Chart example in this guide. We need 4 files:
- chart-view.js
- chart-loader.js
- chart-controller.js
- chart-renderer.js All the above files are located under js/components/chart.
ChartView needs to inherit the base view class. Genotet uses selfish.js for js inheritance. The inheritance can be specified by the following syntax.
var extObject = {
// ChartView implementation
};
var ChartView = View.extend(extObject);
There is only one function ChartView has to implement - createHandlers. This function tells Genotet which loader/controller/renderer to use when launching ChartView.
var extObject = {
createHandlers: function() {
this.loader = ChartLoader.new();
this.controller = ChartController.new();
this.renderer = ChartRenderer.new();
}
};
You can use the base class of loader/controller/renderer if you do not need an implementation for ChartView, e.g.
this.loader = Loader.new(); // will do nothing upon load
Each view in Genotet have its default load/control/render call. However, you may want to override them in chart-view.js for your purpose.
// in js/components/base/view.js
load: function(para) {
// the load call is passed to loader
this.loader.load(para);
},
control: function() {
// the control call is passed to the controller
this.controller.display();
},
render: function() {
// the render call is passed to the renderer
this.renderer.render();
},
Genotet will return a view object upon its creation. The above functions may be called once the view is ready.
var view = CreateView("my view", "chart");
view.load({...});
view.render();
The loader/controller/renderer are aware of its belonging view. You can get the parent view by the loader/controller/renderer's view attribute.
Chart loader needs to provide data for the chart to render. The load function implements the load procedure. Usually after loading is done, a call to the view's render() function is desired.
var extObject = {
// implement load function here
load: function(para) {
// this load function is synchronous for testing
// but in practice you can write a load function that is async with a callback function
// usually, the callback function fires a render call
this.view.data = {
points: [
{x:10, y:30},
{x:20, y:60},
{x:50, y:50},
{x:70, y:150},
{x:100, y:20}
],
color: "red"
};
// here we are sync, so we directly fire the render call
this.view.render();
}
};
var ChartLoader = Loader.extend(extObject);
The load function can either be sync or async. If it is async, then you may need to add an async callback to render().
The renderer visualizes the data. The data is stored under view.data. The following code shows a simple example of rendering 5 circles. The render function is usually the entry point of rendering.
var extObject = {
render: function() {
// get the view this renderer belongs to
var view = this.view;
// clear previous drawing
d3.select("#canvas" + view.viewid + " svg").remove();
// use d3 to set an svg
this.svg = d3.select("#canvas" + view.viewid).append("svg");
// need to explicitly set svg size, otherwise it won't fill the view
this.svg
.style("width", "100%")
.style("height", "100%")
.style("background", "white");
// renders the data using d3
this.svg.selectAll("circle").data(view.data.points).enter()
.append("circle")
.attr("cx", function(d){ return d.x; })
.attr("cy", function(d){ return d.y; })
.attr("r", view.getViewWidth() / 100) // the circles get different radius when view is resized!
.attr("fill", view.data.color);
}
};
var ChartRenderer = Renderer.extend(extObject);
Notice the line with view.getViewWidth(). This is an example of getting the state of the view from the renderer. This is useful for updating the view when the view gets resized.
The view base class supports a series of get functions to allow its inheriting views' loader/controller/renderer to be aware of the view's state. See view API documentation for details.
The controller of a view defines the UI necessary to modify the visualization. In this chart example, we use the controller to toggle the color of the 5 circles. Initially, the 5 circles have red color. We add a button in the controller. Each time the button gets clicked, the color switches from red to blue (or vice versa).
The view.getJqController function returns a jquery selection corresponding to the controller div at the top-right corner of Genotet. The button is appended to that div. Its click callback is defined as the toggleColor function, implemented within chart-view.js. The controlled data is view.data JSON object.
The display function is usually the entry point of showing the controller.
var extObject = {
display: function() {
// display function MUST be implemented in the view's controller
// this function will be called when the view is activated (by user clicking)
// get the view this controller belongs to
var view = this.view;
// obtain the jquery node for controller (at top-right of the system)
var jqctrl = view.getJqController();
// clear the things there
jqctrl.children().remove();
// append a new div as wrapper, which would contain a button
var wrapper = $("<div></div>").appendTo(jqctrl);
$("<input type='button' value='Chart - Toggle Color'>")
.button()
.click( function() {
view.toggleColor(); // when clicked, this fires the toggleColor function of the chart view
})
.appendTo(wrapper);
}
};
var ChartController = Controller.extend(extObject);
So in chart-view.js, we add the toggleColor function to update the data.
// chart-view.js
toggleColor: function() {
// This changes the data attribute color
if (this.data.color === "red")
this.data.color = "blue";
else
this.data.color = "red";
// When the data gets rendered again, new color will be applied
this.renderer.render();
}
We can update the chart when the view gets resized. This is achieved by implementing the onResize callback supported by the base view class.
onResize: function(width, height) {
// once the view is resized, we re-render everything
this.renderer.render();
}
Here we just call the render() function again, because our renderer applies the current view width to the circle's radius. Recall the following line in chart-renderer.js above.
.attr("r", view.getViewWidth() / 100);