The ability to extend or modify existing functionality and create reusable code is critical for any project. We at ZingChart know this just as well as anyone else, so we set out to make it as easy as possible for our users to do this with our JavaScript charting library. To accomplish this task, we did three things:
-
Examined our own modules creation process
-
Streamlined the steps
-
Wrapped everything up into an easy-to-use method,
zingchart.defineModule()
.
Read on to learn how the new defineModule method works.
Custom Modules and Variations
We’ve split modules into two categories: chart
modules and plugin
modules.
- Chart modules are intended for creating new chart types. Chart modules are evoked when the module name is passed to the primary
type
attribute in ZingChart JSON.
{
"type": "myNewChartType",
//ZingChart JSON
}
- Plugin modules are intended to provide additional controls or custom features that are not already available in ZingChart by default. They are evoked when an object with the same name as the module is created in the chart JSON.
{
"type": "bar",
"your-plugin-name": {
//your plugin options
},
//additional ZingChart JSON
}
These two module types share many behaviors and function quite similarly behind the scenes. However, we felt it was important to differentiate between the two, particularly for semantic reasons.
Defining a Custom Module
To define a custom module, use the defineModule()
method. For example:
zingchart.defineModule("toolbar-zoom", "plugin", function(chartJson){
// Do something
});
The defineModule()
method takes three arguments:
-
The name of the new module.
-
The type of module. Accepted values are:
chart
for a chart type moduleplugin
for a plugin module
-
The function that will:
- Manipulate the original chart JSON
- Add event listeners, and
- Define custom module options that you want to make available from the chart JSON. This function is where you define the behavior of your module.
Custom Plugin Modules
In order to get started with a plugin
type module, let’s take a deeper look at the function.
The function that you pass as the third argument to the defineModule()
method should be given its own argument (in the example above, I’ve passed in chartJson
).
By doing this, you are able to intercept the chart object that uses your custom module before it gets rendered by ZingChart.
By gaining access to this object, you can add, remove, and manipulate objects within it as much or as little as you like. You can add shape objects to create buttons, add logic to analyze your data and draw a trendline, create and place custom markers, and so much more. You’re only limited by your creativity and imagination.
In the example below, we’re adding three label objects to the original chart config to be used as buttons in a zoom control toolbar.
zingchart.defineModule('toolbar-zoom', 'plugin', function(chartJson){
/*
* If the 'labels' array of objects already exists, do nothing.
* If it does not exist, initialize it as an empty array.
* We do this to avoid obliteration of any existing labels.
*/
if(!chartJson.labels){
chartJson.labels = []
}
/* Push the toolbar label objects */
chartJson.labels.push({
"type": "rectangle",
"id": "zoomin",
//styling options omitted for brevity
},
{
"type": "rectangle",
"id": "zoomout",
//styling options omitted for brevity
},
{
"type": "rectangle",
"id": "viewall",
//styling options omitted for brevity
}
});
By themselves, the labels won’t do us much good. In order to add button functionality, we must configure event listeners. That way, when a user clicks on the new labels, the desired actions will take place.
To resolve this, we use the label_click
event listener and configure the appropriate zoom action to occur when each label is clicked.
zingchart.defineModule('toolbar-zoom', 'plugin', function(chartJson){
/* Label creation omitted for brevity */
/*
* Add label_click event listener, use the clicked label's
* id in a switch
*/
zingchart.label_click = function (p) {
switch (p.labelid) {
case "zoomin":
zingchart.exec(p.id, "zoomin");
break;
case "zoomout":
zingchart.exec(p.id, "zoomout");
break;
case "viewall":
zingchart.exec(p.id, "viewall");
break;
}
};
In theory, we now have a perfectly functional module that we could include in our chart. But wouldn’t it be nice to be able to customize it like everything else in ZingChart?
This brings us to the option definition phase and begs the following questions :
-
How would you want to allow another user to set different attributes for your module? You would want to make it similar to using ZingChart.
-
Do you really want to go in and hard code changes into the module every time you want to change it for a particular chart? That’s unnecessary work when you can create the ability to changes options and attributes from within the chart JSON.
In order to use this new toolbar-zoom
module, the toolbar-zoom
object must already be included in the chart configuration, so something like this would seem logical for setting background-color
and border-color
on each of the buttons:
var myConfig = {
"type": "bar",
"toolbar-zoom":{ // Add the toolbar
"background-color":"#FFFFFF #D0D7E1",
"border-color":"#ACAFB6"
},
"scale-x":{
"zooming":true
},
"series" : [
{
"values" : [35,42,67,89,25,34,67,85]
}
]
};
All that remains is to create a reference to the module’s object in the original JSON, and perform the appropriate modifications to the chartJson
object before returning the object altogether:
zingchart.defineModule('toolbar-zoom', 'plugin', function(chartJson){
/* Label creation omitted for brevity */
/* Create a reference to the "toolbar-zoom" object */
var optionsObj = chartJson["toolbar-zoom"];
/*
* If the "background-color" attr exists, loop over each label and
* modify the background-color on those with certain "id" values.
*/
if (optionsObj["background-color"]){
for (var n in chartJson["labels"]){
var labelObj = chartJson["labels"][n];
if ( (labelObj["id"] == "zoomin") || (labelObj["id"] == "zoomout")
|| (labelObj["id"] == "viewall") ){
labelObj["background-color"] = optionsObj["background-color"];
}
}
}
/* Same thing as above, but for border-color. */
if (optionsObj["border-color"]){
for (var n in chartJson["labels"]){
var labelObj = chartJson["labels"][n];
if ( (labelObj["id"] == "zoomin") || (labelObj["id"] == "zoomout")
|| (labelObj["id"] == "viewall") ){
labelObj["border-color"] = optionsObj["border-color"];
}
}
}
return chartJson;
});
Our module is now ready for prime time! Plugin modules must be explicitly loaded using the modules
option in the render method in addition to being placed in the chart config as an object.
zingchart.render({
id : 'myChart',
data : myConfig,
height: 400,
width: 600,
modules: "toolbar-zoom" // Load the toolbar
});
Chart Type Modules
Chart type modules can be a bit more complex, as is the case with our choropleth
module, which has been developed in-house by one of our developers who has an affinity for maps. Kicking things off, the initial module definition is mostly the same, with only the second argument - the module type argument - differing:
zingchart.defineModule("choropleth", "chart", function (originalJson) {
//do something
}
This module utilizes a few helper functions, which have been defined at the top of the module for optimal portability:
zingchart.defineModule("choropleth", "chart", function (originalJson) {
/*** Helper Functions ***/
function generateDomain (type, domain, range) {
var scale;
if (type == 'quantize') {
scale = quantizeDomain(domain, range);
}
else {
scale = domain;
}
return scale;
}
function quantize (value, domain, range) {
var index = quantizeIndex(value,domain);
return range[index];
}
/* Additional helper functions omitted for brevity. */
Here, all changes to the chartJson
object are made using information supplied by the user in a scale
object within the options
object (which is already necessary to create a map in ZingChart). This speaks to the flexibility and freedom allowed in a module definition.
Finally, the modified version of chartJson
to be rendered by ZingChart gets returned by the module.
zingchart.defineModule("choropleth", "chart", function (originalJson) {
/* Check for options */
if (originalJson.options) {
var options = originalJson.options;
var mapType = options.map;
var scaleType = options.scale.type;
var range = options.scale.range;
var domain = generateDomain(scaleType, options.scale.domain, range);
/* Additional option handling omitted for brevity. */
}
/* Return modified JSON */
return originalJson;
});
Unlike plugin modules, chart type modules do not need to be loaded explicitly through the use of the modules option inside of the ZingChart render
method, as long as the module definition is in the same page as the chart using it.
However, if you do choose to extract the module and place it into its own JavaScript file, you will need to include a reference to the script in your page.
Custom ZingChart
This is an exciting new feature that we’re thrilled to roll out to our users! And it is just one way ZingChart lets you customize things. You can also check out all the chart styling options, and the custom build generator as well.
For more information on custom modules, check out our Custom Modules feature page. We would love to see what creative uses you can find. Please leave a comment below if you would like to show off your creation.