Commit 184003e6 authored by Sebastien Robin's avatar Sebastien Robin

graph gadget: add prototype of Chart.js and Dygraph gadgets using same API

parent 814afaf3
<!DOCTYPE html>
<html>
<head>
<title>Graph Interface</title>
</head>
<body>
<h1>widget_graph_interface</h1>
<h3>A graph gadget allows to display various types of graph (lines, bars, 3D, etc)</h3>
<dl>
<dt>render</dt>
<dd>render a graph</dd>
<dl>
<dt data-parameter-required="required" data-parameter-type="object">configuration_dict</dt>
<dd><code style="display:block;white-space:pre-wrap">
Generic graph gadget. The purpose of this gadget is to provide an unique
API for various charting libraries
Options supported are :
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Parameters to generate a graph",
"properties": {
"layout": {
"description": "definition of layout",
"properties": {
"title": {
"description": "The title of the graph",
"type": "string"
},
"axis_dict": {
"properties": {
".*": {
"description": "layout definition of one axis",
"properties": {
"title": {
"description": "label to display on axis n",
"type": "string"
},
"type": {
"description": "type of axis",
"enum": [
"linear",
"logarithmic"
],
"default": "linear",
"type": "string"
}, },
"position": {
"description": "where to place the axis, only y axis for now",
"enum": [
"left",
"right"
],
"type": "string"
}
},
"type": "object",
"additionalProperties": false
}
},
"type": "object"
}
},
"type": "object",
"additionalProperties": false
},
"data": {
"description": "the list of data sets",
"items": {
"properties": {
"value_dict": {
"[0-9]*": {
"description": "values for the axis number n",
"type": "array"
},
"type": "object"
},
"type": {
"description": "type of trace that should be displayed",
"enum": [
"pie",
"bar",
"scatter",
"marker",
"surface",
"line"
],
"default": "scatter",
"type": "string"
},
"title": {
"description": "label for this data set",
"type": "string"
},
"axis_mapping_id_dict": {
"[0-9]*": {
"description": "mapping id for the axis number n, this is optional and allows to have several scales per axis, several data set might use same name if they should be grouped on the same scale",
"type": "string"
},
"type": "object"
}
},
"additionalProperties": false,
"type": "object"
},
"type": "array"
}
},
"additionalProperties": false
}
Options were inspired by https://plot.ly/javascript/ which supports various types
of charts.
For axis, typically, on a scatter:
- axis 0 would be mapped to x
- axis 1 would be mapped to y
For a 3D surface, we would have :
- axis 0 would be mapped to x
- axis 1 would be mapped to y
- axis 2 would be mapped to z
Example of options:
{data: [{ value_dict: {"0": [0, 1, 2],
"1": [0, 1, 4]
},
type: "scatter",
label_list: ["First Point", "Second Point", "Third Point"],
axis_mapping_id_dict: {"1": "1_1"},
title: "first data set"
},
{ value_dict: {"0": [0, 1, 3],,
"1": [0, 10, 40]
},
type: "scatter",
title: "second data set",
axis_mapping_id_dict: {"1": "1_2"}
}
],
layout: {axis_dict : {"0": {"title": "x axis label", "type": "linear"},
"1_0": {"title": "y axis label for first data set", "type": "log", "side" : "left"},
"1_1": {"title": "y axis label for second data set", "position": "right"}
},
title: "Title for my global graph"}
});
</code></dd>
</dl>
</dl>
<dl>
<dt>updateConfiguration</dt>
<dd>update Configuration of graph (implies redrawing)</dd>
<dl>
<dt data-parameter-required="required" data-parameter-type="object">configuration_dict</dt>
<dd>same format as the configuration_dict passed to render</dd>
</dl>
</dl>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>ERP5 Widget Graph</title>
<!-- interfaces -->
<link rel="http://www.renderjs.org/rel/interface" href="gadget_officejs_interface_widget_graph.html">
<!-- renderjs -->
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<!-- libraries needed for graphs -->
<script src="chart.js" type="text/javascript"></script>
<!-- custom script -->
<script src="gadget_officejs_widget_graph_chart.js" type="text/javascript"></script>
</head>
<body>
<div id="canvas-holder" style="width:100%">
<canvas class="graph-content"></canvas>
</div>
</body>
</html>
\ No newline at end of file
/*global window, rJS, RSVP, console, Chart */
/*jslint nomen: true, indent: 2 */
(function (window, rJS, RSVP, Chart) {
"use strict";
/////////////////////////////////////////////////////////////////
// templates
/////////////////////////////////////////////////////////////////
var gadget_klass = rJS(window);
var getGraphDataAndParameterFromConfiguration = function (configuration_dict) {
var graph_data_and_parameter = {},
data = configuration_dict.data || [],
trace,
trace_type,
type_list = [],
label_list = [],
trace_value_dict,
dataset_list = [],
dataset,
i, j,
layout = configuration_dict.layout || {},
title = layout.title,
type = 'bar',
x_label, y_label;
/* title seems to be ignored by Chart.js, so do not handle it for now */
/* In Chart.js, there is not yet full support of different types of graph, it only work
under some conditions. So we have to set a global type for whole the graph.
Also Chart.js only support 2D, se we assume we have only only two axis for data.
Then for now we only support series having same x, it can be enhanced later.*/
for (i = 0; i < data.length; i = i + 1) {
trace = data[i];
trace_type = trace.type || 'bar';
type_list.push(trace_type);
trace_value_dict = trace.value_dict || {};
if (trace_value_dict[0] === undefined || trace_value_dict[1] === undefined) {
throw new Error("Unexpected data for Chart.js", data);
}
if (label_list.length === 0) {
for (j = 0; j < trace_value_dict[0].length; j = j + 1) {
label_list[j] = trace_value_dict[0][j];
}
}
dataset = {};
dataset.type = trace_type;
dataset.label = trace.title;
dataset.data = [];
dataset.fill = false;
for (j = 0; j < trace_value_dict[1].length; j = j + 1) {
dataset.data[j] = trace_value_dict[1][j];
}
dataset_list.push(dataset);
}
// Only one type of graph, so set it globally
console.log('type_list', type_list);
if (type_list.length === 1) {
type = type_list[0];
}
graph_data_and_parameter.type = type;
graph_data_and_parameter.data = {};
graph_data_and_parameter.data.labels = label_list;
graph_data_and_parameter.data.datasets = dataset_list;
return graph_data_and_parameter;
};
/////////////////////////////////////////////////////////////////
// some methods
/////////////////////////////////////////////////////////////////
gadget_klass
/////////////////////////////////////////////////////////////////
// ready
/////////////////////////////////////////////////////////////////
.ready(function (gadget) {
gadget.property_dict = {
render_deferred: RSVP.defer()
};
})
.ready(function (gadget) {
return gadget.getElement()
.push(function (element) {
gadget.property_dict.element = element;
});
})
/////////////////////////////////////////////////////////////////
// published methods
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// acquired methods
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// declared methods
/////////////////////////////////////////////////////////////////
.declareMethod('render', function (configuration_dict) {
var gadget = this,
container,
graph_data_and_parameter,
chart;
container = gadget.property_dict.element.querySelector(".graph-content");
graph_data_and_parameter = getGraphDataAndParameterFromConfiguration(configuration_dict);
console.log("graph_data_and_parameter", graph_data_and_parameter);
chart = new Chart(container, graph_data_and_parameter);
gadget.property_dict.chart = chart;
})
.declareMethod('updateConfiguration', function (configuration_dict) {
/* Update the graph with new data/configuration.
Chart.js support update of data, so you update values, it should nicely retransform the graph
with new values. However, it does sounds to works well when everything is changed (like more
lines, or deep reconfiguration). So just fully redraw a graph instead. This might be improved
later.
*/
var gadget = this,
graph_data_and_parameter,
container = gadget.property_dict.element.querySelector(".graph-content"),
chart;
gadget.property_dict.chart.destroy();
graph_data_and_parameter = getGraphDataAndParameterFromConfiguration(configuration_dict);
chart = new Chart(container, graph_data_and_parameter);
gadget.property_dict.chart = chart;
});
}(window, rJS, RSVP, Chart));
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<title>ERP5 Widget Graph</title>
<!-- interfaces -->
<link rel="http://www.renderjs.org/rel/interface" href="gadget_officejs_interface_widget_graph.html">
<!-- renderjs -->
<script src="rsvp.js" type="text/javascript"></script>
<script src="renderjs.js" type="text/javascript"></script>
<!-- libraries needed for graphs -->
<script src="dygraph.js" type="text/javascript"></script>
<!-- custom script -->
<script src="gadget_officejs_widget_graph_dygraph.js" type="text/javascript"></script>
</head>
<body>
<!--div class="graph-content" style="width: 480px; height: 400px;"></div-->
<div style="width: 100%" class="graph-content"></div>
</body>
</html>
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Web Page" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Change_local_roles_Permission</string> </key>
<value>
<tuple>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
</item>
<item>
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>classification/collaborative/team</string>
</tuple>
</value>
</item>
<item>
<key> <string>content_md5</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>gadget_officejs_widget_graph_dygraph.html</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>gadget_officejs_widget_graph_dygraph_html</string> </value>
</item>
<item>
<key> <string>language</string> </key>
<value> <string>en</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Web Page</string> </value>
</item>
<item>
<key> <string>short_title</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>OfficeJS Widget Graph With Dygraph</string> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>document_publication_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>edit_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>publish_alive</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>superseb</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1490781750.0</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>published_alive</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>superseb</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>958.28489.56960.4983</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1491487527.75</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
/*global window, rJS, RSVP, console, Dygraph */
/*jslint nomen: true, indent: 2 */
(function (window, rJS, RSVP, Dygraph) {
"use strict";
/////////////////////////////////////////////////////////////////
// templates
/////////////////////////////////////////////////////////////////
var gadget_klass = rJS(window);
var getGraphDataAndParameterFromConfiguration = function (configuration_dict) {
var graph_data_and_parameter = {},
layout = configuration_dict.layout || {},
data = configuration_dict.data || [],
x_label, y_label,
label_list = [],
trace_value_dict,
dygraph_data = [],
trace,
dygraph_data_and_parameter = {},
axis_dict,
object_key_list,
axis_definition,
axis_mapping_id_dict,
type,
serie,
serie_value_list = [],
serie_value_mapping = {},
i, j;
axis_dict = layout.axis_dict || {};
x_label = (axis_dict[0] || {}).title;
y_label = (axis_dict[1] || {}).title;
if (x_label !== undefined) {
graph_data_and_parameter.xlabel = x_label;
}
if (y_label !== undefined) {
graph_data_and_parameter.ylabel = y_label;
}
label_list.push(x_label);
/* Dygraph only support 2D, se we assume we have only only two axis for data.
Then for now we only support series having same x, it can be enhanced later.*/
/* We assume below that the x axis for all data is the same. Otherwise, dygraph needs that we fill
empty values by hand, which is not done here yet