Browse Source

fixed small bug in network, refactored graph2d. Added handleOverlap for barcharts

v3_develop
Alex de Mulder 10 years ago
parent
commit
a4b9352150
9 changed files with 951 additions and 678 deletions
  1. +0
    -1
      HISTORY.md
  2. +577
    -426
      dist/vis.js
  3. +5
    -42
      docs/graph2d.html
  4. +14
    -10
      examples/graph2d/10_barsSideBySide.html
  5. +24
    -19
      examples/graph2d/11_barsSideBySideGroups.html
  6. +4
    -4
      examples/graph2d/12_customRange.html
  7. +3
    -6
      lib/network/Edge.js
  8. +2
    -2
      lib/timeline/component/GraphGroup.js
  9. +322
    -168
      lib/timeline/component/LineGraph.js

+ 0
- 1
HISTORY.md View File

@ -17,7 +17,6 @@ http://visjs.org
### Graph2D
- Added 'slots' for groups when using barCharts.
- Added 'allowOverlap' option for barCharts.
- Added two examples showing the two additions above.
- Added 'customRange' for the Y axis and an example showing how it works.

+ 577
- 426
dist/vis.js
File diff suppressed because it is too large
View File


+ 5
- 42
docs/graph2d.html View File

@ -37,7 +37,6 @@
<ul>
<li><a href="#items">Items</a></li>
<li><a href="#groups">Groups</a></li>
<li><a href="#GroupOptions">Group-specific options</a></li>
</ul>
</li>
<li><a href="#Configuration_Options">Configuration Options</a>
@ -251,43 +250,6 @@ groups.add({
</tr>
</table>
<h3 id="GroupOptions">Group-specific Options</h3>
The groups have some options that only apply to groups and cannot be set globally. These are listed below.
<pre class="prettyprint lang-js">
var groups = new vis.DataSet();
groups.add({
id: 1,
content: 'Group 1',
options: {
slots: { slot: 1, total: 3}
}
});
</pre>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Required</th>
<th>Description</th>
</tr>
<tr>
<td>slots.slot</td>
<td>Number</td>
<td></td>
<td>The slot the bar chart is plotted in. This has to be used in combination with slots.total. See <a href="../examples/graph2d/11_barsSideBySideGroups.html">example 11</a> for more information.</td>
</tr>
<tr>
<td>slots.total</td>
<td>Number</td>
<td></td>
<td>The total amount of slots available. This has to be used in combination with slots.slot. See <a href="../examples/graph2d/11_barsSideBySideGroups.html">example 11</a> for more information.</td>
</tr>
</table>
<h2 id="Configuration_Options">Configuration Options</h2>
<h3 id="graph2dOptions">Graph2d Options</h3>
@ -384,10 +346,11 @@ The options colored in green can also be used as options for the groups. All opt
<td>The alignment of the bars with regards to the coordinate. The options are 'left', 'right' or 'center'.</td>
</tr>
<tr>
<td class="greenField">barChart.allowOverlap</td>
<td>Boolean</td>
<td>true</td>
<td>When true, bars that have the same x coordinate are plotted on top of eachother (not stacking).
<td class="greenField">barChart.handleOverlap</td>
<td>String</td>
<td>'overlap'</td>
<td>You can choose how graph2d handles the case where barcharts are occupying the same datapoint. The possible options are:
<code>overlap, sideBySide, stack</code>.
See <a href="../examples/graph2d/10_barsSideBySide.html">example 10</a> for more information.
When using groups, see <a href="../examples/graph2d/11_barsSideBySideGroups.html">example 11</a>.
</td>

+ 14
- 10
examples/graph2d/10_barsSideBySide.html View File

@ -1,7 +1,7 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Graph2d | Bar Graph Example</title>
<title>Graph2d | Bar Graphs Side by Side Example</title>
<style type="text/css">
body, html {
@ -15,12 +15,16 @@
<body>
<h2>Graph2d | Bar Graphs Side by Side Example</h2>
<div style="width:700px; font-size:14px; text-align: justify;">
When using Bar graphs, it can often be the case that there are multiple bars on the same timepoint. This may not always be the desired result. If you're not using groups, you can use the
barChart.allowOverlap option to automatically plot the bars next to eachother if they occupy the same timeslot. By default, this option is on, making the bars overlap. If the option is disabled, the overlapping
bars are plotted side by side. Use the toggle below to switch between settings.
When using Bar graphs, it can often be the case that there are multiple bars on the same timepoint. This may not always be the desired result. You can use the
barChart.handleOverlap option to automatically plot the bars next to eachother or stacked on top of eachother if they occupy the same timeslot. By default, this option is on, the bars overlap.
Use the dropdown box to experiment with the options. The stacked only really makes sense when using groups as is shown in the <a href="./11_barsSideBySideGroups.html">next example</a>.
<br /><br />
Allow overlap: <input type="checkbox" id="allowOverlap" checked="checked">
Handle overlap: <select id="dropdownID">
<option value="overlap">overlap</option>
<option value="sideBySide">sideBySide</option>
<option value="stack">stack</option>
</select><br/>
</div>
<br />
@ -29,8 +33,6 @@
<script type="text/javascript">
var container = document.getElementById('visualization');
var checkbox = document.getElementById('allowOverlap');
var items = [
{x: '2014-06-11', y: 10},
{x: '2014-06-12', y: 25},
@ -58,10 +60,12 @@
};
var graph2d = new vis.Graph2d(container, items, options);
checkbox.onchange = toggleOption;
function toggleOption() {
var options = {barChart:{allowOverlap:checkbox.checked}};
var dropdown = document.getElementById("dropdownID");
dropdown.onchange = update;
function update() {
var options = {barChart:{handleOverlap:dropdown.value}};
graph2d.setOptions(options);
}

+ 24
- 19
examples/graph2d/11_barsSideBySideGroups.html View File

@ -1,7 +1,7 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Graph2d | Bar Graph Example</title>
<title>Graph2d | Bar Graphs Side by Side Example with Groups</title>
<style type="text/css">
body, html {
@ -13,22 +13,18 @@
<link href="../../dist/vis.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h2>Graph2d | Bar Graphs Side by Side Example</h2>
<h2>Graph2d | Bar Graphs Side by Side Example with Groups</h2>
<div style="width:700px; font-size:14px; text-align: justify;">
When using Bar graphs, it can often be the case that there are multiple bars on the same timepoint. This may not always be the desired result. Since groups are plotted one at a time, the allowOverlap option does
not work here. For groups you can use the "slots" option.
<pre class="prettyprint lang-js">
var options = {
slots: {
slot: 1,
total:3
}
};
</pre>
When using Bar graphs, it can often be the case that there are multiple bars on the same timepoint. This may not always be the desired result. You can use the
barChart.handleOverlap option to automatically plot the bars next to eachother or stacked on top of eachother if they occupy the same timeslot. By default, this option is on, the bars overlap.
Use the dropdown box to experiment with the options. The stacked only really makes sense when using groups as is shown here.
<br /><br />
The slots tell the group where to plot it's bar. Each group is given a slot and the total amount of slots. Using this, the bars are plotted side by side neatly.
Handle overlap: <select id="dropdownID">
<option value="overlap">overlap</option>
<option value="sideBySide" selected="selected">sideBySide</option>
<option value="stack">stack</option>
</select>
</div>
<br />
@ -38,9 +34,9 @@ var options = {
var container = document.getElementById('visualization');
var groups = new vis.DataSet();
groups.add({id: 0, content: "group0", options: {slots:{slot:1,total:3}}})
groups.add({id: 1, content: "group1", options: {slots:{slot:2,total:3}}})
groups.add({id: 2, content: "group2", options: {slots:{slot:3,total:3}}})
groups.add({id: 0, content: "group0"})
groups.add({id: 1, content: "group1"})
groups.add({id: 2, content: "group2"})
var items = [
{x: '2014-06-11', y: 10, group:0},
@ -66,8 +62,8 @@ var options = {
var dataset = new vis.DataSet(items);
var options = {
style:'bar',
barChart: {width:50, align:'center'}, // align: left, center, right
drawPoints: true,
barChart: {width:50, align:'center', handleOverlap:'sideBySide'}, // align: left, center, right
drawPoints: false,
dataAxis: {
icons:true
},
@ -77,6 +73,15 @@ var options = {
};
var graph2d = new vis.Graph2d(container, items, options,groups);
var dropdown = document.getElementById("dropdownID");
dropdown.onchange = update;
function update() {
var options = {barChart:{handleOverlap:dropdown.value}};
graph2d.setOptions(options);
}
</script>
</body>
</html>

+ 4
- 4
examples/graph2d/12_customRange.html View File

@ -42,9 +42,9 @@ var options = {
var container = document.getElementById('visualization');
var groups = new vis.DataSet();
groups.add({id: 0, content: "group0", options: {slots:{slot:1,total:3}}})
groups.add({id: 1, content: "group1", options: {slots:{slot:2,total:3}}})
groups.add({id: 2, content: "group2", options: {slots:{slot:3,total:3}, yAxisOrientation:'right'}})
groups.add({id: 0, content: "group0"})
groups.add({id: 1, content: "group1"})
groups.add({id: 2, content: "group2",options:{ yAxisOrientation:'right'}})
var items = [
{x: '2014-06-11', y: 10, group:0},
@ -70,7 +70,7 @@ var options = {
var dataset = new vis.DataSet(items);
var options = {
style:'bar',
barChart: {width:50, align:'center'}, // align: left, center, right
barChart: {width:50, align:'center', handleOverlap:"sideBySide"}, // align: left, center, right
drawPoints: true,
dataAxis: {
customRange: {

+ 3
- 6
lib/network/Edge.js View File

@ -941,18 +941,15 @@ Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is
}
else {
var x, y, dx, dy;
var radius = this.physics.springLength / 4;
var radius = 0.25 * this.physics.springLength;
var node = this.from;
if (!node.width) {
node.resize(ctx);
}
if (node.width > node.height) {
x = node.x + node.width / 2;
x = node.x + 0.5 * node.width;
y = node.y - radius;
}
else {
x = node.x + radius;
y = node.y - node.height / 2;
y = node.y - 0.5 * node.height;
}
dx = x - x3;
dy = y - y3;

+ 2
- 2
lib/timeline/component/GraphGroup.js View File

@ -9,7 +9,7 @@ var DOMutil = require('../../DOMutil');
*/
function GraphGroup (group, groupId, options, groupsUsingDefaultStyles) {
this.id = groupId;
var fields = ['sampling','style','sort','yAxisOrientation','barChart','drawPoints','shaded','catmullRom','slots']
var fields = ['sampling','style','sort','yAxisOrientation','barChart','drawPoints','shaded','catmullRom']
this.options = util.selectiveBridgeObject(fields,options);
this.usingDefaultStyle = group.className === undefined;
this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
@ -40,7 +40,7 @@ GraphGroup.prototype.setZeroPosition = function(pos) {
GraphGroup.prototype.setOptions = function(options) {
if (options !== undefined) {
var fields = ['sampling','style','sort','yAxisOrientation','barChart','slots'];
var fields = ['sampling','style','sort','yAxisOrientation','barChart'];
util.selectiveDeepExtend(fields, this.options, options);
util.mergeOptions(this.options, options,'catmullRom');

+ 322
- 168
lib/timeline/component/LineGraph.js View File

@ -33,7 +33,7 @@ function LineGraph(body, options) {
style: 'line', // line, bar
barChart: {
width: 50,
allowOverlap: true,
handleOverlap: 'overlap',
align: 'center' // left, center, right
},
catmullRom: {
@ -549,79 +549,36 @@ LineGraph.prototype.redraw = function() {
LineGraph.prototype._updateGraph = function () {
// reset the svg elements
DOMutil.prepareElements(this.svgElements);
if (this.width != 0 && this.itemsData != null) {
var group, groupData, preprocessedGroup, i;
var preprocessedGroupData = [];
var processedGroupData = [];
var groupRanges = [];
var group, i;
var preprocessedGroupData = {};
var processedGroupData = {};
var groupRanges = {};
var changeCalled = false;
// getting group Ids
var groupIds = [];
for (var groupId in this.groups) {
if (this.groups.hasOwnProperty(groupId)) {
groupIds.push(groupId);
group = this.groups[groupId];
if (group.visible == true) {
groupIds.push(groupId);
}
}
}
// this is the range of the SVG canvas
var minDate = this.body.util.toGlobalTime(- this.body.domProps.root.width);
var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width);
// first select and preprocess the data from the datasets.
// the groups have their preselection of data, we now loop over this data to see
// what data we need to draw. Sorted data is much faster.
// more optimization is possible by doing the sampling before and using the binary search
// to find the end date to determine the increment.
if (groupIds.length > 0) {
// this is the range of the SVG canvas
var minDate = this.body.util.toGlobalTime(- this.body.domProps.root.width);
var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width);
var groupsData = {};
// fill groups data
this._getRelevantData(groupIds, groupsData, minDate, maxDate);
// we transform the X coordinates to detect collisions
for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]];
if (group.visible == true) {
groupData = [];
// optimization for sorted data
if (group.options.sort == true) {
var guess = Math.max(0,util.binarySearchGeneric(group.itemsData, minDate, 'x', 'before'));
for (var j = guess; j < group.itemsData.length; j++) {
var item = group.itemsData[j];
if (item !== undefined) {
if (item.x > maxDate) {
groupData.push(item);
break;
}
else {
groupData.push(item);
}
}
}
}
else {
for (var j = 0; j < group.itemsData.length; j++) {
var item = group.itemsData[j];
if (item !== undefined) {
if (item.x > minDate && item.x < maxDate) {
groupData.push(item);
}
}
}
}
// preprocess, split into ranges and data
if (groupData.length > 0) {
preprocessedGroup = this._preprocessData(groupData, group);
groupRanges.push({min: preprocessedGroup.min, max: preprocessedGroup.max});
preprocessedGroupData.push(preprocessedGroup.data);
}
else {
groupRanges.push({});
preprocessedGroupData.push([]);
}
}
else {
groupRanges.push({});
preprocessedGroupData.push([]);
}
preprocessedGroupData[groupIds[i]] = this._convertXcoordinates(groupsData[groupIds[i]]);
}
// now all needed data has been collected we start the processing.
this._getYRanges(groupIds, preprocessedGroupData, groupRanges);
// update the Y axis first, we use this data to draw at the correct Y points
// changeCalled is required to clean the SVG on a change emit.
@ -632,24 +589,21 @@ LineGraph.prototype._updateGraph = function () {
return;
}
// 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++) {
group = this.groups[groupIds[i]];
processedGroupData.push(this._convertYvalues(preprocessedGroupData[i],group))
processedGroupData[groupIds[i]] = this._convertYcoordinates(groupsData[groupIds[i]], group);
}
// draw the groups
for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]];
if (group.visible == true) {
if (group.options.style == 'line') {
this._drawLineGraph(processedGroupData[i], group);
}
else {
this._drawBarGraph (processedGroupData[i], group);
}
if (group.options.style == 'line') {
this._drawLineGraph(processedGroupData[groupIds[i]], group);
}
}
this._drawBarGraphs(groupIds, processedGroupData);
}
}
@ -657,6 +611,174 @@ LineGraph.prototype._updateGraph = function () {
DOMutil.cleanupElements(this.svgElements);
};
LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, maxDate) {
// first select and preprocess the data from the datasets.
// the groups have their preselection of data, we now loop over this data to see
// what data we need to draw. Sorted data is much faster.
// more optimization is possible by doing the sampling before and using the binary search
// to find the end date to determine the increment.
var group;
if (groupIds.length > 0) {
for (var i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]];
groupsData[groupIds[i]] = [];
var dataContainer = groupsData[groupIds[i]];
// optimization for sorted data
if (group.options.sort == true) {
var guess = Math.max(0, util.binarySearchGeneric(group.itemsData, minDate, 'x', 'before'));
for (var j = guess; j < group.itemsData.length; j++) {
var item = group.itemsData[j];
if (item !== undefined) {
if (item.x > maxDate) {
dataContainer.push(item);
break;
}
else {
dataContainer.push(item);
}
}
}
}
else {
for (var j = 0; j < group.itemsData.length; j++) {
var item = group.itemsData[j];
if (item !== undefined) {
if (item.x > minDate && item.x < maxDate) {
dataContainer.push(item);
}
}
}
}
}
}
this._applySampling(groupIds, groupsData);
};
LineGraph.prototype._applySampling = function (groupIds, groupsData) {
var group;
if (groupIds.length > 0) {
for (var i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]];
if (group.options.sampling == true) {
var dataContainer = groupsData[groupIds[i]];
var increment = 1;
var amountOfPoints = dataContainer.length;
// the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop
// of width changing of the yAxis.
var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x);
var pointsPerPixel = amountOfPoints / xDistance;
increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel)));
var sampledData = [];
for (var j = 0; j < amountOfPoints; j += increment) {
sampledData.push(dataContainer[j]);
}
groupsData[groupIds[i]] = sampledData;
}
}
}
};
LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) {
var groupData, group;
var barCombinedDataLeft = [];
var barCombinedDataRight = [];
var barCombinedData;
if (groupIds.length > 0) {
for (var i = 0; i < groupIds.length; i++) {
groupData = groupsData[groupIds[i]];
group = this.groups[groupIds[i]];
if (group.options.style == 'line' || group.options.barChart.handleOverlap != "stack") {
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;
}
groupRanges[groupIds[i]] = {min: yMin, max: yMax, yAxisOrientation: group.options.yAxisOrientation};
}
else if (group.options.style == 'bar') {
if (group.options.yAxisOrientation == 'left') {
barCombinedData = barCombinedDataLeft;
}
else {
barCombinedData = barCombinedDataRight;
}
groupRanges[groupIds[i]] = {min: 0, max: 0, yAxisOrientation: group.options.yAxisOrientation, ignore: true};
// combine data
for (var j = 0; j < groupData.length; j++) {
barCombinedData.push({
x: groupData[j].x,
y: groupData[j].y,
groupId: groupIds[i]
});
}
}
}
if (barCombinedDataLeft.length > 0) {
// sort by time and by group
barCombinedDataLeft.sort(function (a, b) {
if (a.x == b.x) {
return a.groupId - b.groupId;
} else {
return a.x - b.x;
}
})
var intersections = {};
this._getDataIntersections(intersections, barCombinedDataLeft);
groupRanges["__barchartLeft"] = this._getStackedBarYRange(intersections, barCombinedDataLeft);
groupRanges["__barchartLeft"].yAxisOrientation = "left";
groupIds.push("__barchartLeft");
}
if (barCombinedDataRight.length > 0) {
// sort by time and by group
barCombinedDataRight.sort(function (a, b) {
if (a.x == b.x) {
return a.groupId - b.groupId;
} else {
return a.x - b.x;
}
})
var intersections = {};
this._getDataIntersections(intersections, barCombinedDataRight);
groupRanges["__barchartRight"] = this._getStackedBarYRange(intersections, barCombinedDataRight);
groupRanges["__barchartRight"].yAxisOrientation = "right";
groupIds.push("__barchartRight");
}
}
};
LineGraph.prototype._getStackedBarYRange = 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;
}
else {
intersections[key].accumulated += combinedData[i].y;
}
}
for (var xpos in intersections) {
if (intersections.hasOwnProperty(xpos)) {
yMin = yMin > intersections[xpos].accumulated ? intersections[xpos].accumulated : yMin;
yMax = yMax < intersections[xpos].accumulated ? intersections[xpos].accumulated : yMax;
}
}
return {min: yMin, max: yMax};
};
/**
* this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden.
* @param {array} groupIds
@ -667,22 +789,15 @@ LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
var yAxisLeftUsed = false;
var yAxisRightUsed = false;
var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal;
var orientation = 'left';
// if groups are present
if (groupIds.length > 0) {
for (var i = 0; i < groupIds.length; i++) {
orientation = 'left';
var group = this.groups[groupIds[i]];
if (group.visible == true) {
if (group.options.yAxisOrientation == 'right') {
orientation = 'right';
}
if (groupRanges[groupIds[i]].ignore !== true) {
minVal = groupRanges[groupIds[i]].min;
maxVal = groupRanges[groupIds[i]].max;
minVal = groupRanges[i].min;
maxVal = groupRanges[i].max;
if (orientation == 'left') {
if (groupRanges[groupIds[i]].yAxisOrientation == 'left') {
yAxisLeftUsed = true;
minLeft = minLeft > minVal ? minVal : minLeft;
maxLeft = maxLeft < maxVal ? maxVal : maxLeft;
@ -694,6 +809,7 @@ LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
}
}
}
if (yAxisLeftUsed == true) {
this.yAxisLeft.setRange(minLeft, maxLeft);
}
@ -727,6 +843,15 @@ LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
else {
changeCalled = this.yAxisRight.redraw() || changeCalled;
}
// clean the accumulated lists
if (groupIds.indexOf("__barchartLeft") != -1) {
groupIds.splice(groupIds.indexOf("__barchartLeft"),1);
}
if (groupIds.indexOf("__barchartRight") != -1) {
groupIds.splice(groupIds.indexOf("__barchartRight"),1);
}
return changeCalled;
};
@ -761,90 +886,136 @@ LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) {
* @param datapoints
* @param group
*/
LineGraph.prototype._drawBarGraph = function (dataset, group) {
if (dataset != null) {
if (dataset.length > 0) {
var coreDistance;
var minWidth = 0.1 * group.options.barChart.width;
var offset = 0;
LineGraph.prototype._drawBarGraphs = function (groupIds, processedGroupData) {
var combinedData = [];
var intersections = {};
var coreDistance;
var key;
var group;
var i,j;
var barPoints = 0;
// combine all barchart data
for (i = 0; i < groupIds.length; i++) {
group = this.groups[groupIds[i]];
if (group.options.style == 'bar') {
if (group.visible == true) {
for (j = 0; j < processedGroupData[groupIds[i]].length; j++) {
combinedData.push({
x: processedGroupData[groupIds[i]][j].x,
y: processedGroupData[groupIds[i]][j].y,
groupId: groupIds[i]
});
barPoints += 1;
}
}
}
}
// check for intersections
var intersections = {};
if (barPoints == 0) {return;}
for (var i = 0; i < dataset.length; i++) {
if (i+1 < dataset.length) {coreDistance = Math.abs(dataset[i+1].x - dataset[i].x);}
if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(dataset[i-1].x - dataset[i].x));}
if (coreDistance == 0) {
if (intersections[dataset[i].x] === undefined) {
intersections[dataset[i].x] = {amount:0, resolved:0};
}
intersections[dataset[i].x].amount += 1;
}
// sort by time and by group
combinedData.sort(function (a, b) {
if (a.x == b.x) {
return a.groupId - b.groupId;
} else {
return a.x - b.x;
}
});
// get intersections
this._getDataIntersections(intersections, combinedData);
// plot barchart
for (i = 0; i < combinedData.length; i++) {
group = this.groups[combinedData[i].groupId];
var minWidth = 0.1 * group.options.barChart.width;
key = combinedData[i].x;
var heightOffset = 0;
if (intersections[key] === undefined) {
if (i+1 < combinedData.length) {coreDistance = Math.abs(combinedData[i+1].x - key);}
if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[i-1].x - key));}
var drawData = this._getSafeDrawData(coreDistance, group, minWidth);
}
else {
var nextKey = i + (intersections[key].amount - intersections[key].resolved);
var prevKey = i - (intersections[key].resolved + 1);
if (nextKey < combinedData.length) {coreDistance = Math.abs(combinedData[nextKey].x - key);}
if (prevKey > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[prevKey].x - key));}
var drawData = this._getSafeDrawData(coreDistance, group, minWidth);
intersections[key].resolved += 1;
if (group.options.barChart.handleOverlap == 'stack') {
heightOffset = intersections[key].accumulated;
intersections[key].accumulated += group.zeroPosition - combinedData[i].y;
}
else if (group.options.barChart.handleOverlap == 'sideBySide') {
drawData.width = drawData.width / intersections[key].amount;
drawData.offset += (intersections[key].resolved) * drawData.width - (0.5*drawData.width * (intersections[key].amount+1));
if (group.options.barChart.align == 'left') {offset -= 0.5*drawData.width;}
else if (group.options.barChart.align == 'right') {offset += 0.5*drawData.width;}
}
}
DOMutil.drawBar(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, drawData.width, group.zeroPosition - combinedData[i].y, group.className + ' bar', this.svgElements, this.svg);
// draw points
if (group.options.drawPoints.enabled == true) {
DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, group, this.svgElements, this.svg);
}
}
};
// plot the bargraph
var key;
for (var i = 0; i < dataset.length; i++) {
key = dataset[i].x;
if (intersections[key] === undefined) {
if (i+1 < dataset.length) {coreDistance = Math.abs(dataset[i+1].x - key);}
if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(dataset[i-1].x - key));}
var drawData = this._getSafeDrawData(coreDistance, group, minWidth);
}
else {
var nextKey = i + (intersections[key].amount - intersections[key].resolved);
var prevKey = i - (intersections[key].resolved + 1);
if (nextKey < dataset.length) {coreDistance = Math.abs(dataset[nextKey].x - key);}
if (prevKey > 0) {coreDistance = Math.min(coreDistance,Math.abs(dataset[prevKey].x - key));}
var drawData = this._getSafeDrawData(coreDistance, group, minWidth);
intersections[key].resolved += 1;
if (group.options.barChart.allowOverlap == false) {
drawData.width = drawData.width / intersections[key].amount;
drawData.offset += (intersections[key].resolved) * drawData.width - (0.5*drawData.width * (intersections[key].amount+1));
if (group.options.barChart.align == 'left') {offset -= 0.5*drawData.width;}
else if (group.options.barChart.align == 'right') {offset += 0.5*drawData.width;}
}
}
DOMutil.drawBar(dataset[i].x + drawData.offset, dataset[i].y, drawData.width, group.zeroPosition - dataset[i].y, group.className + ' bar', this.svgElements, this.svg);
// draw points
if (group.options.drawPoints.enabled == true) {
DOMutil.drawPoint(dataset[i].x + drawData.offset, dataset[i].y, group, this.svgElements, this.svg);
}
LineGraph.prototype._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, accumulated: 0};
}
intersections[combinedData[i].x].amount += 1;
}
}
};
//LineGraph.prototype._accumulate = function (intersections, combinedData) {
LineGraph.prototype._getSafeDrawData = function (coreDistance, group, minWidth) {
var width, offset;
if (coreDistance < group.options.barChart.width && coreDistance > 0) {
width = coreDistance < minWidth ? minWidth : coreDistance;
offset = 0; // recalculate offset with the new width;
if (group.options.slots) { // recalculate the shared width and offset if these options are set.
width = (width / group.options.slots.total);
offset = group.options.slots.slot * width - (0.5*width * (group.options.slots.total+1));
if (group.options.barChart.align == 'left') {
offset -= 0.5 * coreDistance;
}
else if (group.options.barChart.align == 'right') {
offset += 0.5 * coreDistance;
}
if (group.options.barChart.align == 'left') {offset -= 0.5*coreDistance;}
else if (group.options.barChart.align == 'right') {offset += 0.5*coreDistance;}
}
else {
// no collisions, plot with default settings
width = group.options.barChart.width;
offset = 0;
if (group.options.slots) {
// if the groups are sharing the same points, this allows them to be plotted side by side
width = width / group.options.slots.total;
offset = group.options.slots.slot * width - (0.5*width * (group.options.slots.total+1));
if (group.options.barChart.align == 'left') {
offset -= 0.5 * group.options.barChart.width;
}
else if (group.options.barChart.align == 'right') {
offset += 0.5 * group.options.barChart.width;
}
if (group.options.barChart.align == 'left') {offset -= 0.5*group.options.barChart.width;}
else if (group.options.barChart.align == 'right') {offset += 0.5*group.options.barChart.width;}
}
return {width: width, offset: offset};
}
};
/**
@ -919,69 +1090,52 @@ LineGraph.prototype._drawPoints = function (dataset, group, JSONcontainer, svg,
* @returns {Array}
* @private
*/
LineGraph.prototype._preprocessData = function (datapoints, group) {
LineGraph.prototype._convertXcoordinates = function (datapoints) {
var extractedData = [];
var xValue, yValue;
var toScreen = this.body.util.toScreen;
var increment = 1;
var amountOfPoints = datapoints.length;
var yMin = datapoints[0].y;
var yMax = datapoints[0].y;
// the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop
// of width changing of the yAxis.
if (group.options.sampling == true) {
var xDistance = this.body.util.toGlobalScreen(datapoints[datapoints.length-1].x) - this.body.util.toGlobalScreen(datapoints[0].x);
var pointsPerPixel = amountOfPoints/xDistance;
increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1,Math.round(pointsPerPixel)));
}
for (var i = 0; i < amountOfPoints; i += increment) {
for (var i = 0; i < datapoints.length; i++) {
xValue = toScreen(datapoints[i].x) + this.width - 1;
yValue = datapoints[i].y;
extractedData.push({x: xValue, y: yValue});
yMin = yMin > yValue ? yValue : yMin;
yMax = yMax < yValue ? yValue : yMax;
}
// extractedData.sort(function (a,b) {return a.x - b.x;});
return {min: yMin, max: yMax, data: extractedData};
return extractedData;
};
/**
* This uses the DataAxis object to generate the correct Y coordinate on the SVG window. It uses the
* util function toScreen to get the x coordinate from the timestamp.
* This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
* util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
* the yAxis.
*
* @param datapoints
* @param options
* @returns {Array}
* @private
*/
LineGraph.prototype._convertYvalues = function (datapoints, group) {
LineGraph.prototype._convertYcoordinates = function (datapoints, group) {
var extractedData = [];
var xValue, yValue;
var toScreen = this.body.util.toScreen;
var axis = this.yAxisLeft;
var svgHeight = Number(this.svg.style.height.replace("px",""));
if (group.options.yAxisOrientation == 'right') {
axis = this.yAxisRight;
}
for (var i = 0; i < datapoints.length; i++) {
xValue = datapoints[i].x;
xValue = toScreen(datapoints[i].x) + this.width - 1;
yValue = Math.round(axis.convertValue(datapoints[i].y));
extractedData.push({x: xValue, y: yValue});
}
group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0)));
// extractedData.sort(function (a,b) {return a.x - b.x;});
return extractedData;
};
/**
* This uses an uniform parametrization of the CatmullRom algorithm:
* "On the Parameterization of Catmull-Rom Curves" by Cem Yuksel et al.

Loading…
Cancel
Save