Browse Source

Introducing group based shading

Separate shading from line drawing (fixes drawing order)
Cleanup of draw code
Fix icon-shading CSS override
newShading
Ludo Stellingwerff 9 years ago
parent
commit
a44e88ef6d
9 changed files with 545 additions and 412 deletions
  1. +12
    -3
      docs/graph2d/index.html
  2. +1
    -1
      examples/graph2d/06_interpolation.html
  3. +14
    -13
      examples/graph2d/17_dynamicStyling.html
  4. +48
    -22
      examples/graph2d/20_shading.html
  5. +46
    -47
      lib/timeline/component/GraphGroup.js
  6. +1
    -1
      lib/timeline/component/Legend.js
  7. +188
    -86
      lib/timeline/component/LineGraph.js
  8. +232
    -237
      lib/timeline/component/graph2d_types/line.js
  9. +3
    -2
      lib/timeline/optionsGraph2d.js

+ 12
- 3
docs/graph2d/index.html View File

@ -758,9 +758,18 @@ onRender: function(item, group, graph2d) {
<tr parent="shaded" class="hidden"> <tr parent="shaded" class="hidden">
<td class="greenField indent">shaded.orientation</td> <td class="greenField indent">shaded.orientation</td>
<td>String</td> <td>String</td>
<td>'zero'</td>
<td>This determines if the shaded area is at the bottom or at the top of the curve, or always towards the zero-axis of the graph. The options are 'zero', 'bottom' or 'top'.
See <a href="../../examples/graph2d/20_shading.html">Example 20</a> what these options look like.</td>
<td>'bottom'</td>
<td>This determines if the shaded area is at the bottom or at the top of the curve, or always towards the zero-axis of the graph.
The options are 'zero', 'bottom', 'top', or the special case of 'group'. If group is chosen, the option groupId is required.
See <a href="../../examples/graph2d/20_shading.html">Example 20</a> what these options look like.
</td>
</tr>
<tr parent="shaded" class="hidden">
<td class="greenField indent">shaded.groupId</td>
<td>String</td>
<td>undefined</td>
<td>The id of the group which should be used as the other shading limit.
</td>
</tr> </tr>
<tr parent="shaded" class="hidden"> <tr parent="shaded" class="hidden">
<td class="greenField indent">shaded.style</td> <td class="greenField indent">shaded.style</td>

+ 1
- 1
examples/graph2d/06_interpolation.html View File

@ -88,7 +88,7 @@
} }
var options = { var options = {
dataPoints: false,
drawPoints: false,
dataAxis: {visible: false}, dataAxis: {visible: false},
legend: true, legend: true,
start: '2014-06-11', start: '2014-06-11',

+ 14
- 13
examples/graph2d/17_dynamicStyling.html View File

@ -79,7 +79,8 @@
<select id="fill" onchange="updateStyle()"> <select id="fill" onchange="updateStyle()">
<option value="">none</option> <option value="">none</option>
<option value="top">top</option> <option value="top">top</option>
<option value="bottom" selected="selected">bottom</option>
<option value="bottom">bottom</option>
<option value="zero" selected="selected">zero</option>
</select> </select>
</td> </td>
</tr> </tr>
@ -98,16 +99,16 @@
<td>Fill Opacity</td> <td>Fill Opacity</td>
<td> <td>
<select id="fillopacity" onchange="updateStyle()"> <select id="fillopacity" onchange="updateStyle()">
<option value="opacity:0.1;">0.1</option>
<option value="opacity:0.2;">0.2</option>
<option value="opacity:0.3;">0.3</option>
<option value="opacity:0.4;">0.4</option>
<option value="opacity:0.5;">0.5</option>
<option value="opacity:0.6;" selected="selected">0.6</option>
<option value="opacity:0.7;">0.7</option>
<option value="opacity:0.8;">0.8</option>
<option value="opacity:0.9;">0.9</option>
<option value="opacity:1;">1</option>
<option value="fill-opacity:0.1;">0.1</option>
<option value="fill-opacity:0.2;">0.2</option>
<option value="fill-opacity:0.3;">0.3</option>
<option value="fill-opacity:0.4;">0.4</option>
<option value="fill-opacity:0.5;">0.5</option>
<option value="fill-opacity:0.6;" selected="selected">0.6</option>
<option value="fill-opacity:0.7;">0.7</option>
<option value="fill-opacity:0.8;">0.8</option>
<option value="fill-opacity:0.9;">0.9</option>
<option value="fill-opacity:1;">1</option>
</select> </select>
</td> </td>
</tr> </tr>
@ -187,7 +188,7 @@
{x: '2014-06-11', y: 10, group: 0}, {x: '2014-06-11', y: 10, group: 0},
{x: '2014-06-12', y: 25, group: 0}, {x: '2014-06-12', y: 25, group: 0},
{x: '2014-06-13', y: 30, group: 0}, {x: '2014-06-13', y: 30, group: 0},
{x: '2014-06-14', y: 10, group: 0},
{x: '2014-06-14', y: -10, group: 0},
{x: '2014-06-15', y: 15, group: 0}, {x: '2014-06-15', y: 15, group: 0},
{x: '2014-06-16', y: 30, group: 0} {x: '2014-06-16', y: 30, group: 0}
]; ];
@ -210,7 +211,7 @@
style: 'square' // square, circle style: 'square' // square, circle
}, },
shaded: { shaded: {
orientation: 'bottom' // top, bottom
orientation: 'zero' // top, bottom
} }
} }
}; };

+ 48
- 22
examples/graph2d/20_shading.html View File

