// o---------------------------------------------------------------------------------o // | This file is part of the RGraph package - you can learn more at: | // | | // | https://www.rgraph.net/license.html | // | | // | RGraph is dual-licensed under the Open Source GPL license. That means that it's | // | free to use and there are no restrictions on what you can use RGraph for! | // | If the GPL license does not suit you however, then there's an inexpensive | // | commercial license option available. See the URL above for more details. | // o---------------------------------------------------------------------------------o RGraph = window.RGraph || {isrgraph:true,isRGraph:true,rgraph:true}; // // The gantt chart constructor // RGraph.Gantt = function (conf) { var id = conf.id var canvas = document.getElementById(id); var data = conf.data; this.id = id; this.canvas = canvas; this.context = this.canvas.getContext ? this.canvas.getContext("2d", {alpha: (typeof id === 'object' && id.alpha === false) ? false : true}) : null; this.canvas.__object__ = this; this.type = 'gantt'; this.isRGraph = true; this.isrgraph = true; this.rgraph = true; this.uid = RGraph.createUID(); this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.createUID(); this.data = data; this.original_data = RGraph.arrayClone(data); this.colorsParsed = false; this.coordsText = []; this.original_colors = []; this.firstDraw = true; // After the first draw this will be false this.stopAnimationRequested = false;// Used to control the animations // Set some defaults this.properties = { backgroundBarsCount: null, backgroundBarsColor1: 'rgba(0,0,0,0)', backgroundBarsColor2: 'rgba(0,0,0,0)', backgroundGrid: true, backgroundGridLinewidth: 1, backgroundGridColor: '#ddd', backgroundGridHsize: 20, backgroundGridVsize: 20, backgroundGridHlines: true, backgroundGridVlines: true, backgroundGridBorder: true, backgroundGridAlign: true, backgroundGridAutofit:true, backgroundGridAutofitAlign:true, backgroundGridHlinesCount: null, backgroundGridVlinesCount: null, backgroundVbars: [], backgroundHbars: [], backgroundBorder: false, backgroundBorderLinewidth: 1, backgroundBorderColor: '#aaa', backgroundBorderDashed: false, backgroundBorderDotted: false, backgroundBorderDashArray: null, textSize: 12, textFont: 'Arial, Verdana, sans-serif', textColor: 'black', textBold: false, textItalic: false, textAccessible: false, textAccessibleOverflow: 'visible', textAccessiblePointerevents: false, text: null, marginLeft: 75, marginRight: 35, marginTop: 35, marginBottom: 35, marginInner: 2, labelsInbar: null, labelsInbarBackground: null, labelsInbarAlign: 'left', labelsInbarSize: null, labelsInbarFont: null, labelsInbarColor: null, labelsInbarBold: null, labelsInbarItalic: null, labelsInbarAbove: false, labelsInbarOffsetx: 0, labelsInbarOffsety: 0, labelsComplete: true, labelsCompleteFont: null, labelsCompleteSize: null, labelsCompleteColor: null, labelsCompleteBold: null, labelsCompleteItalic: null, labelsCompleteOffsetx: 0, labelsCompleteOffsety: 0, title: '', titleX: null, titleY: null, titleBold: null, titleItalic: null, titleFont: null, titleSize: null, titleColor: null, titleHalign: null, titleValign: null, titleOffsetx: 0, titleOffsety: 0, titleSubtitle: '', titleSubtitleSize: null, titleSubtitleColor: '#aaa', titleSubtitleFont: null, titleSubtitleBold: null, titleSubtitleItalic: null, titleSubtitleOffsetx: 0, titleSubtitleOffsety: 0, xaxis: false, xaxisLinewidth: 1, xaxisColor: 'black', xaxisTickmarks: true, xaxisTickmarksLength: 3, xaxisTickmarksLastLeft: null, xaxisTickmarksLastRight: null, xaxisTickmarksCount: null, xaxisLabels: null, xaxisLabelsSize: null, xaxisLabelsFont: null, xaxisLabelsItalic: null, xaxisLabelsBold: null, xaxisLabelsColor: null, xaxisLabelsOffsetx: 0, xaxisLabelsOffsety: 0, xaxisLabelsHalign: null, xaxisLabelsValign: null, xaxisLabelsPosition: 'section', xaxisPosition: 'bottom', xaxisLabelsAngle: 0, xaxisTitle: null, xaxisTitleBold: null, xaxisTitleSize: null, xaxisTitleFont: null, xaxisTitleColor: null, xaxisTitleItalic: null, xaxisTitlePos: null, xaxisTitleOffsetx: 0, xaxisTitleOffsety: 0, xaxisTitleX: null, xaxisTitleY: null, xaxisTitleHalign: null, xaxisTitleValign: null, xaxisScaleMin: 0, xaxisScaleMax: 0, yaxis: false, yaxisColor: 'black', yaxisLinewidth: 1, yaxisTickmarks: false, yaxisTickmarksLength: null, yaxisTickmarksCount: null, yaxisTickmarksLastTop: null, yaxisTickmarksLastBottom: null, yaxisScale: false, yaxisLabelsPosition: 'section', yaxisLabels: null, // This is populated when the chart is first drawn yaxisLabelsFont: null, yaxisLabelsSize: null, yaxisLabelsColor: null, yaxisLabelsBold: null, yaxisLabelsItalic: null, yaxisLabelsOffsety: 0, yaxisLabelsOffsetx: 0, yaxisLabelsValign: 'center', yaxisLabelsHalign: 'right', yaxisTitle: '', yaxisTitleBold: null, yaxisTitleItalic: null, yaxisTitleFont: null, yaxisTitleSize: null, yaxisTitleColor: null, yaxisTitlePos: null, yaxisTitleOffsetx: null, yaxisTitleOffsety: null, yaxisTitleX: null, yaxisTitleY: null, yaxisTitleHalign: null, yaxisTitleValign: null, yaxisTitleAccessible: null, colorsDefault: 'white', borders: true, coords: [], tooltips: null, tooltipsEffect: 'slide', tooltipsCssClass: 'RGraph_tooltip', tooltipsCss: null, tooltipsHighlight: true, tooltipsEvent: 'click', tooltipsFormattedThousand: ',', tooltipsFormattedPoint: '.', tooltipsFormattedDecimals: 0, tooltipsFormattedUnitsPre: '', tooltipsFormattedUnitsPost: '', tooltipsFormattedTableHeaders: null, tooltipsFormattedTableData: null, tooltipsPointer: true, tooltipsPointerOffsetx: 0, tooltipsPointerOffsety: 0, tooltipsPositionStatic: true, tooltipsHotspotIgnore: null, highlightStroke: 'rgba(0,0,0,0)', highlightFill: 'rgba(255,255,255,0.7)', contextmenu: null, annotatable: false, annotatableColor: 'black', annotatableLinewidth: 1, adjustable: false, adjustableOnly: null, corners: 'square', cornersRoundRadius: 25, clearto: 'rgba(0,0,0,0)' } // // Add the reverse look-up table for property names // so that property names can be specified in any case. // this.properties_lowercase_map = []; for (var i in this.properties) { if (typeof i === 'string') { this.properties_lowercase_map[i.toLowerCase()] = i; } } // // Create the dollar objects so that functions can be added to them // if (!data) { alert('[GANTT] The Gantt chart event data is now supplied as the second argument to the constructor - please update your code'); } else { // Go through the data converting relevant args to numbers for (var i=0,idx=0; i properties.xaxisScaleMax) { properties.backgroundVbars[i][1] = 364 - properties.backgroundVbars[i][0]; } var barX = this.marginLeft + (( (properties.backgroundVbars[i][0] - properties.xaxisScaleMin) / (properties.xaxisScaleMax - properties.xaxisScaleMin) ) * this.graphArea), barY = this.marginTop, width = (this.graphArea / (properties.xaxisScaleMax - properties.xaxisScaleMin) ) * properties.backgroundVbars[i][1], height = this.canvas.height - this.marginTop - this.marginBottom; // Right hand bounds checking if ( (barX + width) > (this.canvas.width - this.marginRight) ) { width = this.canvas.width - this.marginRight - barX; } this.context.fillStyle = properties.backgroundVbars[i][2]; this.context.fillRect(barX, barY, width, height); } } // Now draw the horizontal bars if (properties.backgroundHbars) { for (i=0,len=properties.backgroundHbars.length; i (this.canvas.width - this.marginRight) ) { barWidth = this.canvas.width - this.marginRight - barStartX; } // Draw the bar storing the coordinates this.coords.push([ barStartX, barStartY + properties.marginInner, barWidth, this.barHeight - (2 * properties.marginInner), ev, sequentialIndex, ]); // draw the border around the bar if (properties.borders || typeof ev.border === 'number') { this.context.strokeStyle = typeof ev.border === 'string' ? ev.border : 'black'; this.context.lineWidth = (typeof ev.linewidth === 'number' ? ev.linewidth : 1); if (ev.linewidth !== 0) { this.rect( this.context, barStartX, barStartY + properties.marginInner, barWidth, this.barHeight - (2 * properties.marginInner), { stroke: this.context.strokeStyle, begin: true, radius: this.properties.corners === 'round' ? this.properties.cornersRoundRadius : 0 } ); } } // Not entirely sure what this does... if (RGraph.isNull(ev.complete)) { this.context.fillStyle = ev.color ? ev.color : properties.colorsDefault; } else { this.context.fillStyle = ev.background ? ev.background : properties.colorsDefault; } this.rect( this.context, barStartX, barStartY + properties.marginInner, barWidth, this.barHeight - (2 * properties.marginInner), { fill: this.context.fillStyle, begin: true, radius: this.properties.corners === 'round' ? this.properties.cornersRoundRadius : 0 } ); // Work out the completeage indicator var complete = (ev.complete / 100) * barWidth; // Draw the % complete indicator. If it's greater than 0 if (typeof ev.complete === 'number') { this.context.fillStyle = ev.color ? ev.color : '#0c0'; // Draw the percent complete bar if the complete // option is given this.rect( this.context, barStartX, barStartY + properties.marginInner, (ev.complete / 100) * barWidth, this.barHeight - (2 * properties.marginInner), { fill: this.context.fillStyle, begin: true, radius: this.properties.corners === 'round' ? this.properties.cornersRoundRadius : 0 } ); // Don't necessarily have to draw the label if (properties.labelsComplete) { this.context.beginPath(); // Get the text configuration var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsComplete' }); RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: barStartX + barWidth + 5 + properties.labelsCompleteOffsetx, y: barStartY + this.halfBarHeight + properties.labelsCompleteOffsety, text: String(ev.complete) + '%', valign: 'center', tag: 'labels.complete' }); } } // // Draw the inbar label if it's defined // if (properties.labelsInbar && properties.labelsInbar[sequentialIndex]) { var label = String(properties.labelsInbar[sequentialIndex]), halign = properties.labelsInbarAlign == 'left' ? 'left' : 'center'; halign = properties.labelsInbarAlign == 'right' ? 'right' : halign; // Work out the position of the text if (halign == 'right') { var x = (barStartX + barWidth) - 5; } else if (halign == 'center') { var x = barStartX + (barWidth / 2); } else { var x = barStartX + 5; } // Draw the labels "above" the bar if (properties.labelsInbarAbove) { x = barStartX + barWidth + 5; halign = 'left'; } // Get the text configuration var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsInbar' }); // Set the color this.context.fillStyle = properties.labelsInbarColor; RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: x + properties.labelsInbarOffsetx, y: barStartY + this.halfBarHeight + properties.labelsInbarOffsety, text: label, valign: 'center', halign: halign, bounding: typeof properties.labelsInbarBackground == 'string', boundingStroke: 'transparent', boundingFill: typeof properties.labelsInbarBackground === 'string' ? properties.labelsInbarBackground : null, tag: 'labels.inbar' }); } }; // // Each object type has its own Highlight() function which highlights the appropriate shape // // @param object shape The shape to highlight // this.highlight = function (shape) { // First check for inverted highlighting if (typeof properties.highlightStyle === 'string' && properties.highlightStyle === 'invert') { for (var i=0; i this.marginLeft && mouseXY[0] < (this.canvas.width - this.marginRight) && mouseXY[1] > this.marginTop && mouseXY[1] < (this.canvas.height - this.marginBottom) ) { return this; } }; // // This method handles the adjusting calculation for when the mouse is moved // // @param object e The event object // this.adjusting_mousemove = function (e) { // Handle adjusting for the Bar if (properties.adjustable && RGraph.Registry.get('adjusting') && RGraph.Registry.get('adjusting').uid == this.uid) { var bar = RGraph.Registry.get('adjusting.gantt'); if (bar) { var mouseXY = RGraph.getMouseXY(e), obj = bar.object, dataset = bar.dataset, index = bar.index, diff = ((mouseXY[0] - bar.mousex) / (obj.canvas.width - obj.marginLeft - obj.marginRight)) * properties.xaxisScaleMax, eventStart = bar.event_start || 0, duration = bar.event_duration, event = typeof obj.data[dataset][index] === 'object' ? obj.data[dataset][index] : obj.data[dataset] if (bar['mode'] === 'move') { diff = Math.round(diff); // Single event if (!RGraph.isArray(obj.data[dataset])) { event.start = eventStart + diff; if (eventStart + diff < 0) { obj.data[dataset].start = 0; } else if ((eventStart + diff + obj.data[dataset].duration) > properties.xaxisScaleMax) { obj.data[dataset].start = properties.xaxisScaleMax - obj.data[dataset].duration; } // Multiple events } else { var dataset = bar.dataset, index = typeof bar.index === 'number' ? bar.index : 0, event = obj.data[dataset][index]; event.start = eventStart + diff; if ( (eventStart + diff) < 0) { event.start = 0; } else if ( (eventStart + diff + event.duration) > properties.xaxisScaleMax ) { event.start = properties.xaxisScaleMax - event.duration; } } } else if (bar.mode == 'resize') { // // Account for the right hand gutter. Appears to be a FF bug // if (mouseXY[0] > (obj.canvas.width - obj.marginRight)) { mouseXY[0] = obj.canvas.width - obj.marginRight; } var diff = ((mouseXY[0] - RGraph.Registry.get('adjusting.gantt')['mousex']) / (obj.canvas.width - obj.marginLeft - obj.marginRight)) * properties.xaxisScaleMax; diff = Math.round(diff); // Single event if (!RGraph.isArray(obj.data[dataset])) { obj.data[dataset].duration = duration + diff; if (obj.data[dataset].duration < 0) { obj.data[dataset].duration = 1; } // Multiple events } else { obj.data[dataset][index].duration = duration + diff; if (obj.data[dataset][index].duration <= 0) { obj.data[dataset][index].duration = 1; } } } RGraph.resetColorsToOriginalValues(obj); //RGraph.clear(obj.canvas); RGraph.redrawCanvas(obj.canvas); RGraph.fireCustomEvent(obj, 'onadjust'); } } }; // // Returns the X coordinate for the given value // // @param number value The desired value (eg minute/hour/day etc) // this.getXCoord = function (value) { var min = properties.xaxisScaleMin, max = properties.xaxisScaleMax, graphArea = this.canvas.width - this.marginLeft - this.marginRight; if (value > max || value < min) { return null; } var x = (((value - min) / (max - min)) * graphArea) + this.marginLeft; return x; }; // // Returns the value given EITHER the event object OR a two element array containing the X/Y coords // this.getValue = function (arg) { if (arg.length == 2) { var mouseXY = arg; // Two coords given } else { var mouseXY = RGraph.getMouseXY(arg); // event given } var mouseX = mouseXY[0], mouseY = mouseXY[1]; var value = (mouseX - this.marginLeft) / (this.canvas.width - this.marginLeft - this.marginRight); value *= (properties.xaxisScaleMax - properties.xaxisScaleMin); // Bounds checking if (value < properties.xaxisScaleMin || value > properties.xaxisScaleMax) { value = null; } return value; }; // // This allows for easy specification of gradients. Could optimise this not to repeatedly call parseSingleColors() // this.parseColors = function () { // Save the original colors so that they can be restored when the canvas is reset if (this.original_colors.length === 0) { this.original_colors.data = RGraph.arrayClone(this.data); this.original_colors.backgroundBarsColor1 = RGraph.arrayClone(properties.backgroundBarsColor1); this.original_colors.backgroundBarsColor2 = RGraph.arrayClone(properties.backgroundBarsColor2); this.original_colors.backgroundGridColor = RGraph.arrayClone(properties.backgroundGridColor); this.original_colors.colorsDefault = RGraph.arrayClone(properties.colorsDefault); this.original_colors.highlightStroke = RGraph.arrayClone(properties.highlightStroke); this.original_colors.highlightFill = RGraph.arrayClone(properties.highlightFill); } // // this.coords can be used here as gradients are only parsed on the SECOND draw - not the first. // A .redraw() is downe at the end of the first draw. // for (var i=0,sequentialIndex=0; i properties.xaxisScaleMax ? properties.xaxisScaleMax : (opts.start + opts.duration); // Create the gradient var grad = this.context.createLinearGradient( typeof opts.start === 'number' ? this.getXCoord(opts.start) : this.marginLeft, 0, typeof opts.start === 'number' ? this.getXCoord(value) : this.canvas.width - this.marginRight, 0 ); var diff = 1 / (parts.length - 1); grad.addColorStop(0, RGraph.trim(parts[0])); for (var j=1; j 0) { context.roundRect(x, y, width, height, opt.radius); } else { context.rect(x, y, width, height); } if (opt.stroke) { context.strokeStyle = opt.stroke; context.stroke(); } if (opt.fill) { context.fillStyle = opt.fill; context.fill(); } }; // // This function handles clipping to scale values. Because // each chart handles scales differently, a worker function // is needed instead of it all being done centrally in the // RGraph.clipTo.start() function. // // @param string clip The clip string as supplied by the // user in the chart configuration // this.clipToScaleWorker = function (clip) { // The Regular expression is actually done by the // calling RGraph.clipTo.start() function in the core // library if (RegExp.$1 === 'min') from = 0; else from = Number(RegExp.$1); if (RegExp.$2 === 'max') to = this.properties.xaxisScaleMax; else to = Number(RegExp.$2); var x = this.marginLeft + (((from - properties.xaxisScaleMin) / (properties.xaxisScaleMax - properties.xaxisScaleMin)) * this.graphArea) width = ((to-from) / (properties.xaxisScaleMax - properties.xaxisScaleMin) ) * this.graphArea; // Change the X if the number is "min" if (RegExp.$1 === 'min') { x = 0; width += this.properties.marginLeft; } // Change the width if the number is "max" if (RegExp.$2 === 'max') { width = this.canvas.width - x; } this.path( 'sa b r % % % % cl', x, 0, width, this.canvas.height ); }; // // This function handles TESTING clipping to scale values. // Because each chart handles scales differently, a worker // function is needed instead of it all being done // centrally in the RGraph.clipTo.start() function. // // @param string clip The clip string as supplied by the // user in the chart configuration // this.clipToScaleTestWorker = function (clip) { // The Regular expression is actually done by the // calling RGraph.clipTo.start() function in the core // library if (RegExp.$1 === 'min') from = 0; else from = Number(RegExp.$1); if (RegExp.$2 === 'max') to = this.properties.xaxisScaleMax; else to = Number(RegExp.$2); var x = this.marginLeft + (((from - properties.xaxisScaleMin) / (properties.xaxisScaleMax - properties.xaxisScaleMin)) * this.graphArea) width = ((to-from) / (properties.xaxisScaleMax - properties.xaxisScaleMin) ) * this.graphArea; // Change the X if the number is "min" if (RegExp.$1 === 'min') { x = 0; width += this.properties.marginLeft; } // Change the width if the number is "max" if (RegExp.$2 === 'max') { width = this.canvas.width - x; } this.path( 'b r % % % %', x, 0, width, this.canvas.height ); }; // // Register the object // RGraph.register(this); // // This is the 'end' of the constructor so if the first argument // contains configuration data - handle that. // RGraph.parseObjectStyleConfig(this, conf.options); };