var util = require('../../util'); var DOMutil = require('../../DOMutil'); var DataSet = require('../../DataSet'); var DataView = require('../../DataView'); var Component = require('./Component'); var DataAxis = require('./DataAxis'); var GraphGroup = require('./GraphGroup'); var Legend = require('./Legend'); var BarFunctions = require('./graph2d_types/bar'); var LineFunctions = require('./graph2d_types/line'); var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items /** * This is the constructor of the LineGraph. It requires a Timeline body and options. * * @param body * @param options * @constructor */ function LineGraph(body, options) { this.id = util.randomUUID(); this.body = body; this.defaultOptions = { yAxisOrientation: 'left', defaultGroup: 'default', sort: true, sampling: true, stack:false, graphHeight: '400px', shaded: { enabled: false, orientation: 'bottom' // top, bottom }, style: 'line', // line, bar barChart: { width: 50, handleOverlap: 'overlap', align: 'center' // left, center, right }, catmullRom: { enabled: true, parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5) alpha: 0.5 }, drawPoints: { enabled: true, size: 6, style: 'square' // square, circle }, dataAxis: { showMinorLabels: true, showMajorLabels: true, icons: false, width: '40px', visible: true, alignZeros: true, left:{ range: {min:undefined,max:undefined}, format: {decimals:undefined}, title: {text:undefined,style:undefined} }, right:{ range: {min:undefined,max:undefined}, format: {decimals:undefined}, title: {text:undefined,style:undefined} } }, legend: { enabled: false, icons: true, left: { visible: true, position: 'top-left' // top/bottom - left,right }, right: { visible: true, position: 'top-right' // top/bottom - left,right } }, groups: { visibility: {} } }; // options is shared by this ItemSet and all its items this.options = util.extend({}, this.defaultOptions); this.dom = {}; this.props = {}; this.hammer = null; this.groups = {}; this.abortedGraphUpdate = false; this.updateSVGheight = false; this.updateSVGheightOnResize = false; var me = this; this.itemsData = null; // DataSet this.groupsData = null; // DataSet // listeners for the DataSet of the items this.itemListeners = { 'add': function (event, params, senderId) { me._onAdd(params.items); }, 'update': function (event, params, senderId) { me._onUpdate(params.items); }, 'remove': function (event, params, senderId) { me._onRemove(params.items); } }; // listeners for the DataSet of the groups this.groupListeners = { 'add': function (event, params, senderId) { me._onAddGroups(params.items); }, 'update': function (event, params, senderId) { me._onUpdateGroups(params.items); }, 'remove': function (event, params, senderId) { me._onRemoveGroups(params.items); } }; this.items = {}; // object with an Item for every data item this.selection = []; // list with the ids of all selected nodes this.lastStart = this.body.range.start; this.touchParams = {}; // stores properties while dragging this.svgElements = {}; this.setOptions(options); this.groupsUsingDefaultStyles = [0]; this.COUNTER = 0; this.body.emitter.on('rangechanged', function() { me.lastStart = me.body.range.start; me.svg.style.left = util.option.asSize(-me.props.width); me.redraw.call(me,true); }); // create the HTML DOM this._create(); this.framework = {svg: this.svg, svgElements: this.svgElements, options: this.options, groups: this.groups}; this.body.emitter.emit('change'); } LineGraph.prototype = new Component(); /** * Create the HTML DOM for the ItemSet */ LineGraph.prototype._create = function(){ var frame = document.createElement('div'); frame.className = 'vis-line-graph'; this.dom.frame = frame; // create svg element for graph drawing. this.svg = document.createElementNS('http://www.w3.org/2000/svg','svg'); this.svg.style.position = 'relative'; this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px'; this.svg.style.display = 'block'; frame.appendChild(this.svg); // data axis this.options.dataAxis.orientation = 'left'; this.yAxisLeft = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups); this.options.dataAxis.orientation = 'right'; this.yAxisRight = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups); delete this.options.dataAxis.orientation; // legends this.legendLeft = new Legend(this.body, this.options.legend, 'left', this.options.groups); this.legendRight = new Legend(this.body, this.options.legend, 'right', this.options.groups); this.show(); }; /** * set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element. * @param {object} options */ LineGraph.prototype.setOptions = function(options) { if (options) { 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) { this.updateSVGheight = true; this.updateSVGheightOnResize = true; } else if (this.body.domProps.centerContainer.height !== undefined && options.graphHeight !== undefined) { if (parseInt((options.graphHeight + '').replace("px",'')) < this.body.domProps.centerContainer.height) { this.updateSVGheight = true; } } util.selectiveDeepExtend(fields, this.options, options); util.mergeOptions(this.options, options,'catmullRom'); util.mergeOptions(this.options, options,'drawPoints'); util.mergeOptions(this.options, options,'shaded'); util.mergeOptions(this.options, options,'legend'); if (options.catmullRom) { if (typeof options.catmullRom == 'object') { if (options.catmullRom.parametrization) { if (options.catmullRom.parametrization == 'uniform') { this.options.catmullRom.alpha = 0; } else if (options.catmullRom.parametrization == 'chordal') { this.options.catmullRom.alpha = 1.0; } else { this.options.catmullRom.parametrization = 'centripetal'; this.options.catmullRom.alpha = 0.5; } } } } if (this.yAxisLeft) { if (options.dataAxis !== undefined) { this.yAxisLeft.setOptions(this.options.dataAxis); this.yAxisRight.setOptions(this.options.dataAxis); } } if (this.legendLeft) { if (options.legend !== undefined) { this.legendLeft.setOptions(this.options.legend); this.legendRight.setOptions(this.options.legend); } } if (this.groups.hasOwnProperty(UNGROUPED)) { this.groups[UNGROUPED].setOptions(options); } } // this is used to redraw the graph if the visibility of the groups is changed. if (this.dom.frame) { this.redraw(true); } }; /** * Hide the component from the DOM */ LineGraph.prototype.hide = function() { // remove the frame containing the items if (this.dom.frame.parentNode) { this.dom.frame.parentNode.removeChild(this.dom.frame); } }; /** * Show the component in the DOM (when not already visible). * @return {Boolean} changed */ LineGraph.prototype.show = function() { // show frame containing the items if (!this.dom.frame.parentNode) { this.body.dom.center.appendChild(this.dom.frame); } }; /** * Set items * @param {vis.DataSet | null} items */ LineGraph.prototype.setItems = function(items) { var me = this, ids, oldItemsData = this.itemsData; // replace the dataset if (!items) { this.itemsData = null; } else if (items instanceof DataSet || items instanceof DataView) { this.itemsData = items; } else { throw new TypeError('Data must be an instance of DataSet or DataView'); } if (oldItemsData) { // unsubscribe from old dataset util.forEach(this.itemListeners, function (callback, event) { oldItemsData.off(event, callback); }); // remove all drawn items ids = oldItemsData.getIds(); this._onRemove(ids); } if (this.itemsData) { // subscribe to new dataset var id = this.id; util.forEach(this.itemListeners, function (callback, event) { me.itemsData.on(event, callback, id); }); // add all new items ids = this.itemsData.getIds(); this._onAdd(ids); } this._updateUngrouped(); //this._updateGraph(); this.redraw(true); }; /** * Set groups * @param {vis.DataSet} groups */ LineGraph.prototype.setGroups = function(groups) { var me = this; var ids; // unsubscribe from current dataset if (this.groupsData) { util.forEach(this.groupListeners, function (callback, event) { me.groupsData.unsubscribe(event, callback); }); // remove all drawn groups ids = this.groupsData.getIds(); this.groupsData = null; this._onRemoveGroups(ids); // note: this will cause a redraw } // replace the dataset if (!groups) { this.groupsData = null; } else if (groups instanceof DataSet || groups instanceof DataView) { this.groupsData = groups; } else { throw new TypeError('Data must be an instance of DataSet or DataView'); } if (this.groupsData) { // subscribe to new dataset var id = this.id; util.forEach(this.groupListeners, function (callback, event) { me.groupsData.on(event, callback, id); }); // draw all ms ids = this.groupsData.getIds(); this._onAddGroups(ids); } this._onUpdate(); }; /** * Update the data * @param [ids] * @private */ LineGraph.prototype._onUpdate = function(ids) { this._updateUngrouped(); this._updateAllGroupData(); //this._updateGraph(); 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]); } //this._updateGraph(); this.redraw(true); }; LineGraph.prototype._onAddGroups = function (groupIds) {this._onUpdateGroups(groupIds);}; /** * this cleans the group out off the legends and the dataaxis, updates the ungrouped and updates the graph * @param {Array} groupIds * @private */ LineGraph.prototype._onRemoveGroups = function (groupIds) { for (var i = 0; i < groupIds.length; i++) { if (this.groups.hasOwnProperty(groupIds[i])) { if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') { this.yAxisRight.removeGroup(groupIds[i]); this.legendRight.removeGroup(groupIds[i]); this.legendRight.redraw(); } else { this.yAxisLeft.removeGroup(groupIds[i]); this.legendLeft.removeGroup(groupIds[i]); this.legendLeft.redraw(); } delete this.groups[groupIds[i]]; } } this._updateUngrouped(); //this._updateGraph(); this.redraw(true); }; /** * update a group object with the group dataset entree * * @param group * @param groupId * @private */ LineGraph.prototype._updateGroup = function (group, groupId) { if (!this.groups.hasOwnProperty(groupId)) { this.groups[groupId] = new GraphGroup(group, groupId, this.options, this.groupsUsingDefaultStyles); if (this.groups[groupId].options.yAxisOrientation == 'right') { this.yAxisRight.addGroup(groupId, this.groups[groupId]); this.legendRight.addGroup(groupId, this.groups[groupId]); } else { this.yAxisLeft.addGroup(groupId, this.groups[groupId]); this.legendLeft.addGroup(groupId, this.groups[groupId]); } } else { this.groups[groupId].update(group); if (this.groups[groupId].options.yAxisOrientation == 'right') { this.yAxisRight.updateGroup(groupId, this.groups[groupId]); this.legendRight.updateGroup(groupId, this.groups[groupId]); } else { this.yAxisLeft.updateGroup(groupId, this.groups[groupId]); this.legendLeft.updateGroup(groupId, this.groups[groupId]); } } this.legendLeft.redraw(); this.legendRight.redraw(); }; /** * this updates all groups, it is used when there is an update the the itemset. * * @private */ LineGraph.prototype._updateAllGroupData = function () { if (this.itemsData != null) { var groupsContent = {}; var groupId; for (groupId in this.groups) { if (this.groups.hasOwnProperty(groupId)) { groupsContent[groupId] = []; } } for (var itemId in this.itemsData._data) { if (this.itemsData._data.hasOwnProperty(itemId)) { var item = this.itemsData._data[itemId]; if (groupsContent[item.group] === undefined) { throw new Error('Cannot find referenced group. Possible reason: items added before groups? Groups need to be added before items, as items refer to groups.') } item.x = util.convert(item.x,'Date'); groupsContent[item.group].push(item); } } for (groupId in this.groups) { if (this.groups.hasOwnProperty(groupId)) { this.groups[groupId].setItems(groupsContent[groupId]); } } } }; /** * Create or delete the group holding all ungrouped items. This group is used when * there are no groups specified. This anonymous group is called 'graph'. * @protected */ LineGraph.prototype._updateUngrouped = function() { if (this.itemsData && this.itemsData != null) { var ungroupedCounter = 0; for (var itemId in this.itemsData._data) { if (this.itemsData._data.hasOwnProperty(itemId)) { var item = this.itemsData._data[itemId]; if (item != undefined) { if (item.hasOwnProperty('group')) { if (item.group === undefined) { item.group = UNGROUPED; } } else { item.group = UNGROUPED; } ungroupedCounter = item.group == UNGROUPED ? ungroupedCounter + 1 : ungroupedCounter; } } } if (ungroupedCounter == 0) { delete this.groups[UNGROUPED]; this.legendLeft.removeGroup(UNGROUPED); this.legendRight.removeGroup(UNGROUPED); this.yAxisLeft.removeGroup(UNGROUPED); this.yAxisRight.removeGroup(UNGROUPED); } else { var group = {id: UNGROUPED, content: this.options.defaultGroup}; this._updateGroup(group, UNGROUPED); } } else { delete this.groups[UNGROUPED]; this.legendLeft.removeGroup(UNGROUPED); this.legendRight.removeGroup(UNGROUPED); this.yAxisLeft.removeGroup(UNGROUPED); this.yAxisRight.removeGroup(UNGROUPED); } this.legendLeft.redraw(); this.legendRight.redraw(); }; /** * Redraw the component, mandatory function * @return {boolean} Returns true if the component is resized */ LineGraph.prototype.redraw = function(forceGraphUpdate) { var resized = false; // calculate actual size and position this.props.width = this.dom.frame.offsetWidth; this.props.height = this.body.domProps.centerContainer.height - 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 if (this.lastWidth === undefined && this.props.width) { forceGraphUpdate = true; } // check if this component is resized resized = this._isResized() || resized; // check whether zoomed (in that case we need to re-stack everything) var visibleInterval = this.body.range.end - this.body.range.start; var zoomed = (visibleInterval != this.lastVisibleInterval); this.lastVisibleInterval = visibleInterval; // 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 if (resized == true) { this.svg.style.width = util.option.asSize(3*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 ((this.options.height + '').indexOf("%") != -1 || this.updateSVGheightOnResize == true) { this.updateSVGheight = true; } } // update the height of the graph on each redraw of the graph. if (this.updateSVGheight == true) { if (this.options.graphHeight != this.props.height + 'px') { this.options.graphHeight = this.props.height + 'px'; this.svg.style.height = this.props.height + 'px'; } this.updateSVGheight = false; } else { this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px'; } // zoomed is here to ensure that animations are shown correctly. if (resized == true || zoomed == true || this.abortedGraphUpdate == true || forceGraphUpdate == true) { resized = this._updateGraph() || resized; } else { // move the whole svg while dragging if (this.lastStart != 0) { var offset = this.body.range.start - this.lastStart; var range = this.body.range.end - this.body.range.start; if (this.props.width != 0) { var rangePerPixelInv = this.props.width/range; var xOffset = offset * rangePerPixelInv; this.svg.style.left = (-this.props.width - xOffset) + 'px'; } } } this.legendLeft.redraw(); this.legendRight.redraw(); return resized; }; /** * Update and redraw the graph. * */ LineGraph.prototype._updateGraph = function () { // reset the svg elements DOMutil.prepareElements(this.svgElements); if (this.props.width != 0 && this.itemsData != null) { 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)) { group = this.groups[groupId]; if (group.visible == true && (this.options.groups.visibility[groupId] === undefined || this.options.groups.visibility[groupId] == true)) { groupIds.push(groupId); } } } 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 only loads the data we require based on the timewindow this._getRelevantData(groupIds, groupsData, minDate, maxDate); // apply sampling, if disabled, it will pass through this function. this._applySampling(groupIds, groupsData); // we transform the X coordinates to detect collisions for (i = 0; i < groupIds.length; i++) { 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. changeCalled = this._updateYAxis(groupIds, groupRanges); var MAX_CYCLES = 5; if (changeCalled == true && this.COUNTER < MAX_CYCLES) { DOMutil.cleanupElements(this.svgElements); this.abortedGraphUpdate = true; this.COUNTER++; this.body.emitter.emit('change'); return true; } else { if (this.COUNTER > MAX_CYCLES) { console.log("WARNING: there may be an infinite loop in the _updateGraph emitter cycle."); } this.COUNTER = 0; this.abortedGraphUpdate = false; // 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[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.options.style != 'bar') { // bar needs to be drawn enmasse group.draw(processedGroupData[groupIds[i]], group, this.framework); } } BarFunctions.draw(groupIds, processedGroupData, this.framework); } } } // cleanup unused svg elements DOMutil.cleanupElements(this.svgElements); return false; }; /** * 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. * * @param {array} groupIds * @param {object} groupsData * @param {date} minDate * @param {date} maxDate * @private */ LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, maxDate) { var group, i, j, item; if (groupIds.length > 0) { for (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.binarySearchValue(group.itemsData, minDate, 'x', 'before')); for (j = guess; j < group.itemsData.length; j++) { item = group.itemsData[j]; if (item !== undefined) { if (item.x > maxDate) { dataContainer.push(item); break; } else { dataContainer.push(item); } } } } 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); } } } } } } }; /** * * @param groupIds * @param groupsData * @private */ 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]]; if (dataContainer.length > 0) { 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; } } } } }; /** * * * @param {array} groupIds * @param {object} groupsData * @param {object} groupRanges | this is being filled here * @private */ LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) { var groupData, group, i; var combinedDataLeft = []; var combinedDataRight = []; var options; if (groupIds.length > 0) { for (i = 0; i < groupIds.length; i++) { groupData = groupsData[groupIds[i]]; options = this.groups[groupIds[i]].options; if (groupData.length > 0) { group = this.groups[groupIds[i]]; // if bar graphs are stacked, their range need to be handled differently and accumulated over all groups. if (options.stack === true) { if (options.yAxisOrientation == 'left') {combinedDataLeft = combinedDataLeft .concat(group.getData(groupData));} else {combinedDataRight = combinedDataRight.concat(group.getData(groupData));} } else { 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. BarFunctions.getStackedYRange(combinedDataLeft , groupRanges, groupIds, '__barStackLeft' , 'left' ); BarFunctions.getStackedYRange(combinedDataRight, groupRanges, groupIds, '__barStackRight', 'right'); // 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(combinedDataRight, groupRanges, groupIds, '__lineStackRight', 'right'); } }; /** * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden. * @param {Array} groupIds * @param {Object} groupRanges * @private */ LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) { var resized = false; var yAxisLeftUsed = false; var yAxisRightUsed = false; var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal; // if groups are present if (groupIds.length > 0) { // this is here to make sure that if there are no items in the axis but there are groups, that there is no infinite draw/redraw loop. for (var i = 0; i < groupIds.length; i++) { var group = this.groups[groupIds[i]]; if (group && group.options.yAxisOrientation != 'right') { yAxisLeftUsed = true; minLeft = 0; maxLeft = 0; } else if (group && group.options.yAxisOrientation) { yAxisRightUsed = true; minRight = 0; maxRight = 0; } } // if there are items: for (var i = 0; i < groupIds.length; i++) { if (groupRanges.hasOwnProperty(groupIds[i])) { if (groupRanges[groupIds[i]].ignore !== true) { minVal = groupRanges[groupIds[i]].min; maxVal = groupRanges[groupIds[i]].max; if (groupRanges[groupIds[i]].yAxisOrientation != 'right') { yAxisLeftUsed = true; minLeft = minLeft > minVal ? minVal : minLeft; maxLeft = maxLeft < maxVal ? maxVal : maxLeft; } else { yAxisRightUsed = true; minRight = minRight > minVal ? minVal : minRight; maxRight = maxRight < maxVal ? maxVal : maxRight; } } } } if (yAxisLeftUsed == true) { this.yAxisLeft.setRange(minLeft, maxLeft); } if (yAxisRightUsed == true) { this.yAxisRight.setRange(minRight, maxRight); } } resized = this._toggleAxisVisiblity(yAxisLeftUsed , this.yAxisLeft) || resized; resized = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || resized; if (yAxisRightUsed == true && yAxisLeftUsed == true) { this.yAxisLeft.drawIcons = true; this.yAxisRight.drawIcons = true; } else { this.yAxisLeft.drawIcons = false; this.yAxisRight.drawIcons = false; } this.yAxisRight.master = !yAxisLeftUsed; if (this.yAxisRight.master == false) { if (yAxisRightUsed == true) {this.yAxisLeft.lineOffset = this.yAxisRight.width;} else {this.yAxisLeft.lineOffset = 0;} resized = this.yAxisLeft.redraw() || resized; this.yAxisRight.stepPixels = this.yAxisLeft.stepPixels; this.yAxisRight.zeroCrossing = this.yAxisLeft.zeroCrossing; this.yAxisRight.amountOfSteps = this.yAxisLeft.amountOfSteps; resized = this.yAxisRight.redraw() || resized; } else { resized = this.yAxisRight.redraw() || resized; } // clean the accumulated lists var tempGroups = ['__barStackLeft','__barStackRight','__lineStackLeft','__lineStackRight']; for (var i = 0; i < tempGroups.length; i++) { if (groupIds.indexOf(tempGroups[i]) != -1) {groupIds.splice(groupIds.indexOf(tempGroups[i]),1);} } return resized; }; /** * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function * * @param {boolean} axisUsed * @returns {boolean} * @private * @param axis */ LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) { var changed = false; if (axisUsed == false) { if (axis.dom.frame.parentNode && axis.hidden == false) { axis.hide() changed = true; } } else { if (!axis.dom.frame.parentNode && axis.hidden == true) { axis.show(); changed = true; } } return changed; }; /** * 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 * @returns {Array} * @private */ LineGraph.prototype._convertXcoordinates = function (datapoints) { var extractedData = []; var xValue, yValue; var toScreen = this.body.util.toScreen; for (var i = 0; i < datapoints.length; i++) { xValue = toScreen(datapoints[i].x) + this.props.width; yValue = datapoints[i].y; extractedData.push({x: xValue, y: yValue}); } return extractedData; }; /** * 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 group * @returns {Array} * @private */ 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++) { var labelValue = datapoints[i].label ? datapoints[i].label : null; xValue = toScreen(datapoints[i].x) + this.props.width; yValue = Math.round(axis.convertValue(datapoints[i].y)); extractedData.push({x: xValue, y: yValue, label:labelValue}); } group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0))); return extractedData; }; module.exports = LineGraph;