@ -24,7 +24,7 @@
<script type="text/javascript"> <script type="text/javascript">
// create a dataSet with groups // create a dataSet with groups
var names = ['top', 'bottom', 'zero', 'none'];
var names = ['top', 'bottom', 'zero', 'none', 'group', 'none'];
var groups = new vis.DataSet(); var groups = new vis.DataSet();
groups.add({ groups.add({
id: 0, id: 0,
@ -55,34 +55,60 @@
groups.add({ groups.add({
id: 3, id: 3,
content: names[3],
options: {
excludeFromLegend: true
}
});
groups.add({
id: 4,
content: names[4],
options: {
shaded: {
orientation: 'group',
groupId: '3'
}
}
});
groups.add({
id: 5,
content: names[5]
}); });
var container = document.getElementById('visualization'); var container = document.getElementById('visualization');
var items = [
{x: '2014-06-10', y: 0, group: 0},
{x: '2014-06-11', y: 15, group: 0},
{x: '2014-06-13', y: -15, group: 0},
{x: '2014-06-14', y: 0, group: 0},
{x: '2014-06-15', y: 0, group: 1},
{x: '2014-06-16', y: 15, group: 1},
{x: '2014-06-17', y: -15, group: 1},
{x: '2014-06-18', y: 0, group: 1},
{x: '2014-06-19', y: 0, group: 2},
{x: '2014-06-20', y: 15, group: 2},
{x: '2014-06-21', y: -15, group: 2},
{x: '2014-06-22', y: 0, group: 2},
{x: '2014-06-23', y: 0, group: 3},
{x: '2014-06-24', y: 15, group: 3},
{x: '2014-06-25', y: -15, group: 3},
{x: '2014-06-26', y: 0, group: 3},
];
var items = [
{x: '2014-06-11', y: 0, group: 0},
{x: '2014-06-12', y: 15, group: 0},
{x: '2014-06-13', y: -15, group: 0},
{x: '2014-06-14', y: 0, group: 0},
{x: '2014-06-15', y: 0, group: 1},
{x: '2014-06-16', y: 15, group: 1},
{x: '2014-06-17', y: -15, group: 1},
{x: '2014-06-18', y: 0, group: 1},
{x: '2014-06-19', y: 0, group: 2},
{x: '2014-06-20', y: 15, group: 2},
{x: '2014-06-21', y: -15, group: 2},
{x: '2014-06-22', y: 0, group: 2},
{x: '2014-06-23', y: -2, group: 3},
{x: '2014-06-24', y: 13, group: 3},
{x: '2014-06-25', y: -17, group: 3},
{x: '2014-06-26', y: -2, group: 3},
{x: '2014-06-23', y: 2, group: 4},
{x: '2014-06-24', y: 17, group: 4},
{x: '2014-06-25', y: -13, group: 4},
{x: '2014-06-26', y: 2, group: 4},
{x: '2014-06-27', y: 0, group: 5},
{x: '2014-06-28', y: 15, group: 5},
{x: '2014-06-29', y: -15, group: 5},
{x: '2014-06-30', y: 0, group: 5}
];
var dataset = new vis.DataSet(items); var dataset = new vis.DataSet(items);
var options = { var options = {
legend: true, legend: true,
start: '2014-06-05',
end: '2014-06-29'
start: '2014-06-07',
end: '2014-07-03'
}; };
var graph2d = new vis.Graph2d(container, dataset, groups, options); var graph2d = new vis.Graph2d(container, dataset, groups, options);

+ 46
- 47
lib/timeline/component/GraphGroup.js View File

@ -14,10 +14,10 @@ var Points = require('./graph2d_types/points');
* It enumerates through the default styles * It enumerates through the default styles
* @constructor * @constructor
*/ */
function GraphGroup (group, groupId, options, groupsUsingDefaultStyles) {
function GraphGroup(group, groupId, options, groupsUsingDefaultStyles) {
this.id = groupId; this.id = groupId;
var fields = ['sampling','style','sort','yAxisOrientation','barChart','drawPoints','shaded','interpolation']
this.options = util.selectiveBridgeObject(fields,options);
var fields = ['sampling', 'style', 'sort', 'yAxisOrientation', 'barChart', 'drawPoints', 'shaded', 'interpolation']
this.options = util.selectiveBridgeObject(fields, options);
this.usingDefaultStyle = group.className === undefined; this.usingDefaultStyle = group.className === undefined;
this.groupsUsingDefaultStyles = groupsUsingDefaultStyles; this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
this.zeroPosition = 0; this.zeroPosition = 0;
@ -34,11 +34,13 @@ function GraphGroup (group, groupId, options, groupsUsingDefaultStyles) {
* this loads a reference to all items in this group into this group. * this loads a reference to all items in this group into this group.
* @param {array} items * @param {array} items
*/ */
GraphGroup.prototype.setItems = function(items) {
GraphGroup.prototype.setItems = function (items) {
if (items != null) { if (items != null) {
this.itemsData = items; this.itemsData = items;
if (this.options.sort == true) { if (this.options.sort == true) {
this.itemsData.sort(function (a,b) {return a.x - b.x;})
this.itemsData.sort(function (a, b) {
return a.x - b.x;
})
} }
// typecast all items to numbers. Takes around 10ms for 500.000 items // typecast all items to numbers. Takes around 10ms for 500.000 items
for (var i = 0; i < this.itemsData.length; i++) { for (var i = 0; i < this.itemsData.length; i++) {
@ -55,30 +57,29 @@ GraphGroup.prototype.setItems = function(items) {
* this is used for plotting barcharts, this way, we only have to calculate it once. * this is used for plotting barcharts, this way, we only have to calculate it once.
* @param pos * @param pos
*/ */
GraphGroup.prototype.setZeroPosition = function(pos) {
GraphGroup.prototype.setZeroPosition = function (pos) {
this.zeroPosition = pos; this.zeroPosition = pos;
}; };
/** /**
* set the options of the graph group over the default options. * set the options of the graph group over the default options.
* @param options * @param options
*/ */
GraphGroup.prototype.setOptions = function(options) {
GraphGroup.prototype.setOptions = function (options) {
if (options !== undefined) { if (options !== undefined) {
var fields = ['sampling','style','sort','yAxisOrientation','barChart','excludeFromLegend'];
var fields = ['sampling', 'style', 'sort', 'yAxisOrientation', 'barChart', 'excludeFromLegend'];
util.selectiveDeepExtend(fields, this.options, options); util.selectiveDeepExtend(fields, this.options, options);
// if the group's drawPoints is a function delegate the callback to the onRender property // if the group's drawPoints is a function delegate the callback to the onRender property
if (typeof options.drawPoints == 'function') { if (typeof options.drawPoints == 'function') {
options.drawPoints = {
onRender: options.drawPoints
}
options.drawPoints = {
onRender: options.drawPoints
}
} }
util.mergeOptions(this.options, options,'interpolation');
util.mergeOptions(this.options, options,'drawPoints');
util.mergeOptions(this.options, options,'shaded');
util.mergeOptions(this.options, options, 'interpolation');
util.mergeOptions(this.options, options, 'drawPoints');
util.mergeOptions(this.options, options, 'shaded');
if (options.interpolation) { if (options.interpolation) {
if (typeof options.interpolation == 'object') { if (typeof options.interpolation == 'object') {
@ -114,7 +115,7 @@ GraphGroup.prototype.setOptions = function(options) {
* this updates the current group class with the latest group dataset entree, used in _updateGroup in linegraph * this updates the current group class with the latest group dataset entree, used in _updateGroup in linegraph
* @param group * @param group
*/ */
GraphGroup.prototype.update = function(group) {
GraphGroup.prototype.update = function (group) {
this.group = group; this.group = group;
this.content = group.content || 'graph'; this.content = group.content || 'graph';
this.className = group.className || this.className || 'vis-graph-group' + this.groupsUsingDefaultStyles[0] % 10; this.className = group.className || this.className || 'vis-graph-group' + this.groupsUsingDefaultStyles[0] % 10;
@ -134,7 +135,7 @@ GraphGroup.prototype.update = function(group) {
* @param iconWidth * @param iconWidth
* @param iconHeight * @param iconHeight
*/ */
GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) {
GraphGroup.prototype.drawIcon = function (x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) {
var fillHeight = iconHeight * 0.5; var fillHeight = iconHeight * 0.5;
var path, fillPath; var path, fillPath;
@ -142,40 +143,43 @@ GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, icon
outline.setAttributeNS(null, "x", x); outline.setAttributeNS(null, "x", x);
outline.setAttributeNS(null, "y", y - fillHeight); outline.setAttributeNS(null, "y", y - fillHeight);
outline.setAttributeNS(null, "width", iconWidth); outline.setAttributeNS(null, "width", iconWidth);
outline.setAttributeNS(null, "height", 2*fillHeight);
outline.setAttributeNS(null, "height", 2 * fillHeight);
outline.setAttributeNS(null, "class", "vis-outline"); outline.setAttributeNS(null, "class", "vis-outline");
if (this.options.style == 'line') { if (this.options.style == 'line') {
path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer); path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
path.setAttributeNS(null, "class", this.className); path.setAttributeNS(null, "class", this.className);
if(this.style !== undefined) {
if (this.style !== undefined) {
path.setAttributeNS(null, "style", this.style); path.setAttributeNS(null, "style", this.style);
} }
path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+"");
path.setAttributeNS(null, "d", "M" + x + "," + y + " L" + (x + iconWidth) + "," + y + "");
if (this.options.shaded.enabled == true) { if (this.options.shaded.enabled == true) {
fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer); fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
if (this.options.shaded.orientation == 'top') { if (this.options.shaded.orientation == 'top') {
fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) +
"L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight));
fillPath.setAttributeNS(null, "d", "M" + x + ", " + (y - fillHeight) +
"L" + x + "," + y + " L" + (x + iconWidth) + "," + y + " L" + (x + iconWidth) + "," + (y - fillHeight));
} }
else { else {
fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " +
"L"+x+"," + (y + fillHeight) + " " +
"L"+ (x + iconWidth) + "," + (y + fillHeight) +
"L"+ (x + iconWidth) + ","+y);
fillPath.setAttributeNS(null, "d", "M" + x + "," + y + " " +
"L" + x + "," + (y + fillHeight) + " " +
"L" + (x + iconWidth) + "," + (y + fillHeight) +
"L" + (x + iconWidth) + "," + y);
} }
fillPath.setAttributeNS(null, "class", this.className + " vis-icon-fill"); fillPath.setAttributeNS(null, "class", this.className + " vis-icon-fill");
if (this.options.shaded.style !== undefined && this.options.shaded.style !== "") {
fillPath.setAttributeNS(null, "style", this.options.shaded.style);
}
} }
if (this.options.drawPoints.enabled == true) { if (this.options.drawPoints.enabled == true) {
var groupTemplate = {
style: this.options.drawPoints.style,
styles: this.options.drawPoints.styles,
size:this.options.drawPoints.size,
className: this.className
};
DOMutil.drawPoint(x + 0.5 * iconWidth, y, groupTemplate, JSONcontainer, SVGcontainer);
var groupTemplate = {
style: this.options.drawPoints.style,
styles: this.options.drawPoints.styles,
size: this.options.drawPoints.size,
className: this.className
};
DOMutil.drawPoint(x + 0.5 * iconWidth, y, groupTemplate, JSONcontainer, SVGcontainer);
} }
} }
else { else {
@ -183,10 +187,10 @@ GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, icon
var bar1Height = Math.round(0.4 * iconHeight); var bar1Height = Math.round(0.4 * iconHeight);
var bar2Height = Math.round(0.75 * iconHeight); var bar2Height = Math.round(0.75 * iconHeight);
var offset = Math.round((iconWidth - (2 * barWidth))/3);
var offset = Math.round((iconWidth - (2 * barWidth)) / 3);
DOMutil.drawBar(x + 0.5*barWidth + offset , y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' vis-bar', JSONcontainer, SVGcontainer, this.style);
DOMutil.drawBar(x + 1.5*barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' vis-bar', JSONcontainer, SVGcontainer, this.style);
DOMutil.drawBar(x + 0.5 * barWidth + offset, y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' vis-bar', JSONcontainer, SVGcontainer, this.style);
DOMutil.drawBar(x + 1.5 * barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' vis-bar', JSONcontainer, SVGcontainer, this.style);
} }
}; };
@ -198,23 +202,18 @@ GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, icon
* @param iconHeight * @param iconHeight
* @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOrientation|*)}} * @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOrientation|*)}}
*/ */
GraphGroup.prototype.getLegend = function(iconWidth, iconHeight) {
var svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
this.drawIcon(0,0.5*iconHeight,[],svg,iconWidth,iconHeight);
return {icon: svg, label: this.content, orientation:this.options.yAxisOrientation};
GraphGroup.prototype.getLegend = function (iconWidth, iconHeight) {
var svg = document.createElementNS('http://www.w3.org/2000/svg', "svg");
this.drawIcon(0, 0.5 * iconHeight, [], svg, iconWidth, iconHeight);
return {icon: svg, label: this.content, orientation: this.options.yAxisOrientation};
}; };
GraphGroup.prototype.getYRange = function(groupData) {
GraphGroup.prototype.getYRange = function (groupData) {
return this.type.getYRange(groupData); return this.type.getYRange(groupData);
}; };
GraphGroup.prototype.getData = function(groupData) {
GraphGroup.prototype.getData = function (groupData) {
return this.type.getData(groupData); return this.type.getData(groupData);
}; };
GraphGroup.prototype.draw = function(dataset, group, framework) {
this.type.draw(dataset, group, framework);
};
module.exports = GraphGroup; module.exports = GraphGroup;

+ 1
- 1
lib/timeline/component/Legend.js View File

@ -187,7 +187,7 @@ Legend.prototype.drawLegendIcons = function() {
var groupArray = Object.keys(this.groups); var groupArray = Object.keys(this.groups);
groupArray.sort(function (a,b) { groupArray.sort(function (a,b) {
return (a < b ? -1 : 1); return (a < b ? -1 : 1);
})
});
// this resets the elements so the order is maintained // this resets the elements so the order is maintained
DOMutil.resetElements(this.svgElements); DOMutil.resetElements(this.svgElements);

+ 188
- 86
lib/timeline/component/LineGraph.js View File

@ -6,8 +6,9 @@ var Component = require('./Component');
var DataAxis = require('./DataAxis'); var DataAxis = require('./DataAxis');
var GraphGroup = require('./GraphGroup'); var GraphGroup = require('./GraphGroup');
var Legend = require('./Legend'); var Legend = require('./Legend');
var BarFunctions = require('./graph2d_types/bar');
var LineFunctions = require('./graph2d_types/line');
var Bars = require('./graph2d_types/bar');
var Lines = require('./graph2d_types/line');
var Points = require('./graph2d_types/points');
var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
@ -27,11 +28,11 @@ function LineGraph(body, options) {
defaultGroup: 'default', defaultGroup: 'default',
sort: true, sort: true,
sampling: true, sampling: true,
stack:false,
stack: false,
graphHeight: '400px', graphHeight: '400px',
shaded: { shaded: {
enabled: false, enabled: false,
orientation: 'bottom' // top, bottom
orientation: 'bottom' // top, bottom, zero
}, },
style: 'line', // line, bar style: 'line', // line, bar
barChart: { barChart: {
@ -56,15 +57,19 @@ function LineGraph(body, options) {
width: '40px', width: '40px',
visible: true, visible: true,
alignZeros: true, alignZeros: true,
left:{
range: {min:undefined,max:undefined},
format: function (value) {return value;},
title: {text:undefined,style:undefined}
left: {
range: {min: undefined, max: undefined},
format: function (value) {
return value;
},
title: {text: undefined, style: undefined}
}, },
right:{
range: {min:undefined,max:undefined},
format: function (value) {return value;},
title: {text:undefined,style:undefined}
right: {
range: {min: undefined, max: undefined},
format: function (value) {
return value;
},
title: {text: undefined, style: undefined}
} }
}, },
legend: { legend: {
@ -133,10 +138,10 @@ function LineGraph(body, options) {
this.setOptions(options); this.setOptions(options);
this.groupsUsingDefaultStyles = [0]; this.groupsUsingDefaultStyles = [0];
this.COUNTER = 0; this.COUNTER = 0;
this.body.emitter.on('rangechanged', function() {
this.body.emitter.on('rangechanged', function () {
me.lastStart = me.body.range.start; me.lastStart = me.body.range.start;
me.svg.style.left = util.option.asSize(-me.props.width); me.svg.style.left = util.option.asSize(-me.props.width);
me.redraw.call(me,true);
me.redraw.call(me, true);
}); });
// create the HTML DOM // create the HTML DOM
@ -151,15 +156,15 @@ LineGraph.prototype = new Component();
/** /**
* Create the HTML DOM for the ItemSet * Create the HTML DOM for the ItemSet
*/ */
LineGraph.prototype._create = function(){
LineGraph.prototype._create = function () {
var frame = document.createElement('div'); var frame = document.createElement('div');
frame.className = 'vis-line-graph'; frame.className = 'vis-line-graph';
this.dom.frame = frame; this.dom.frame = frame;
// create svg element for graph drawing. // create svg element for graph drawing.
this.svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
this.svg.style.position = 'relative'; this.svg.style.position = 'relative';
this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
this.svg.style.height = ('' + this.options.graphHeight).replace('px', '') + 'px';
this.svg.style.display = 'block'; this.svg.style.display = 'block';
frame.appendChild(this.svg); frame.appendChild(this.svg);
@ -182,23 +187,23 @@ LineGraph.prototype._create = function(){
* set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element. * set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element.
* @param {object} options * @param {object} options
*/ */
LineGraph.prototype.setOptions = function(options) {
LineGraph.prototype.setOptions = function (options) {
if (options) { if (options) {
var fields = ['sampling','defaultGroup','stack','height','graphHeight','yAxisOrientation','style','barChart','dataAxis','sort','groups'];
var fields = ['sampling', 'defaultGroup', 'stack', 'height', 'graphHeight', 'yAxisOrientation', 'style', 'barChart', 'dataAxis', 'sort', 'groups'];
if (options.graphHeight === undefined && options.height !== undefined && this.body.domProps.centerContainer.height !== undefined) { if (options.graphHeight === undefined && options.height !== undefined && this.body.domProps.centerContainer.height !== undefined) {
this.updateSVGheight = true; this.updateSVGheight = true;
this.updateSVGheightOnResize = true; this.updateSVGheightOnResize = true;
} }
else if (this.body.domProps.centerContainer.height !== undefined && options.graphHeight !== undefined) { else if (this.body.domProps.centerContainer.height !== undefined && options.graphHeight !== undefined) {
if (parseInt((options.graphHeight + '').replace("px",'')) < this.body.domProps.centerContainer.height) {
if (parseInt((options.graphHeight + '').replace("px", '')) < this.body.domProps.centerContainer.height) {
this.updateSVGheight = true; this.updateSVGheight = true;
} }
} }
util.selectiveDeepExtend(fields, this.options, options); util.selectiveDeepExtend(fields, this.options, options);
util.mergeOptions(this.options, options,'interpolation');
util.mergeOptions(this.options, options,'drawPoints');
util.mergeOptions(this.options, options,'shaded');
util.mergeOptions(this.options, options,'legend');
util.mergeOptions(this.options, options, 'interpolation');
util.mergeOptions(this.options, options, 'drawPoints');
util.mergeOptions(this.options, options, 'shaded');
util.mergeOptions(this.options, options, 'legend');
if (options.interpolation) { if (options.interpolation) {
if (typeof options.interpolation == 'object') { if (typeof options.interpolation == 'object') {
@ -245,7 +250,7 @@ LineGraph.prototype.setOptions = function(options) {
/** /**
* Hide the component from the DOM * Hide the component from the DOM
*/ */
LineGraph.prototype.hide = function() {
LineGraph.prototype.hide = function () {
// remove the frame containing the items // remove the frame containing the items
if (this.dom.frame.parentNode) { if (this.dom.frame.parentNode) {
this.dom.frame.parentNode.removeChild(this.dom.frame); this.dom.frame.parentNode.removeChild(this.dom.frame);
@ -257,7 +262,7 @@ LineGraph.prototype.hide = function() {
* Show the component in the DOM (when not already visible). * Show the component in the DOM (when not already visible).
* @return {Boolean} changed * @return {Boolean} changed
*/ */
LineGraph.prototype.show = function() {
LineGraph.prototype.show = function () {
// show frame containing the items // show frame containing the items
if (!this.dom.frame.parentNode) { if (!this.dom.frame.parentNode) {
this.body.dom.center.appendChild(this.dom.frame); this.body.dom.center.appendChild(this.dom.frame);
@ -269,7 +274,7 @@ LineGraph.prototype.show = function() {
* Set items * Set items
* @param {vis.DataSet | null} items * @param {vis.DataSet | null} items
*/ */
LineGraph.prototype.setItems = function(items) {
LineGraph.prototype.setItems = function (items) {
var me = this, var me = this,
ids, ids,
oldItemsData = this.itemsData; oldItemsData = this.itemsData;
@ -315,7 +320,7 @@ LineGraph.prototype.setItems = function(items) {
* Set groups * Set groups
* @param {vis.DataSet} groups * @param {vis.DataSet} groups
*/ */
LineGraph.prototype.setGroups = function(groups) {
LineGraph.prototype.setGroups = function (groups) {
var me = this; var me = this;
var ids; var ids;
@ -362,20 +367,23 @@ LineGraph.prototype.setGroups = function(groups) {
* @param [ids] * @param [ids]
* @private * @private
*/ */
LineGraph.prototype._onUpdate = function(ids) {
LineGraph.prototype._onUpdate = function (ids) {
this._updateAllGroupData(); this._updateAllGroupData();
this.redraw(true); this.redraw(true);
}; };
LineGraph.prototype._onAdd = function (ids) {this._onUpdate(ids);};
LineGraph.prototype._onRemove = function (ids) {this._onUpdate(ids);};
LineGraph.prototype._onUpdateGroups = function (groupIds) {
for (var i = 0; i < groupIds.length; i++) {
var group = this.groupsData.get(groupIds[i]);
this._updateGroup(group, groupIds[i]);
}
LineGraph.prototype._onAdd = function (ids) {
this._onUpdate(ids);
};
LineGraph.prototype._onRemove = function (ids) {
this._onUpdate(ids);
};
LineGraph.prototype._onUpdateGroups = function (groupIds) {
this._updateAllGroupData();
this.redraw(true); this.redraw(true);
}; };
LineGraph.prototype._onAddGroups = function (groupIds) {this._onUpdateGroups(groupIds);};
LineGraph.prototype._onAddGroups = function (groupIds) {
this._onUpdateGroups(groupIds);
};
/** /**
@ -446,7 +454,9 @@ LineGraph.prototype._updateGroup = function (group, groupId) {
LineGraph.prototype._updateAllGroupData = function () { LineGraph.prototype._updateAllGroupData = function () {
if (this.itemsData != null) { if (this.itemsData != null) {
var groupsContent = {}; var groupsContent = {};
this.itemsData.get().forEach(function(item){
var items = this.itemsData.get();
for (var i = 0; i < items.length; i++) {
var item = items[i];
var groupId = item.group; var groupId = item.group;
if (groupId === null || groupId === undefined) { if (groupId === null || groupId === undefined) {
groupId = UNGROUPED; groupId = UNGROUPED;
@ -454,11 +464,13 @@ LineGraph.prototype._updateAllGroupData = function () {
if (groupsContent[groupId] === undefined) { if (groupsContent[groupId] === undefined) {
groupsContent[groupId] = []; groupsContent[groupId] = [];
} }
var extended = Object.create(item);
var extended = util.bridgeObject(item);
extended.x = util.convert(item.x, 'Date'); extended.x = util.convert(item.x, 'Date');
extended.orginalY = item.y; //real Y
extended.y = item.y;
groupsContent[groupId].push(extended); groupsContent[groupId].push(extended);
});
//Update legendas and axis
}
//Update legendas, style and axis
for (var groupId in groupsContent) { for (var groupId in groupsContent) {
if (groupsContent.hasOwnProperty(groupId)) { if (groupsContent.hasOwnProperty(groupId)) {
if (groupsContent[groupId].length == 0) { if (groupsContent[groupId].length == 0) {
@ -466,13 +478,11 @@ LineGraph.prototype._updateAllGroupData = function () {
this._onRemoveGroups([groupId]); this._onRemoveGroups([groupId]);
} }
} else { } else {
if (!this.groups.hasOwnProperty(groupId)) {
var group = {id: groupId, content: this.options.defaultGroup};
if (this.groupsData && this.groupsData.hasOwnProperty(groupId)) {
group = this.groupsData[groupId];
}
this._updateGroup(group, groupId);
var group = this.groupsData.get(groupId);
if (group === undefined) {
group = {id: groupId, content: this.options.defaultGroup + groupId};
} }
this._updateGroup(group, groupId);
this.groups[groupId].setItems(groupsContent[groupId]); this.groups[groupId].setItems(groupsContent[groupId]);
} }
} }
@ -484,14 +494,14 @@ LineGraph.prototype._updateAllGroupData = function () {
* Redraw the component, mandatory function * Redraw the component, mandatory function
* @return {boolean} Returns true if the component is resized * @return {boolean} Returns true if the component is resized
*/ */
LineGraph.prototype.redraw = function(forceGraphUpdate) {
LineGraph.prototype.redraw = function (forceGraphUpdate) {
var resized = false; var resized = false;
// calculate actual size and position // calculate actual size and position
this.props.width = this.dom.frame.offsetWidth; this.props.width = this.dom.frame.offsetWidth;
this.props.height = this.body.domProps.centerContainer.height this.props.height = this.body.domProps.centerContainer.height
- this.body.domProps.border.top
- this.body.domProps.border.bottom;
- this.body.domProps.border.top
- this.body.domProps.border.bottom;
// update the graph if there is no lastWidth or with, used for the initial draw // update the graph if there is no lastWidth or with, used for the initial draw
if (this.lastWidth === undefined && this.props.width) { if (this.lastWidth === undefined && this.props.width) {
@ -510,7 +520,7 @@ LineGraph.prototype.redraw = function(forceGraphUpdate) {
// the svg element is three times as big as the width, this allows for fully dragging left and right // the svg element is three times as big as the width, this allows for fully dragging left and right
// without reloading the graph. the controls for this are bound to events in the constructor // without reloading the graph. the controls for this are bound to events in the constructor
if (resized == true) { if (resized == true) {
this.svg.style.width = util.option.asSize(3*this.props.width);
this.svg.style.width = util.option.asSize(3 * this.props.width);
this.svg.style.left = util.option.asSize(-this.props.width); this.svg.style.left = util.option.asSize(-this.props.width);
// if the height of the graph is set as proportional, change the height of the svg // if the height of the graph is set as proportional, change the height of the svg
@ -528,7 +538,7 @@ LineGraph.prototype.redraw = function(forceGraphUpdate) {
this.updateSVGheight = false; this.updateSVGheight = false;
} }
else { else {
this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
this.svg.style.height = ('' + this.options.graphHeight).replace('px', '') + 'px';
} }
// zoomed is here to ensure that animations are shown correctly. // zoomed is here to ensure that animations are shown correctly.
@ -541,7 +551,7 @@ LineGraph.prototype.redraw = function(forceGraphUpdate) {
var offset = this.body.range.start - this.lastStart; var offset = this.body.range.start - this.lastStart;
var range = this.body.range.end - this.body.range.start; var range = this.body.range.end - this.body.range.start;
if (this.props.width != 0) { if (this.props.width != 0) {
var rangePerPixelInv = this.props.width/range;
var rangePerPixelInv = this.props.width / range;
var xOffset = offset * rangePerPixelInv; var xOffset = offset * rangePerPixelInv;
this.svg.style.left = (-this.props.width - xOffset) + 'px'; this.svg.style.left = (-this.props.width - xOffset) + 'px';
} }
@ -583,6 +593,7 @@ LineGraph.prototype._updateGraph = function () {
var minDate = this.body.util.toGlobalTime(-this.body.domProps.root.width); var minDate = this.body.util.toGlobalTime(-this.body.domProps.root.width);
var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width); var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width);
var groupsData = {}; var groupsData = {};
// fill groups data, this only loads the data we require based on the timewindow // fill groups data, this only loads the data we require based on the timewindow
this._getRelevantData(groupIds, groupsData, minDate, maxDate); this._getRelevantData(groupIds, groupsData, minDate, maxDate);
@ -618,17 +629,64 @@ LineGraph.prototype._updateGraph = function () {
// With the yAxis scaled correctly, use this to get the Y values of the points. // With the yAxis scaled correctly, use this to get the Y values of the points.
for (i = 0; i < groupIds.length; i++) { for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]]; group = this.groups[groupIds[i]];
if (this.options.stack === true && this.options.style === 'line' && i > 0) {
this._stack(groupsData[groupIds[i]], groupsData[groupIds[i - 1]]);
}
processedGroupData[groupIds[i]] = this._convertYcoordinates(groupsData[groupIds[i]], group); processedGroupData[groupIds[i]] = this._convertYcoordinates(groupsData[groupIds[i]], group);
} }
// draw the groups
BarFunctions.draw(groupIds, processedGroupData, this.framework);
//Precalculate paths and draw shading if appropriate. This will make sure the shading is always behind any lines.
var paths = {};
for (i = 0; i < groupIds.length; i++) { for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]]; group = this.groups[groupIds[i]];
if (group.options.style != 'bar') { // bar needs to be drawn enmasse
group.draw(processedGroupData[groupIds[i]], group, this.framework);
if (group.options.style === 'line' && group.options.shaded.enabled == true) {
var dataset = processedGroupData[groupIds[i]];
if (!paths.hasOwnProperty(groupIds[i])) {
paths[groupIds[i]] = Lines.calcPath(dataset, group);
}
if (group.options.shaded.orientation === "group") {
var subGroupId = group.options.shaded.groupId;
if (groupIds.indexOf(subGroupId) === -1) {
console.log("Unknown shading group target given:" + subGroupId);
continue;
}
if (!paths.hasOwnProperty(subGroupId)) {
paths[subGroupId] = Lines.calcPath(processedGroupData[subGroupId], this.groups[subGroupId]);
}
Lines.drawShading(paths[groupIds[i]], group, paths[subGroupId], this.framework);
}
else {
Lines.drawShading(paths[groupIds[i]], group, undefined, this.framework);
}
} }
} }
// draw the groups, calculating paths if still necessary.
Bars.draw(groupIds, processedGroupData, this.framework);
for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]];
if (processedGroupData[groupIds[i]].length > 0) {
switch (group.options.style) {
case "line":
if (!paths.hasOwnProperty(groupIds[i])) {
paths[groupIds[i]] = Lines.calcPath(processedGroupData[groupIds[i]], group);
}
Lines.draw(paths[groupIds[i]], group, this.framework);
//explicit no break;
case "points":
if (group.options.style == "points" || group.options.drawPoints.enabled == true) {
Points.draw(processedGroupData[groupIds[i]], group, this.framework);
}
break;
case "bar":
// bar needs to be drawn enmasse
//explicit no break
default:
//do nothing...
}
}
}
} }
} }
} }
@ -638,6 +696,51 @@ LineGraph.prototype._updateGraph = function () {
return false; return false;
}; };
LineGraph.prototype._stack = function (data, subData) {
var index, dx, dy, subPrevPoint, subNextPoint;
index = 0;
// for each data point we look for a matching on in the set below
for (var j = 0; j < data.length; j++) {
subPrevPoint = undefined;
subNextPoint = undefined;
// we look for time matches or a before-after point
for (var k = index; k < subData.length; k++) {
// if times match exactly
if (subData[k].x === data[j].x) {
subPrevPoint = subData[k];
subNextPoint = subData[k];
index = k;
break;
}
else if (subData[k].x > data[j].x) { // overshoot
subNextPoint = subData[k];
if (k == 0) {
subPrevPoint = subNextPoint;
}
else {
subPrevPoint = subData[k - 1];
}
index = k;
break;
}
}
// in case the last data point has been used, we assume it stays like this.
if (subNextPoint === undefined) {
subPrevPoint = subData[subData.length - 1];
subNextPoint = subData[subData.length - 1];
}
// linear interpolation
dx = subNextPoint.x - subPrevPoint.x;
dy = subNextPoint.y - subPrevPoint.y;
if (dx == 0) {
data[j].y = data[j].orginalY + subNextPoint.y;
}
else {
data[j].y = data[j].orginalY + (dy / dx) * (data[j].x - subPrevPoint.x) + subPrevPoint.y; // ax + b where b is data[j].y
}
}
}
/** /**
* first select and preprocess the data from the datasets. * first select and preprocess the data from the datasets.
@ -658,32 +761,21 @@ LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate,
for (i = 0; i < groupIds.length; i++) { for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]]; group = this.groups[groupIds[i]];
groupsData[groupIds[i]] = []; groupsData[groupIds[i]] = [];
var dataContainer = groupsData[groupIds[i]];
// optimization for sorted data // optimization for sorted data
if (group.options.sort == true) { if (group.options.sort == true) {
var dataContainer = groupsData[groupIds[i]];
var guess = Math.max(0, util.binarySearchValue(group.itemsData, minDate, 'x', 'before')); var guess = Math.max(0, util.binarySearchValue(group.itemsData, minDate, 'x', 'before'));
for (j = guess; j < group.itemsData.length; j++) { for (j = guess; j < group.itemsData.length; j++) {
item = group.itemsData[j]; item = group.itemsData[j];
if (item !== undefined) {
if (item.x > maxDate) {
dataContainer.push(item);
break;
}
else {
dataContainer.push(item);
}
dataContainer.push(item);
if (item.x > maxDate) {
break;
} }
} }
} }
else { else {
for (j = 0; j < group.itemsData.length; j++) {
item = group.itemsData[j];
if (item !== undefined) {
if (item.x > minDate && item.x < maxDate) {
dataContainer.push(item);
}
}
}
// If unsorted data, all data is relevant, just returning entire structure
groupsData[groupIds[i]] = group.itemsData;
} }
} }
} }
@ -747,18 +839,22 @@ LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) {
group = this.groups[groupIds[i]]; group = this.groups[groupIds[i]];
// if bar graphs are stacked, their range need to be handled differently and accumulated over all groups. // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups.
if (options.stack === true && options.style === 'bar') { if (options.stack === true && options.style === 'bar') {
if (options.yAxisOrientation === 'left') {combinedDataLeft = combinedDataLeft .concat(group.getData(groupData));}
else {combinedDataRight = combinedDataRight.concat(group.getData(groupData));}
if (options.yAxisOrientation === 'left') {
combinedDataLeft = combinedDataLeft.concat(group.getData(groupData));
}
else {
combinedDataRight = combinedDataRight.concat(group.getData(groupData));
}
} }
else { else {
groupRanges[groupIds[i]] = group.getYRange(groupData,groupIds[i]);
groupRanges[groupIds[i]] = group.getYRange(groupData, groupIds[i]);
} }
} }
} }
// if bar graphs are stacked, their range need to be handled differently and accumulated over all groups. // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups.
BarFunctions.getStackedYRange(combinedDataLeft , groupRanges, groupIds, '__barStackLeft' , 'left' );
BarFunctions.getStackedYRange(combinedDataRight, groupRanges, groupIds, '__barStackRight', 'right');
Bars.getStackedYRange(combinedDataLeft, groupRanges, groupIds, '__barStackLeft', 'left');
Bars.getStackedYRange(combinedDataRight, groupRanges, groupIds, '__barStackRight', 'right');
// if line graphs are stacked, their range need to be handled differently and accumulated over all groups. // if line graphs are stacked, their range need to be handled differently and accumulated over all groups.
//LineFunctions.getStackedYRange(combinedDataLeft , groupRanges, groupIds, '__lineStackLeft' , 'left' ); //LineFunctions.getStackedYRange(combinedDataLeft , groupRanges, groupIds, '__lineStackLeft' , 'left' );
//LineFunctions.getStackedYRange(combinedDataRight, groupRanges, groupIds, '__lineStackRight', 'right'); //LineFunctions.getStackedYRange(combinedDataRight, groupRanges, groupIds, '__lineStackRight', 'right');
@ -823,7 +919,7 @@ LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
this.yAxisRight.setRange(minRight, maxRight); this.yAxisRight.setRange(minRight, maxRight);
} }
} }
resized = this._toggleAxisVisiblity(yAxisLeftUsed , this.yAxisLeft) || resized;
resized = this._toggleAxisVisiblity(yAxisLeftUsed, this.yAxisLeft) || resized;
resized = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || resized; resized = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || resized;
if (yAxisRightUsed == true && yAxisLeftUsed == true) { if (yAxisRightUsed == true && yAxisLeftUsed == true) {
@ -836,8 +932,12 @@ LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
} }
this.yAxisRight.master = !yAxisLeftUsed; this.yAxisRight.master = !yAxisLeftUsed;
if (this.yAxisRight.master == false) { if (this.yAxisRight.master == false) {
if (yAxisRightUsed == true) {this.yAxisLeft.lineOffset = this.yAxisRight.width;}
else {this.yAxisLeft.lineOffset = 0;}
if (yAxisRightUsed == true) {
this.yAxisLeft.lineOffset = this.yAxisRight.width;
}
else {
this.yAxisLeft.lineOffset = 0;
}
resized = this.yAxisLeft.redraw() || resized; resized = this.yAxisLeft.redraw() || resized;
this.yAxisRight.stepPixels = this.yAxisLeft.stepPixels; this.yAxisRight.stepPixels = this.yAxisLeft.stepPixels;
@ -850,9 +950,11 @@ LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
} }
// clean the accumulated lists // clean the accumulated lists
var tempGroups = ['__barStackLeft','__barStackRight','__lineStackLeft','__lineStackRight'];
var tempGroups = ['__barStackLeft', '__barStackRight', '__lineStackLeft', '__lineStackRight'];
for (var i = 0; i < tempGroups.length; i++) { for (var i = 0; i < tempGroups.length; i++) {
if (groupIds.indexOf(tempGroups[i]) != -1) {groupIds.splice(groupIds.indexOf(tempGroups[i]),1);}
if (groupIds.indexOf(tempGroups[i]) != -1) {
groupIds.splice(groupIds.indexOf(tempGroups[i]), 1);
}
} }
return resized; return resized;
@ -924,7 +1026,7 @@ LineGraph.prototype._convertYcoordinates = function (datapoints, group) {
var xValue, yValue; var xValue, yValue;
var toScreen = this.body.util.toScreen; var toScreen = this.body.util.toScreen;
var axis = this.yAxisLeft; var axis = this.yAxisLeft;
var svgHeight = Number(this.svg.style.height.replace('px',''));
var svgHeight = Number(this.svg.style.height.replace('px', ''));
if (group.options.yAxisOrientation == 'right') { if (group.options.yAxisOrientation == 'right') {
axis = this.yAxisRight; axis = this.yAxisRight;
} }
@ -933,7 +1035,7 @@ LineGraph.prototype._convertYcoordinates = function (datapoints, group) {
var labelValue = datapoints[i].label ? datapoints[i].label : null; var labelValue = datapoints[i].label ? datapoints[i].label : null;
xValue = toScreen(datapoints[i].x) + this.props.width; xValue = toScreen(datapoints[i].x) + this.props.width;
yValue = Math.round(axis.convertValue(datapoints[i].y)); yValue = Math.round(axis.convertValue(datapoints[i].y));
extractedData.push({x: xValue, y: yValue, label:labelValue});
extractedData.push({x: xValue, y: yValue, label: labelValue});
} }
group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0))); group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0)));

+ 232
- 237
lib/timeline/component/graph2d_types/line.js View File

@ -1,82 +1,30 @@
var DOMutil = require('../../../DOMutil'); var DOMutil = require('../../../DOMutil');
var Points = require('./points');
function Line(groupId, options) { function Line(groupId, options) {
this.groupId = groupId;
this.options = options;
this.groupId = groupId;
this.options = options;
} }
Line.prototype.getData = function(groupData) {
var combinedData = [];
for (var j = 0; j < groupData.length; j++) {
combinedData.push({
x: groupData[j].x,
y: groupData[j].y,
groupId: this.groupId
});
}
return combinedData;
}
Line.prototype.getYRange = function(groupData) {
var yMin = groupData[0].y;
var yMax = groupData[0].y;
for (var j = 0; j < groupData.length; j++) {
yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
}
return {min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation};
};
Line.getStackedYRange = function(combinedData, groupRanges, groupIds, groupLabel, orientation) {
if (combinedData.length > 0) {
// sort by time and by group
combinedData.sort(function (a, b) {
if (a.x === b.x) {
return a.groupId < b.groupId ? -1 : 1;
}
else {
return a.x - b.x;
}
});
var intersections = {};
Line._getDataIntersections(intersections, combinedData);
groupRanges[groupLabel] = Line._getStackedYRange(intersections, combinedData);
groupRanges[groupLabel].yAxisOrientation = orientation;
groupIds.push(groupLabel);
}
Line.prototype.getData = function (groupData) {
var combinedData = [];
for (var j = 0; j < groupData.length; j++) {
combinedData.push({
x: groupData[j].x,
y: groupData[j].y,
groupId: this.groupId
});
}
return combinedData;
} }
Line._getStackedYRange = function (intersections, combinedData) {
var key;
var yMin = combinedData[0].y;
var yMax = combinedData[0].y;
for (var i = 0; i < combinedData.length; i++) {
key = combinedData[i].x;
if (intersections[key] === undefined) {
yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin;
yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax;
Line.prototype.getYRange = function (groupData) {
var yMin = groupData[0].y;
var yMax = groupData[0].y;
for (var j = 0; j < groupData.length; j++) {
yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
} }
else {
if (combinedData[i].y < 0) {
intersections[key].accumulatedNegative += combinedData[i].y;
}
else {
intersections[key].accumulatedPositive += combinedData[i].y;
}
}
}
for (var xpos in intersections) {
if (intersections.hasOwnProperty(xpos)) {
yMin = yMin > intersections[xpos].accumulatedNegative ? intersections[xpos].accumulatedNegative : yMin;
yMin = yMin > intersections[xpos].accumulatedPositive ? intersections[xpos].accumulatedPositive : yMin;
yMax = yMax < intersections[xpos].accumulatedNegative ? intersections[xpos].accumulatedNegative : yMax;
yMax = yMax < intersections[xpos].accumulatedPositive ? intersections[xpos].accumulatedPositive : yMax;
}
}
return {min: yMin, max: yMax};
return {min: yMin, max: yMax, yAxisOrientation: this.options.yAxisOrientation};
}; };
/** /**
@ -86,82 +34,128 @@ Line._getStackedYRange = function (intersections, combinedData) {
* @private * @private
*/ */
Line._getDataIntersections = function (intersections, combinedData) { Line._getDataIntersections = function (intersections, combinedData) {
// get intersections
var coreDistance;
for (var i = 0; i < combinedData.length; i++) {
if (i + 1 < combinedData.length) {
coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x);
}
if (i > 0) {
coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x));
}
if (coreDistance === 0) {
if (intersections[combinedData[i].x] === undefined) {
intersections[combinedData[i].x] = {amount: 0, resolved: 0, accumulatedPositive: 0, accumulatedNegative: 0};
}
intersections[combinedData[i].x].amount += 1;
// get intersections
var coreDistance;
for (var i = 0; i < combinedData.length; i++) {
if (i + 1 < combinedData.length) {
coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x);
}
if (i > 0) {
coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x));
}
if (coreDistance === 0) {
if (intersections[combinedData[i].x] === undefined) {
intersections[combinedData[i].x] = {
amount: 0,
resolved: 0,
accumulatedPositive: 0,
accumulatedNegative: 0
};
}
intersections[combinedData[i].x].amount += 1;
}
} }
}
}; };
Line.calcPath = function (dataset, group) {
if (dataset != null) {
if (dataset.length > 0) {
var d = [];
// construct path from dataset
if (group.options.interpolation.enabled == true) {
d = Line._catmullRom(dataset, group);
}
else {
d = Line._linear(dataset);
}
return d;
}
}
}
/**
* draw a line graph
*
* @param dataset
* @param group
*/
Line.prototype.draw = function (dataset, group, framework) {
if (dataset != null) {
if (dataset.length > 0) {
var path, d;
var svgHeight = Number(framework.svg.style.height.replace('px',''));
path = DOMutil.getSVGElement('path', framework.svgElements, framework.svg);
path.setAttributeNS(null, "class", group.className);
if(group.style !== undefined) {
path.setAttributeNS(null, "style", group.style);
}
// construct path from dataset
if (group.options.interpolation.enabled == true) {
d = Line._catmullRom(dataset, group);
}
else {
d = Line._linear(dataset);
}
// append with points for fill and finalize the path
if (group.options.shaded.enabled == true) {
Line.drawShading = function (pathArray, group, subPathArray, framework) {
// append shading to the path
if (group.options.shaded.enabled == true) {
var svgHeight = Number(framework.svg.style.height.replace('px',''));
var fillPath = DOMutil.getSVGElement('path', framework.svgElements, framework.svg); var fillPath = DOMutil.getSVGElement('path', framework.svgElements, framework.svg);
var type = "L";
if (group.options.interpolation.enabled == true){
type = "C";
}
var dFill;
var zero = 0; var zero = 0;
if (group.options.shaded.orientation == 'top') { if (group.options.shaded.orientation == 'top') {
zero = 0;
zero = 0;
} }
else if (group.options.shaded.orientation == 'bottom') { else if (group.options.shaded.orientation == 'bottom') {
zero = svgHeight;
zero = svgHeight;
}
else {
zero = Math.min(Math.max(0, group.zeroPosition), svgHeight);
}
if (group.options.shaded.orientation == 'group' && (subPathArray != null && subPathArray != undefined)) {
dFill = 'M' + pathArray[0][0]+ ","+pathArray[0][1] + " " +
this.serializePath(pathArray,type,false) +
' L'+ subPathArray[subPathArray.length-1][0]+ "," + subPathArray[subPathArray.length-1][1] + " " +
this.serializePath(subPathArray,type,true) +
subPathArray[0][0]+ ","+subPathArray[0][1] + " Z";
} }
else { else {
zero = Math.min(Math.max(0,group.zeroPosition),svgHeight);
dFill = 'M' + pathArray[0][0]+ ","+pathArray[0][1] + " " +
this.serializePath(pathArray,type,false) +
' V' + zero + ' H'+ pathArray[0][0] + " Z";
} }
var dFill = 'M' + dataset[0].x + ',' + zero + ' ' + d + 'L' + dataset[dataset.length - 1].x + ',' + zero;
fillPath.setAttributeNS(null, 'class', group.className + ' vis-fill'); fillPath.setAttributeNS(null, 'class', group.className + ' vis-fill');
if(group.options.shaded.style !== undefined) {
fillPath.setAttributeNS(null, 'style', group.options.shaded.style);
if (group.options.shaded.style !== undefined) {
fillPath.setAttributeNS(null, 'style', group.options.shaded.style);
} }
fillPath.setAttributeNS(null, 'd', dFill); fillPath.setAttributeNS(null, 'd', dFill);
}
// copy properties to path for drawing.
path.setAttributeNS(null, 'd', 'M' + d);
// draw points
if (group.options.drawPoints.enabled == true) {
Points.draw(dataset, group, framework);
}
} }
}
};
}
/**
* draw a line graph
*
* @param dataset
* @param group
*/
Line.draw = function (pathArray, group, framework) {
if (pathArray != null && pathArray != undefined) {
var path = DOMutil.getSVGElement('path', framework.svgElements, framework.svg);
path.setAttributeNS(null, "class", group.className);
if (group.style !== undefined) {
path.setAttributeNS(null, "style", group.style);
}
var type = "L";
if (group.options.interpolation.enabled == true){
type = "C";
}
// copy properties to path for drawing.
path.setAttributeNS(null, 'd', 'M' + pathArray[0][0]+ ","+pathArray[0][1] + " " + this.serializePath(pathArray,type,false));
}
};
Line.serializePath = function(pathArray,type,inverse){
if (pathArray.length <= 2){
//Too little data to create a path.
return "";
}
var d = type;
if (inverse){
for (var i = pathArray.length-2; i > 0; i--){
d += pathArray[i][0] + "," + pathArray[i][1] + " ";
}
}
else {
for (var i = 1; i < pathArray.length; i++){
d += pathArray[i][0] + "," + pathArray[i][1] + " ";
}
}
return d;
}
/** /**
* This uses an uniform parametrization of the interpolation algorithm: * This uses an uniform parametrization of the interpolation algorithm:
@ -170,41 +164,38 @@ Line.prototype.draw = function (dataset, group, framework) {
* @returns {string} * @returns {string}
* @private * @private
*/ */
Line._catmullRomUniform = function(data) {
// catmull rom
var p0, p1, p2, p3, bp1, bp2;
var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' ';
var normalization = 1/6;
var length = data.length;
for (var i = 0; i < length - 1; i++) {
p0 = (i == 0) ? data[0] : data[i-1];
p1 = data[i];
p2 = data[i+1];
p3 = (i + 2 < length) ? data[i+2] : p2;
// Catmull-Rom to Cubic Bezier conversion matrix
// 0 1 0 0
// -1/6 1 1/6 0
// 0 1/6 1 -1/6
// 0 0 1 0
// bp0 = { x: p1.x, y: p1.y };
bp1 = { x: ((-p0.x + 6*p1.x + p2.x) *normalization), y: ((-p0.y + 6*p1.y + p2.y) *normalization)};
bp2 = { x: (( p1.x + 6*p2.x - p3.x) *normalization), y: (( p1.y + 6*p2.y - p3.y) *normalization)};
// bp0 = { x: p2.x, y: p2.y };
d += 'C' +
bp1.x + ',' +
bp1.y + ' ' +
bp2.x + ',' +
bp2.y + ' ' +
p2.x + ',' +
p2.y + ' ';
}
return d;
Line._catmullRomUniform = function (data) {
// catmull rom
var p0, p1, p2, p3, bp1, bp2;
var d = [];
d.push( [ Math.round(data[0].x) , Math.round(data[0].y) ]);
var normalization = 1 / 6;
var length = data.length;
for (var i = 0; i < length - 1; i++) {
p0 = (i == 0) ? data[0] : data[i - 1];
p1 = data[i];
p2 = data[i + 1];
p3 = (i + 2 < length) ? data[i + 2] : p2;
// Catmull-Rom to Cubic Bezier conversion matrix
// 0 1 0 0
// -1/6 1 1/6 0
// 0 1/6 1 -1/6
// 0 0 1 0
// bp0 = { x: p1.x, y: p1.y };
bp1 = {x: ((-p0.x + 6 * p1.x + p2.x) * normalization), y: ((-p0.y + 6 * p1.y + p2.y) * normalization)};
bp2 = {x: (( p1.x + 6 * p2.x - p3.x) * normalization), y: (( p1.y + 6 * p2.y - p3.y) * normalization)};
// bp0 = { x: p2.x, y: p2.y };
d.push( [ bp1.x , bp1.y ]);
d.push( [ bp2.x , bp2.y ]);
d.push( [ p2.x , p2.y ]);
}
return d;
}; };
/** /**
@ -218,70 +209,79 @@ Line._catmullRomUniform = function(data) {
* @returns {string} * @returns {string}
* @private * @private
*/ */
Line._catmullRom = function(data, group) {
var alpha = group.options.interpolation.alpha;
if (alpha == 0 || alpha === undefined) {
return this._catmullRomUniform(data);
}
else {
var p0, p1, p2, p3, bp1, bp2, d1,d2,d3, A, B, N, M;
var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
var d = Math.round(data[0].x) + ',' + Math.round(data[0].y) + ' ';
var length = data.length;
for (var i = 0; i < length - 1; i++) {
p0 = (i == 0) ? data[0] : data[i-1];
p1 = data[i];
p2 = data[i+1];
p3 = (i + 2 < length) ? data[i+2] : p2;
d1 = Math.sqrt(Math.pow(p0.x - p1.x,2) + Math.pow(p0.y - p1.y,2));
d2 = Math.sqrt(Math.pow(p1.x - p2.x,2) + Math.pow(p1.y - p2.y,2));
d3 = Math.sqrt(Math.pow(p2.x - p3.x,2) + Math.pow(p2.y - p3.y,2));
// Catmull-Rom to Cubic Bezier conversion matrix
// A = 2d1^2a + 3d1^a * d2^a + d3^2a
// B = 2d3^2a + 3d3^a * d2^a + d2^2a
// [ 0 1 0 0 ]
// [ -d2^2a /N A/N d1^2a /N 0 ]
// [ 0 d3^2a /M B/M -d2^2a /M ]
// [ 0 0 1 0 ]
d3powA = Math.pow(d3, alpha);
d3pow2A = Math.pow(d3,2*alpha);
d2powA = Math.pow(d2, alpha);
d2pow2A = Math.pow(d2,2*alpha);
d1powA = Math.pow(d1, alpha);
d1pow2A = Math.pow(d1,2*alpha);
A = 2*d1pow2A + 3*d1powA * d2powA + d2pow2A;
B = 2*d3pow2A + 3*d3powA * d2powA + d2pow2A;
N = 3*d1powA * (d1powA + d2powA);
if (N > 0) {N = 1 / N;}
M = 3*d3powA * (d3powA + d2powA);
if (M > 0) {M = 1 / M;}
bp1 = { x: ((-d2pow2A * p0.x + A*p1.x + d1pow2A * p2.x) * N),
y: ((-d2pow2A * p0.y + A*p1.y + d1pow2A * p2.y) * N)};
bp2 = { x: (( d3pow2A * p1.x + B*p2.x - d2pow2A * p3.x) * M),
y: (( d3pow2A * p1.y + B*p2.y - d2pow2A * p3.y) * M)};
if (bp1.x == 0 && bp1.y == 0) {bp1 = p1;}
if (bp2.x == 0 && bp2.y == 0) {bp2 = p2;}
d += 'C' +
bp1.x + ',' +
bp1.y + ' ' +
bp2.x + ',' +
bp2.y + ' ' +
p2.x + ',' +
p2.y + ' ';
Line._catmullRom = function (data, group) {
var alpha = group.options.interpolation.alpha;
if (alpha == 0 || alpha === undefined) {
return this._catmullRomUniform(data);
} }
else {
var p0, p1, p2, p3, bp1, bp2, d1, d2, d3, A, B, N, M;
var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
var d = [];
d.push( [ Math.round(data[0].x) , Math.round(data[0].y) ]);
var length = data.length;
for (var i = 0; i < length - 1; i++) {
p0 = (i == 0) ? data[0] : data[i - 1];
p1 = data[i];
p2 = data[i + 1];
p3 = (i + 2 < length) ? data[i + 2] : p2;
d1 = Math.sqrt(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2));
d2 = Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
d3 = Math.sqrt(Math.pow(p2.x - p3.x, 2) + Math.pow(p2.y - p3.y, 2));
// Catmull-Rom to Cubic Bezier conversion matrix
// A = 2d1^2a + 3d1^a * d2^a + d3^2a
// B = 2d3^2a + 3d3^a * d2^a + d2^2a
// [ 0 1 0 0 ]
// [ -d2^2a /N A/N d1^2a /N 0 ]
// [ 0 d3^2a /M B/M -d2^2a /M ]
// [ 0 0 1 0 ]
d3powA = Math.pow(d3, alpha);
d3pow2A = Math.pow(d3, 2 * alpha);
d2powA = Math.pow(d2, alpha);
d2pow2A = Math.pow(d2, 2 * alpha);
d1powA = Math.pow(d1, alpha);
d1pow2A = Math.pow(d1, 2 * alpha);
A = 2 * d1pow2A + 3 * d1powA * d2powA + d2pow2A;
B = 2 * d3pow2A + 3 * d3powA * d2powA + d2pow2A;
N = 3 * d1powA * (d1powA + d2powA);
if (N > 0) {
N = 1 / N;
}
M = 3 * d3powA * (d3powA + d2powA);
if (M > 0) {
M = 1 / M;
}
bp1 = {
x: ((-d2pow2A * p0.x + A * p1.x + d1pow2A * p2.x) * N),
y: ((-d2pow2A * p0.y + A * p1.y + d1pow2A * p2.y) * N)
};
bp2 = {
x: (( d3pow2A * p1.x + B * p2.x - d2pow2A * p3.x) * M),
y: (( d3pow2A * p1.y + B * p2.y - d2pow2A * p3.y) * M)
};
if (bp1.x == 0 && bp1.y == 0) {
bp1 = p1;
}
if (bp2.x == 0 && bp2.y == 0) {
bp2 = p2;
}
d.push( [ bp1.x , bp1.y ]);
d.push( [ bp2.x , bp2.y ]);
d.push( [ p2.x , p2.y ]);
}
return d;
}
return d;
}
}; };
/** /**
@ -290,18 +290,13 @@ Line._catmullRom = function(data, group) {
* @returns {string} * @returns {string}
* @private * @private
*/ */
Line._linear = function(data) {
// linear
var d = '';
for (var i = 0; i < data.length; i++) {
if (i == 0) {
d += data[i].x + ',' + data[i].y;
}
else {
d += ' ' + data[i].x + ',' + data[i].y;
Line._linear = function (data) {
// linear
var d = [];
for (var i = 0; i < data.length; i++) {
d.push([ data[i].x , data[i].y ]);
} }
}
return d;
return d;
}; };
module.exports = Line; module.exports = Line;

+ 3
- 2
lib/timeline/optionsGraph2d.js View File

@ -33,7 +33,8 @@ let allOptions = {
graphHeight: {string, number}, graphHeight: {string, number},
shaded: { shaded: {
enabled: {boolean}, enabled: {boolean},
orientation: {string:['bottom','top','zero']}, // top, bottom, zero
orientation: {string:['bottom','top','zero','group']}, // top, bottom, zero, group
groupId: {object},
__type__: {boolean,object} __type__: {boolean,object}
}, },
style: {string:['line','bar','points']}, // line, bar style: {string:['line','bar','points']}, // line, bar
@ -172,7 +173,7 @@ let configureOptions = {
stack:false, stack:false,
shaded: { shaded: {
enabled: false, enabled: false,
orientation: ['zero','top','bottom'] // zero, top, bottom
orientation: ['zero','top','bottom','group'] // zero, top, bottom
}, },
style: ['line','bar','points'], // line, bar style: ['line','bar','points'], // line, bar
barChart: { barChart: {

Loading…
Cancel
Save