UNPKG

vis-timeline

Version:

Create a fully customizable, interactive timeline with items and ranges.

287 lines (258 loc) 10.5 kB
var DOMutil = require('../../../DOMutil'); var Points = require('./points'); /** * * @param {vis.GraphGroup.id} groupId * @param {Object} options // TODO: Describe options * @constructor Bargraph */ function Bargraph(groupId, options) { // eslint-disable-line no-unused-vars } Bargraph.drawIcon = function (group, x, y, iconWidth, iconHeight, framework) { var fillHeight = iconHeight * 0.5; var outline = DOMutil.getSVGElement("rect", framework.svgElements, framework.svg); outline.setAttributeNS(null, "x", x); outline.setAttributeNS(null, "y", y - fillHeight); outline.setAttributeNS(null, "width", iconWidth); outline.setAttributeNS(null, "height", 2 * fillHeight); outline.setAttributeNS(null, "class", "vis-outline"); var barWidth = Math.round(0.3 * iconWidth); var originalWidth = group.options.barChart.width; var scale = originalWidth / barWidth; var bar1Height = Math.round(0.4 * iconHeight); var bar2Height = Math.round(0.75 * iconHeight); var offset = Math.round((iconWidth - (2 * barWidth)) / 3); DOMutil.drawBar(x + 0.5 * barWidth + offset, y + fillHeight - bar1Height - 1, barWidth, bar1Height, group.className + ' vis-bar', framework.svgElements, framework.svg, group.style); DOMutil.drawBar(x + 1.5 * barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, group.className + ' vis-bar', framework.svgElements, framework.svg, group.style); if (group.options.drawPoints.enabled == true) { var groupTemplate = { style: group.options.drawPoints.style, styles: group.options.drawPoints.styles, size: (group.options.drawPoints.size / scale), className: group.className }; DOMutil.drawPoint(x + 0.5 * barWidth + offset, y + fillHeight - bar1Height - 1, groupTemplate, framework.svgElements, framework.svg); DOMutil.drawPoint(x + 1.5 * barWidth + offset + 2, y + fillHeight - bar2Height - 1, groupTemplate, framework.svgElements, framework.svg); } }; /** * draw a bar graph * * @param {Array.<vis.GraphGroup.id>} groupIds * @param {Object} processedGroupData * @param {{svg: Object, svgElements: Array.<Object>, options: Object, groups: Array.<vis.Group>}} framework */ Bargraph.draw = function (groupIds, processedGroupData, framework) { var combinedData = []; var intersections = {}; var coreDistance; var key, drawData; var group; var i, j; var barPoints = 0; // combine all barchart data for (i = 0; i < groupIds.length; i++) { group = framework.groups[groupIds[i]]; if (group.options.style === 'bar') { if (group.visible === true && (framework.options.groups.visibility[groupIds[i]] === undefined || framework.options.groups.visibility[groupIds[i]] === true)) { for (j = 0; j < processedGroupData[groupIds[i]].length; j++) { combinedData.push({ screen_x: processedGroupData[groupIds[i]][j].screen_x, screen_end: processedGroupData[groupIds[i]][j].screen_end, screen_y: processedGroupData[groupIds[i]][j].screen_y, x: processedGroupData[groupIds[i]][j].x, end: processedGroupData[groupIds[i]][j].end, y: processedGroupData[groupIds[i]][j].y, groupId: groupIds[i], label: processedGroupData[groupIds[i]][j].label }); barPoints += 1; } } } } if (barPoints === 0) { return; } // sort by time and by group combinedData.sort(function (a, b) { if (a.screen_x === b.screen_x) { return a.groupId < b.groupId ? -1 : 1; } else { return a.screen_x - b.screen_x; } }); // get intersections Bargraph._getDataIntersections(intersections, combinedData); // plot barchart for (i = 0; i < combinedData.length; i++) { group = framework.groups[combinedData[i].groupId]; var minWidth = group.options.barChart.minWidth != undefined ? group.options.barChart.minWidth : 0.1 * group.options.barChart.width; key = combinedData[i].screen_x; var heightOffset = 0; if (intersections[key] === undefined) { if (i + 1 < combinedData.length) { coreDistance = Math.abs(combinedData[i + 1].screen_x - key); } drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth); } else { var nextKey = i + (intersections[key].amount - intersections[key].resolved); if (nextKey < combinedData.length) { coreDistance = Math.abs(combinedData[nextKey].screen_x - key); } drawData = Bargraph._getSafeDrawData(coreDistance, group, minWidth); intersections[key].resolved += 1; if (group.options.stack === true && group.options.excludeFromStacking !== true) { if (combinedData[i].screen_y < group.zeroPosition) { heightOffset = intersections[key].accumulatedNegative; intersections[key].accumulatedNegative += group.zeroPosition - combinedData[i].screen_y; } else { heightOffset = intersections[key].accumulatedPositive; intersections[key].accumulatedPositive += group.zeroPosition - combinedData[i].screen_y; } } else if (group.options.barChart.sideBySide === true) { drawData.width = drawData.width / intersections[key].amount; drawData.offset += (intersections[key].resolved) * drawData.width - (0.5 * drawData.width * (intersections[key].amount + 1)); } } let dataWidth = drawData.width; let start = combinedData[i].screen_x; // are we drawing explicit boxes? (we supplied an end value) if (combinedData[i].screen_end != undefined){ dataWidth = combinedData[i].screen_end - combinedData[i].screen_x; start += (dataWidth * 0.5); } else { start += drawData.offset } DOMutil.drawBar(start, combinedData[i].screen_y - heightOffset, dataWidth, group.zeroPosition - combinedData[i].screen_y, group.className + ' vis-bar', framework.svgElements, framework.svg, group.style); // draw points if (group.options.drawPoints.enabled === true) { let pointData = { screen_x: combinedData[i].screen_x, screen_y: combinedData[i].screen_y - heightOffset, x: combinedData[i].x, y: combinedData[i].y, groupId: combinedData[i].groupId, label: combinedData[i].label }; Points.draw([pointData], group, framework, drawData.offset); //DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y, group, framework.svgElements, framework.svg); } } }; /** * Fill the intersections object with counters of how many datapoints share the same x coordinates * @param {Object} intersections * @param {Array.<Object>} combinedData * @private */ Bargraph._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].screen_x - combinedData[i].screen_x); } if (i > 0) { coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].screen_x - combinedData[i].screen_x)); } if (coreDistance === 0) { if (intersections[combinedData[i].screen_x] === undefined) { intersections[combinedData[i].screen_x] = { amount: 0, resolved: 0, accumulatedPositive: 0, accumulatedNegative: 0 }; } intersections[combinedData[i].screen_x].amount += 1; } } }; /** * Get the width and offset for bargraphs based on the coredistance between datapoints * * @param {number} coreDistance * @param {vis.Group} group * @param {number} minWidth * @returns {{width: number, offset: number}} * @private */ Bargraph._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.barChart.align === 'left') { offset -= 0.5 * coreDistance; } else if (group.options.barChart.align === 'right') { offset += 0.5 * coreDistance; } } else { // default settings width = group.options.barChart.width; offset = 0; 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}; }; Bargraph.getStackedYRange = function (combinedData, groupRanges, groupIds, groupLabel, orientation) { if (combinedData.length > 0) { // sort by time and by group combinedData.sort(function (a, b) { if (a.screen_x === b.screen_x) { return a.groupId < b.groupId ? -1 : 1; } else { return a.screen_x - b.screen_x; } }); var intersections = {}; Bargraph._getDataIntersections(intersections, combinedData); groupRanges[groupLabel] = Bargraph._getStackedYRange(intersections, combinedData); groupRanges[groupLabel].yAxisOrientation = orientation; groupIds.push(groupLabel); } }; Bargraph._getStackedYRange = function (intersections, combinedData) { var key; var yMin = combinedData[0].screen_y; var yMax = combinedData[0].screen_y; for (var i = 0; i < combinedData.length; i++) { key = combinedData[i].screen_x; if (intersections[key] === undefined) { yMin = yMin > combinedData[i].screen_y ? combinedData[i].screen_y : yMin; yMax = yMax < combinedData[i].screen_y ? combinedData[i].screen_y : yMax; } else { if (combinedData[i].screen_y < 0) { intersections[key].accumulatedNegative += combinedData[i].screen_y; } else { intersections[key].accumulatedPositive += combinedData[i].screen_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}; }; module.exports = Bargraph;