// 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 line chart constructor // RGraph.Line = function (conf) { var id = conf.id, canvas = document.getElementById(id), data = conf.data; this.id = id; this.canvas = canvas; this.context = this.canvas.getContext('2d'); this.canvas.__object__ = this; this.type = 'line'; this.max = 0; this.coords = []; this.coords2 = []; this.coords.key = []; this.coordsText = []; this.coordsSpline = []; this.coordsAxes = {xaxis: [], yaxis: []}; this.hasnegativevalues = false; 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.colorsParsed = false; this.original_colors = []; this.firstDraw = true; // After the first draw this will be false this.stopAnimationRequested = false;// Used to control the animations // Various config type stuff this.properties = { backgroundBarsCount: null, backgroundBarsColor1: 'rgba(0,0,0,0)', backgroundBarsColor2: 'rgba(0,0,0,0)', backgroundGrid: 1, backgroundGridLinewidth: 1, backgroundGridHsize: 25, backgroundGridVsize: 25, backgroundGridColor: '#ddd', backgroundGridVlines: true, backgroundGridHlines: true, backgroundGridBorder: true, backgroundGridAutofit: true, backgroundGridAutofitAlign: true, backgroundGridHlinesCount: 5, backgroundGridVlinesCount: null, backgroundGridDashed: false, backgroundGridDotted: false, backgroundGridDashArray: null, backgroundHbars: null, backgroundImage: null, backgroundImageStretch: true, backgroundImageX: null, backgroundImageY: null, backgroundImageW: null, backgroundImageH: null, backgroundImageAlign: null, backgroundColor: null, backgroundBorder: false, backgroundBorderLinewidth: 1, backgroundBorderColor: '#aaa', backgroundBorderDashed: false, backgroundBorderDotted: false, backgroundBorderDashArray: null, xaxis: true, xaxisLinewidth: 1, xaxisColor: 'black', xaxisTickmarks: true, xaxisTickmarksLength: 3, xaxisTickmarksLastLeft: null, xaxisTickmarksLastRight: null, xaxisTickmarksCount: null, xaxisLabels: null, xaxisLabelsFormattedDecimals: 0, xaxisLabelsFormattedPoint: '.', xaxisLabelsFormattedThousand: ',', xaxisLabelsFormattedUnitsPre: '', xaxisLabelsFormattedUnitsPost: '', xaxisLabelsSize: null, xaxisLabelsFont: null, xaxisLabelsItalic: null, xaxisLabelsBold: null, xaxisLabelsColor: null, xaxisLabelsOffsetx: 0, xaxisLabelsOffsety: 0, xaxisLabelsHalign: null, xaxisLabelsValign: null, xaxisLabelsPosition: 'edge', xaxisLabelsSpecificAlign:'left', xaxisPosition: 'bottom', xaxisPosition: 'bottom', xaxisLabelsAngle: 0, xaxisTitle: '', xaxisTitleBold: null, xaxisTitleSize: null, xaxisTitleFont: null, xaxisTitleColor: null, xaxisTitleItalic: null, xaxisTitlePos: null, xaxisTitleOffsetx: 0, xaxisTitleOffsety: 0, xaxisTitleX: null, xaxisTitleY: null, xaxisTitleHalign: 'center', xaxisTitleValign: 'top', yaxis: true, yaxisLinewidth: 1, yaxisColor: 'black', yaxisTickmarks: true, yaxisTickmarksCount: null, yaxisTickmarksLastTop: null, yaxisTickmarksLastBottom: null, yaxisTickmarksLength: 3, yaxisScale: true, yaxisScaleMin: 0, yaxisScaleMax: null, yaxisScaleUnitsPre: '', yaxisScaleUnitsPost: '', yaxisScaleDecimals: 0, yaxisScalePoint: '.', yaxisScaleThousand: ',', yaxisScaleRound: false, yaxisScaleFormatter: null, yaxisScaleInvert: false, yaxisLabelsSpecific: null, yaxisLabelsCount: 5, yaxisLabelsOffsetx: 0, yaxisLabelsOffsety: 0, yaxisLabelsHalign: null, yaxisLabelsValign: null, yaxisLabelsFont: null, yaxisLabelsSize: null, yaxisLabelsColor: null, yaxisLabelsBold: null, yaxisLabelsItalic: null, yaxisLabelsPosition: 'edge', yaxisPosition: 'left', yaxisTitle: '', yaxisTitleBold: null, yaxisTitleSize: null, yaxisTitleFont: null, yaxisTitleColor: null, yaxisTitleItalic: null, yaxisTitlePos: null, yaxisTitleX: null, yaxisTitleY: null, yaxisTitleOffsetx: 0, yaxisTitleOffsety: 0, yaxisTitleHalign: null, yaxisTitleValign: null, yaxisTitleAccessible: null, labelsAbove: false, labelsAboveDecimals: null, labelsAboveSize: null, labelsAboveColor: null, labelsAboveFont: null, labelsAboveBold: null, labelsAboveItalic: null, labelsAboveBackground: 'rgba(255,255,255,0.7)', labelsAboveBorder: false, labelsAboveUnitsPre: '', labelsAboveUnitsPost: '', labelsAboveSpecific: null, labelsAboveOffsetx: 0, labelsAboveOffsety: 0, labelsAboveFormatter: null, linewidth: 2.001, linecap: 'round', linejoin: 'round', colors: ['red', '#0f0', '#00f', '#f0f', '#ff0', '#0ff','green','pink','blue','black'], tickmarksStyle: 'none', tickmarksLinewidth: null, tickmarksSize: 3, tickmarksColor: null, tickmarksStyleDotStroke: 'white', tickmarksStyleDotFill: null, tickmarksStyleDotLinewidth: 3, tickmarksStyleImage: null, tickmarksStyleImageHalign: 'center', tickmarksStyleImageValign: 'center', tickmarksStyleImageOffsetx: 0, tickmarksStyleImageOffsety: 0, marginLeft: 35, marginRight: 35, marginTop: 35, marginBottom: 35, marginInner: 0, filledColors: null, textBold: false, textItalic: false, textSize: 12, textColor: 'black', textFont: "Arial, Verdana, sans-serif", textAccessible: false, textAccessibleOverflow: 'visible', textAccessiblePointerevents:false, text: null, title: '', titleFont: null, titleSize: null, titleColor: null, titleBold: null, titleItalic: null, titleX: null, titleY: null, titleHalign: null, titleValign: null, titleOffsetx: 0, titleOffsety: 0, titleSubtitle: '', titleSubtitleSize: null, titleSubtitleColor: '#aaa', titleSubtitleFont: null, titleSubtitleBold: null, titleSubtitleItalic: null, titleSubtitleOffsetx: 0, titleSubtitleOffsety: 0, shadow: true, shadowOffsetx: 2, shadowOffsety: 2, shadowBlur: 3, shadowColor: 'rgba(128,128,128,0.5)', tooltips: null, tooltipsHotspotXonly: false, tooltipsHotspotSize: 5, tooltipsHotspotIgnore: null, tooltipsEffect: 'slide', tooltipsCssClass: 'RGraph_tooltip', tooltipsCss: null, tooltipsEvent: 'onmousemove', tooltipsHighlight: true, tooltipsCoordsPage: false, tooltipsFormattedThousand: ',', tooltipsFormattedPoint: '.', tooltipsFormattedDecimals: 0, tooltipsFormattedUnitsPre: '', tooltipsFormattedUnitsPost: '', tooltipsFormattedKeyColors: null, tooltipsFormattedKeyColorsShape: 'square', tooltipsFormattedKeyLabels: [], tooltipsFormattedListType: 'ul', tooltipsFormattedListItems: null, tooltipsFormattedTableHeaders: null, tooltipsFormattedTableData: null, tooltipsDataset: null, tooltipsDatasetEvent: 'click', tooltipsPointer: true, tooltipsPointerOffsetx: 0, tooltipsPointerOffsety: 0, tooltipsPositionStatic: true, highlightStyle: null, highlightStroke: 'gray', highlightFill: 'white', highlightPointRadius: 2, highlightDataset: false, highlightDatasetStroke: 'rgba(0,0,0,0.25)', highlightDatasetFill: 'rgba(255,255,255,0.75)', highlightDatasetStrokeAlpha: 1, highlightDatasetFillAlpha: 1, highlightDatasetFillUseColors: false, highlightDatasetStrokeUseColors: false, highlightDatasetLinewidth: null, highlightDatasetDotted: false, highlightDatasetDashed: false, highlightDatasetDashArray: null, highlightDatasetExclude: null, highlightDatasetCallback: null, highlightDatasetEvent: 'click', stepped: false, key: null, keyBackground: 'white', keyPosition: 'graph', keyHalign: null, keyShadow: false, keyShadowColor: '#666', keyShadowBlur: 3, keyShadowOffsetx: 2, keyShadowOffsety: 2, keyPositionMarginBoxed: false, keyPositionMarginHSpace: 0, keyPositionX: null, keyPositionY: null, keyColorShape: 'square', keyRounded: true, keyLinewidth: 1, keyColors: null, keyInteractive: false, keyInteractiveHighlightChartStroke: 'rgba(255,0,0,0.3)', keyInteractiveHighlightLabel: 'rgba(255,0,0,0.2)', keyLabelsFont: null, keyLabelsSize: null, keyLabelsColor: null, keyLabelsBold: null, keyLabelsItalic: null, keyLabelsOffsetx: 0, keyLabelsOffsety: 0, keyFormattedDecimals: 0, keyFormattedPoint: '.', keyFormattedThousand: ',', keyFormattedUnitsPre: '', keyFormattedUnitsPost: '', keyFormattedValueSpecific: null, keyFormattedItemsCount: null, contextmenu: null, crosshairs: false, crosshairsColor: '#333', crosshairsLinewidth: 1, crosshairsHline: true, crosshairsVline: true, crosshairsSnapToScale: false, annotatable: false, annotatableColor: 'black', annotatableLinewidth: 1, filled: false, filledRange: false, filledRangeThreshold: null, filledRangeThresholdColors: ['red', 'green'], filledAccumulative: true, variant: null, axesAbove: false, backdrop: false, backdropSize: 30, backdropAlpha: 0.2, adjustable: false, adjustableOnly: null, adjustableXonly: false, redraw: true, outofbounds: false, outofboundsClip: false, animationFactor: 1, animationUnfoldX: false, animationUnfoldY: true, animationUnfoldInitial: 2, animationTraceClip: 1, animationTraceCenter: false, spline: false, lineVisible: [], errorbars: false, errorbarsColor: 'black', errorbarsCapped: true, errorbarsCappedWidth: 12, errorbarsLinewidth: 1, combinedEffect: null, combinedEffectOptions: null, combinedEffectCallback: null, clearto: 'rgba(0,0,0,0)', dotted: false, dashed: false, trendline: false, trendlineColors: ['#666'], trendlineLinewidth: 1, trendlineMargin: 25, trendlineDashed: false, trendlineDotted: false, trendlineDashArray: null, trendlineClip: true, nullBridge: false, nullBridgeLinewidth: null, nullBridgeColors: null, // Can be null, a string or an object nullBridgeDashArray: [5,5], labelsAngled: false, labelsAngledSpecific: null, labelsAngledAccessible: null, labelsAngledFont: null, labelsAngledColor: null, labelsAngledSize: null, labelsAngledBold: null, labelsAngledItalic: null, labelsAngledUpFont: null, labelsAngledUpColor: null, labelsAngledUpSize: null, labelsAngledUpBold: null, labelsAngledUpItalic: null, labelsAngledDownFont: null, labelsAngledDownColor: null, labelsAngledDownSize: null, labelsAngledDownBold: null, labelsAngledDownItalic: null, labelsAngledLevelFont: null, labelsAngledLevelColor: null, labelsAngledLevelSize: null, labelsAngledLevelBold: null, labelsAngledILeveltalic:null, lines: null // Used to show an average line indicator (for example) } // // 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; } } // Convert strings to numbers this.original_data = RGraph.stringsToNumbers(conf.data); // // Store the original data. This also allows for giving arguments as one big array. // if (typeof this.original_data[0] === 'number' || RGraph.isNull(this.original_data[0])) { this.original_data = [RGraph.arrayClone(this.original_data)]; } // Some of the animations actually modify the // .original_data array so we need to keep a // copy of the unmodified, unmodified data. this // variable is used to access that data in those // effects (eg the wave effect). this.unmodified_data = RGraph.arrayClone(this.original_data); // Check for support if (!this.canvas) { alert('[LINE] Fatal error: no canvas support'); return; } // // Store the data here as one big array // this.data_arr = RGraph.arrayLinearize(this.original_data); for (var i=0; i 1 && properties.filledAccumulative) { var accumulation = []; for (var set=0; set 0) { RGraph.drawBars(this); } if (!properties.axesAbove) { this.drawAxes(); } // // This facilitates the new Trace2 effect // this.context.save() this.context.beginPath(); // The clipping region is idfferent based on th animationTraceCenter option if (properties.animationTraceCenter) { this.context.rect( (this.canvas.width / 2) * (1 - properties.animationTraceClip), 0, this.canvas.width * properties.animationTraceClip, this.canvas.height ); } else { this.context.rect(0, 0, this.canvas.width * properties.animationTraceClip, this.canvas.height); } this.context.clip(); for (var i=0, j=0, len=this.data.length; i 0) { var fill = properties.filledColors; } else if (typeof properties.filledColors == 'string') { var fill = properties.filledColors; } } else if (properties.filled) { var fill = properties.colors[j]; } else { var fill = null; } // // Figure out the tickmark to use // if (properties.tickmarksStyle && typeof properties.tickmarksStyle == 'object') { var tickmarks = properties.tickmarksStyle[i]; } else if (properties.tickmarksStyle && typeof properties.tickmarksStyle == 'string') { var tickmarks = properties.tickmarksStyle; } else if (properties.tickmarksStyle && typeof properties.tickmarksStyle == 'function') { var tickmarks = properties.tickmarksStyle; } else { var tickmarks = null; } // // Draw the line, accounting for the outofboundsClip option // if (properties.outofboundsClip) { this.path( 'sa b r % % % % cl b', 0,this.marginTop,this.canvas.width,this.canvas.height - this.marginTop - this.marginBottom ); } this.drawLine( this.data[i], properties.colors[j], fill, this.getLineWidth(j), tickmarks, i ); if (properties.outofboundsClip) { this.context.restore(); } this.context.stroke(); } // // If the line is filled re-stroke the lines // if (properties.outofboundsClip) { this.path( 'sa b r % % % % cl b', 0,this.marginTop,this.canvas.width,this.canvas.height - this.marginTop - this.marginBottom ); } if (properties.filled && properties.filledAccumulative && !properties.spline) { for (var i=0; i=len; --i) { if (!RGraph.isNull(this.coords[i][1])) { if (i == (this.coords.length - 1)) { this.context.moveTo(this.coords[i][0], this.coords[i][1]); } else { this.context.lineTo(this.coords[i][0], this.coords[i][1]); } } } this.context.stroke(); } else if (properties.filled && properties.filledRange) { alert('[LINE] You must have only two sets of data for a filled range chart'); } // Add trendlines if they have been enabled for (var i=0; i 0) { var prevLineCoords = this.coords2[index - 1]; } this.setLinecap({index: index}); this.setLinejoin({index: index}); // Work out the X interval var xInterval = (this.canvas.width - (2 * properties.marginInner) - this.marginLeft - this.marginRight) / (lineData.length - 1); // Loop thru each value given, plotting the line // (FORMERLY FIRST) for (i=0,len=lineData.length; i this.max) && !properties.outofbounds)) { yPos = null; } // Plot the line if we're at least on the second iteration if (i > 0) { xPos = xPos + xInterval; } else { xPos = properties.marginInner + this.marginLeft; } if (properties.animationUnfoldX) { xPos *= properties.animationFactor; if (xPos < properties.marginLeft) { xPos = properties.marginLeft; } } // // Add the coords to an array // this.coords.push([xPos, yPos]); lineCoords.push([xPos, yPos]); } this.context.stroke(); // Store the coords in another format, indexed by line number this.coords2[index] = lineCoords; // // Now draw the actual line [FORMERLY SECOND] // this.context.beginPath(); // Transparent now as of 11/19/2011 this.context.strokeStyle = 'rgba(0,0,0,0)'; //this.context.strokeStyle = fill; if (fill) { this.context.fillStyle = fill; } var isStepped = properties.stepped; var isFilled = properties.filled; if ( properties.xaxisPosition == 'top') { var xAxisPos = this.marginTop; } else if ( properties.xaxisPosition == 'center') { var xAxisPos = this.marginTop + (this.grapharea / 2); } else if ( properties.xaxisPosition == 'bottom') { var xAxisPos = this.getYCoord( properties.yaxisScaleMin) } for (var i=0,len=lineCoords.length; i (this.canvas.height - this.marginBottom) ) ) { penUp = true; } if (i == 0 || penUp || !yPos || !prevY || prevY < this.marginTop) { if (properties.filled && !properties.filledRange) { if (!properties.outofbounds || prevY === null || yPos === null) { this.context.moveTo(xPos + 1, xAxisPos); } // This facilitates the X axis being at the top // NOTE: Also done below if ( properties.xaxisPosition == 'top') { this.context.moveTo(xPos + 1, xAxisPos); } if (isStepped && i > 0) { this.context.lineTo(xPos, lineCoords[i - 1][1]); } this.context.lineTo(xPos, yPos); } else { if (RGraph.ISOLD && yPos == null) { // Nada } else { this.context.moveTo(xPos + 1, yPos); } } if (yPos == null) { penUp = true; } else { penUp = false; } } else { // Draw the stepped part of stepped lines if (isStepped) { this.context.lineTo(xPos, lineCoords[i - 1][1]); } if ((yPos >= this.marginTop && yPos <= (this.canvas.height - this.marginBottom)) || properties.outofbounds ) { if (isLast && properties.filled && !properties.filledRange && properties.yaxisPosition == 'right') { xPos -= 1; } // Added 8th September 2009 if (!isStepped || !isLast) { this.context.lineTo(xPos, yPos); if (isFilled && lineCoords[i+1] && lineCoords[i+1][1] == null) { this.context.lineTo(xPos, xAxisPos); } // Added August 2010 } else if (isStepped && isLast) { this.context.lineTo(xPos,yPos); } penUp = false; } else { penUp = true; } } } // // Draw a line to the X axis if the chart is filled // if (properties.filled && !properties.filledRange && !properties.spline) { // Is this needed ?? var fillStyle = properties.filledColors; // // Draw the bottom edge of the filled bit using either the X axis or the prevlinedata, // depending on the index of the line. The first line uses the X axis, and subsequent // lines use the prevLineCoords array // if (index > 0 && properties.filledAccumulative) { this.context.lineTo(xPos, prevLineCoords ? prevLineCoords[i - 1][1] : (this.canvas.height - this.marginBottom - 1 + ( properties.xaxisPosition == 'center' ? (this.canvas.height - this.marginTop - this.marginBottom) / 2 : 0))); for (var k=(i - 1); k>=0; --k) { this.context.lineTo(k == 0 ? prevLineCoords[k][0] + 1: prevLineCoords[k][0], prevLineCoords[k][1]); } } else { // Draw a line down to the X axis if ( properties.xaxisPosition == 'top') { this.context.lineTo(xPos, properties.marginTop + 1); this.context.lineTo(lineCoords[0][0], properties.marginTop + 1); } else if (typeof lineCoords[i - 1][1] == 'number') { var yPosition = this.getYCoord(0); this.context.lineTo(xPos,yPosition); this.context.lineTo(lineCoords[0][0],yPosition); } } this.context.fillStyle = !this.hidden(index) ? fill : 'rgba(0,0,0,0)'; this.context.fill(); this.context.beginPath(); } this.context.stroke(); if (properties.backdrop) { this.drawBackdrop(lineCoords, color); } // // TODO CLIP TRACE // By using the clip() method the Trace animation can be updated. // NOTE: Needs to be done for the filled part as well // NOTE: This appears to have been done? // this.context.save(); this.context.beginPath(); // The clipping region is different based on th animationTraceCenter option if (properties.animationTraceCenter) { this.context.rect( (this.canvas.width / 2) * (1 - properties.animationTraceClip), 0, this.canvas.width * properties.animationTraceClip, this.canvas.height ); } else { this.context.rect(0, 0, this.canvas.width * properties.animationTraceClip, this.canvas.height); } this.context.clip(); // // Draw errorbars // if (typeof properties.errorbars !== 'null') { this.drawErrorbars(); } // Now redraw the lines with the correct line width this.setShadow(index); this.redrawLine(lineCoords, color, linewidth, index); this.context.stroke(); RGraph.noShadow(this); // Draw the tickmarks for (var i=0; i (this.canvas.height - this.marginBottom)) && !properties.outofbounds) { return; } else if ((yPos < this.marginTop) && !properties.outofbounds) { return; } this.context.beginPath(); var offset = 0; // Reset the stroke and lineWidth back to the same as what they were when the line was drawn // UPDATE 28th July 2011 - the line width is now set to 1 this.path( 'lw % ss % fs %', properties.tickmarksLinewidth ? properties.tickmarksLinewidth : properties.linewidth, isShadow ? properties.shadowColor : color, isShadow ? properties.shadowColor : color ); // Cicular tick marks if ( tickmarks == 'circle' || tickmarks == 'round' || tickmarks == 'filledcircle' || tickmarks == 'endcircle' || tickmarks === 'filledendcircle') { if (tickmarks == 'round'|| tickmarks == 'circle'|| tickmarks == 'filledcircle' || ((tickmarks == 'endcircle' || tickmarks === 'filledendcircle') && (index == 0 || index == (lineData.length - 1)))) { this.path( 'b a % % % % % %', xPos + offset,yPos + offset,properties.tickmarksSize,0,360 / (180 / RGraph.PI),false ); if (tickmarks.indexOf('filled') !== -1) { this.path( 'fs %', isShadow ? properties.shadowColor : color ); } else { this.path( 'fs %', isShadow ? properties.shadowColor : 'white' ); } this.context.fill(); this.context.stroke(); } // Halfheight "Line" style tick marks } else if (tickmarks == 'halftick') { this.path( 'b m % % l % % s null', Math.round(xPos), yPos, Math.round(xPos), yPos + properties.tickmarksSize ); // Tick style tickmarks } else if (tickmarks == 'tick') { this.path( 'b m % % l % % s', Math.round(xPos), yPos - properties.tickmarksSize, Math.round(xPos), yPos + properties.tickmarksSize ); // Endtick style tickmarks } else if (tickmarks == 'endtick' && (index == 0 || index == (lineData.length - 1))) { this.path( 'b m % % l % % s', Math.round(xPos), yPos - properties.tickmarksSize, Math.round(xPos), yPos + properties.tickmarksSize ); // "Cross" style tick marks } else if (tickmarks == 'cross') { var ticksize = properties.tickmarksSize; this.path( 'b m % % l % % m % % l % % s %', xPos - ticksize, yPos - ticksize, xPos + ticksize, yPos + ticksize, xPos + ticksize, yPos - ticksize, xPos - ticksize, yPos + ticksize, color ); // Triangle style tick marks } else if (tickmarks == 'triangle' || tickmarks == 'filledtriangle' || (tickmarks == 'endtriangle' && (index == 0 || index == (lineData.length - 1)))) { this.path( 'b m % % l % % l % % c f % s null', Math.round(xPos - properties.tickmarksSize), yPos + properties.tickmarksSize, Math.round(xPos), yPos - properties.tickmarksSize, Math.round(xPos + properties.tickmarksSize), yPos + properties.tickmarksSize, tickmarks === 'filledtriangle' ? (isShadow ? properties.shadowColor : this.context.strokeStyle) : 'white' ); // // A white bordered circle // } else if (tickmarks == 'borderedcircle' || tickmarks == 'dot') { this.path( 'lw % b a % % % % % false c f % s %', properties.tickmarksStyleDotLinewidth || 0.00000001, xPos, yPos, properties.tickmarksSize, 0, 360 / (180 / RGraph.PI), properties.tickmarksStyleDotFill || color, properties.tickmarksStyleDotStroke || color ); } else if ( tickmarks == 'square' || tickmarks == 'rect' || tickmarks == 'filledsquare' || (tickmarks == 'endsquare' && (index == 0 || index == (lineData.length - 1))) || (tickmarks == 'filledendsquare' && (index == 0 || index == (lineData.length - 1))) ) { this.path( 'b r % % % % f % s %', Math.round(xPos - properties.tickmarksSize), Math.round(yPos - properties.tickmarksSize), properties.tickmarksSize * 2, properties.tickmarksSize * 2, 'white', this.context.strokeStyle ); // Fillrect if (tickmarks == 'filledsquare' || tickmarks == 'filledendsquare') { this.path( 'b r % % % % f %', Math.round(xPos - properties.tickmarksSize), Math.round(yPos - properties.tickmarksSize), properties.tickmarksSize * 2, properties.tickmarksSize * 2, isShadow ? properties.shadowColor : this.context.strokeStyle ); } this.path('f null s null'); // // Diamond style tickmarks // } else if ( tickmarks === 'diamond' || tickmarks === 'filleddiamond' || (tickmarks === 'enddiamond' && (index == 0 || index == (lineData.length - 1))) || (tickmarks === 'filledenddiamond' && (index == 0 || index == (lineData.length - 1))) ) { this.path( 'b m % % l % % l % % l % % c f % s', xPos - properties.tickmarksSize, yPos, xPos, yPos - properties.tickmarksSize, xPos + properties.tickmarksSize, yPos, xPos, yPos + properties.tickmarksSize, tickmarks.substr(0, 6) === 'filled' ? (isShadow ? properties.shadowColor : this.context.strokeStyle) : 'white' ); // // Filled arrowhead // } else if (tickmarks == 'filledarrow') { // If the spline option is enabled then update the // variables that are used to calculate the arrow if (properties.spline) { xPos = this.coordsSpline[dataset][this.coordsSpline[dataset].length - 1][0]; yPos = this.coordsSpline[dataset][this.coordsSpline[dataset].length - 1][1]; prevX = this.coordsSpline[dataset][this.coordsSpline[dataset].length - 3][0]; prevY = this.coordsSpline[dataset][this.coordsSpline[dataset].length - 3][1]; } var x = Math.abs(xPos - prevX); var y = Math.abs(yPos - prevY); if (yPos < prevY) { var a = Math.atan(x / y) + 1.57; } else { var a = Math.atan(y / x) + 3.14; } this.path( 'b lj miter m % % a % % % % % false a % % % % % false c s % f %', xPos, yPos, xPos, yPos, properties.tickmarksSize, a - 0.3, a - 0.3, xPos, yPos, properties.tickmarksSize, a + 0.3, a + 0.3, this.context.strokeStyle, this.context.fillStyle ); // // Arrow head, NOT filled // } else if (tickmarks === 'arrow') { // If the spline option is enabled then update the // variables that are used to calculate the arrow if (properties.spline) { xPos = this.coordsSpline[dataset][this.coordsSpline[dataset].length - 1][0]; yPos = this.coordsSpline[dataset][this.coordsSpline[dataset].length - 1][1]; prevX = this.coordsSpline[dataset][this.coordsSpline[dataset].length - 2][0]; prevY = this.coordsSpline[dataset][this.coordsSpline[dataset].length - 2][1]; } var orig_linewidth = this.context.lineWidth; var x = Math.abs(xPos - prevX); var y = Math.abs(yPos - prevY); this.context.lineWidth; if (yPos < prevY) { var a = Math.atan(x / y) + 1.57; } else { var a = Math.atan(y / x) + 3.14; } this.path( 'b lj miter m % % a % % % % % false m % % a % % % % % false s % lw %', xPos, yPos, xPos, yPos, properties.tickmarksSize, a - 0.3, a - 0.3, xPos, yPos, xPos, yPos, properties.tickmarksSize, a + 0.3, a + 0.3, this.context.strokeStyle, orig_linewidth ); // // Image based tickmark // // lineData, xPos, yPos, color, isShadow, prevX, prevY, tickmarks, index } else if ( typeof tickmarks === 'string' && ( tickmarks.substr(0, 6) === 'image:' || tickmarks.substr(0, 5) === 'data:' || tickmarks.substr(0, 1) === '/' || tickmarks.substr(0, 3) === '../' || tickmarks.substr(0, 7) === 'images/' || tickmarks.substr(0, 4) === 'src:' ) ) { var img = new Image(); if (tickmarks.substr(0, 6) === 'image:') { img.src = tickmarks.substr(6); } else if (tickmarks.substr(0, 4) === 'src:') { img.src = tickmarks.substr(4); } else { img.src = tickmarks; } var obj = this; img.onload = function () { if (properties.tickmarksStyleImageHalign === 'center') xPos -= (this.width / 2); if (properties.tickmarksStyleImageHalign === 'right') xPos -= this.width; if (properties.tickmarksStyleImageValign === 'center') yPos -= (this.height / 2); if (properties.tickmarksStyleImageValign === 'bottom') yPos -= this.height; xPos += properties.tickmarksStyleImageOffsetx; yPos += properties.tickmarksStyleImageOffsety; obj.context.drawImage(this, xPos, yPos); }; // // Custom tick drawing function // } else if (typeof tickmarks == 'function') { tickmarks( this, lineData, lineData[index], index, xPos, yPos, color, prevX, prevY ); } }; // // Draws a filled range if necessary // this.drawRange = function () { // // Fill the range if necessary // if (properties.filledRange && properties.filled) { if (RGraph.isNull(properties.filledRangeThreshold)) { properties.filledRangeThreshold = this.ymin properties.filledRangeThresholdColors = [properties.filledColors, properties.filledColors] } for (var idx=0; idx<2; ++idx) { var threshold_colors = properties.filledRangeThresholdColors; var y = this.getYCoord(properties.filledRangeThreshold) this.context.save(); if (idx == 0) { this.context.beginPath(); this.context.rect(0,0,this.canvas.width,y); this.context.clip(); } else { this.context.beginPath(); this.context.rect(0,y,this.canvas.width, this.canvas.height); this.context.clip(); } this.context.beginPath(); this.context.fillStyle = (idx == 1 ? properties.filledRangeThresholdColors[1] : properties.filledRangeThresholdColors[0]); this.context.lineWidth = !this.hidden(idx) ? 1 : 0; var len = (this.coords.length / 2); for (var i=0; i=len; --i) { if (RGraph.isNull(this.coords[i][1])) { this.context.moveTo(this.coords[i][0], this.coords[i][1]) } else { this.context.lineTo(this.coords[i][0], this.coords[i][1]) } //this.context.lineTo(this.coords[i][0], this.coords[i][1]) } // Taken out - 10th Oct 2012 //this.context.stroke(); this.context.fill(); this.context.restore(); } } }; // // Redraws the line with the correct line width etc // // @param array coords The coordinates of the line // this.redrawLine = function (coords, color, linewidth, index) { if (!properties.redraw || properties.filledRange) { return; } this.context.strokeStyle = (typeof color == 'object' && color && color.toString().indexOf('CanvasGradient') == -1 ? color[0] : color); this.context.lineWidth = linewidth; // Added this on 1/1/17 to facilitate dotted and dashed lines if (properties.dotted || properties.dashed ) { if (properties.dashed) { this.context.setLineDash([2,6]) } else if (properties.dotted) { this.context.setLineDash([1,5]) } } if (this.hidden(index)) { this.context.strokeStyle = 'rgba(0,0,0,0)'; } if (properties.spline) { this.drawCurvyLine(coords, this.hidden(index) ? 'rgba(0,0,0,0)' : color, linewidth, index); return; } this.setLinejoin({index: index}); this.setLinecap({index: index}); this.context.beginPath(); var len = coords.length; var width = this.canvas.width var height = this.canvas.height; var penUp = false; for (var i=0; i 0) { var prevX = coords[i - 1][0]; var prevY = coords[i - 1][1]; } if (( (i == 0 && coords[i]) || (yPos < this.marginTop) || (prevY < this.marginTop) || (yPos > (height - this.marginBottom)) || (i > 0 && prevX > (width - this.marginRight)) || (i > 0 && prevY > (height - this.marginBottom)) || prevY == null || penUp == true ) && (!properties.outofbounds || yPos == null || prevY == null) ) { if (RGraph.ISOLD && yPos == null) { // ...? } else { this.context.moveTo(coords[i][0], coords[i][1]); } penUp = false; } else { if (properties.stepped && i > 0) { this.context.lineTo(coords[i][0], coords[i - 1][1]); } // Don't draw the last bit of a stepped chart. Now DO //if (!this.properties.stepped || i < (coords.length - 1)) { this.context.lineTo(coords[i][0], coords[i][1]); //} penUp = false; } } // // If two colors are specified instead of one, go over the up bits // if ( properties.colorsAlternate && typeof color == 'object' && color[0] && color[1]) { for (var i=1; i this.data[dataset].length) { idx -= this.data[dataset].length; dataset++; } // Do this if the hotspot is triggered by the X coord AND the Y coord if ( mouseX <= (x + properties.tooltipsHotspotSize) && mouseX >= (x - properties.tooltipsHotspotSize) && mouseY <= (y + properties.tooltipsHotspotSize) && mouseY >= (y - properties.tooltipsHotspotSize) && (this.properties.clip ? RGraph.clipTo.test(this, mouseX, mouseY) : true) ) { if (RGraph.parseTooltipText) { var tooltip = RGraph.parseTooltipText(properties.tooltips, i); } // Don't return points for hidden datasets // Added 10/08/17 // Fixed 22/09/17 Thanks to zsolt - this should be a continue // not a return. if (this.hidden(dataset)) { continue; } return { object: obj, x: x, y: y, dataset: dataset, index: idx, sequentialIndex: i, label: properties.xaxisLabels && typeof properties.xaxisLabels[idx] === 'string' ? properties.xaxisLabels[idx] : null, tooltip: typeof tooltip === 'string' ? tooltip : null }; } else if ( properties.tooltipsHotspotXonly == true && mouseX <= (x + properties.tooltipsHotspotSize) && mouseX >= (x - properties.tooltipsHotspotSize)) { var tooltip = RGraph.parseTooltipText(properties.tooltips, i); return { object: obj, x: x, y: y, dataset: dataset, index: idx, sequentialIndex: i, label: properties.xaxisLabels && typeof properties.xaxisLabels[idx] === 'string' ? properties.xaxisLabels[idx] : null, tooltip: tooltip }; } } }; // // The getShapeByX() method - used to get the point the mouse is currently over, if any // but it ONLY considers the X coordinate - not the Y // // @param object e The event object // @param object OPTIONAL You can pass in the bar object instead of the // function getting it from the canvas // this.getShapeByX = function (e) { var obj = this, mouseXY = RGraph.getMouseXY(e), mouseX = mouseXY[0], mouseY = mouseXY[1]; // This facilitates you being able to pass in the bar object as a parameter instead of // the function getting it from the object if (arguments[1]) { obj = arguments[1]; } for (var i=0; i this.data[dataset].length) { idx -= this.data[dataset].length; dataset++; } if (mouseX <= (x + properties.tooltipsHotspotSize) && mouseX >= (x - properties.tooltipsHotspotSize)) { return { object: obj, x: x, y: y, dataset: dataset, index: idx, sequentialIndex: i, label: properties.xaxisLabels && typeof properties.xaxisLabels[idx] === 'string' ? properties.xaxisLabels[idx] : null }; } } }; // // Draws the above line labels // this.drawAboveLabels = function () { var units_pre = properties.labelsAboveUnitsPre, units_post = properties.labelsAboveUnitsPost, decimals = properties.labelsAboveDecimals, point = properties.labelsAbovePoint, thousand = properties.labelsAboveThousand, bgcolor = properties.labelsAboveBackground || 'white', border = (( typeof properties.labelsAboveBorder === 'boolean' || typeof properties.labelsAboveBorder === 'number' ) ? properties.labelsAboveBorder : true), offsety = properties.labelsAboveOffsety, specific = properties.labelsAboveSpecific, formatter = properties.labelsAboveFormatter, data_arr = RGraph.arrayLinearize(this.original_data);; var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsAbove' }); offsety -= textConf.size; // Use this to 'reset' the drawing state this.context.beginPath(); // Don't need to check that chart.labels.above is enabled here, it's been done already for (var i=0, len=this.coords.length; i 0) { for (var i=(this.coordsSpline[index - 1].length - 1); i>=0; i-=1) { this.context.lineTo(this.coordsSpline[index - 1][i][0], this.coordsSpline[index - 1][i][1]); } } else { this.context.lineTo(coords[coords.length - 1][0],xaxisY); } this.context.fill(); } this.context.beginPath(); this.drawSpline(this.context, yCoords, color, index); this.context.stroke(); }; // // When you click on the chart, this method can return the Y value at that point. It works for any point on the // chart (that is inside the gutters) - not just points on the Line. // // @param object e The event object // this.getValue = function (arg) { if (arg.length == 2) { var mouseX = arg[0]; var mouseY = arg[1]; } else { var mouseCoords = RGraph.getMouseXY(arg); var mouseX = mouseCoords[0]; var mouseY = mouseCoords[1]; } var obj = this; var xaxispos = properties.xaxisPosition; if (mouseY < properties.marginTop) { return xaxispos == 'bottom' || xaxispos == 'center' ? this.max : this.min; } else if (mouseY > (this.canvas.height - properties.marginBottom)) { return xaxispos == 'bottom' ? this.min : this.max; } if ( properties.xaxisPosition == 'center') { var value = (( (obj.grapharea / 2) - (mouseY - properties.marginTop)) / obj.grapharea) * (obj.max - obj.min); value *= 2; value > 0 ? value += this.min : value -= this.min; return value; } else if ( properties.xaxisPosition == 'top') { var value = ((obj.grapharea - (mouseY - properties.marginTop)) / obj.grapharea) * (obj.max - obj.min); value = Math.abs(obj.max - value) * -1; return value; } else { var value = ((obj.grapharea - (mouseY - properties.marginTop)) / obj.grapharea) * (obj.max - obj.min) value += obj.min; return value; } }; // // Each object type has its own Highlight() function which highlights the appropriate shape // // @param object shape The shape to highlight // this.highlight = function (shape) { if (properties.tooltipsHighlight) { if (typeof properties.highlightStyle === 'function') { (properties.highlightStyle)(shape); // Inverted highlighting } else if (properties.highlightStyle === 'invert') { // Clip to the graph area this.path( 'sa b r % % % % cl', properties.marginLeft, properties.marginTop, this.canvas.width - properties.marginLeft - properties.marginRight, this.canvas.height - properties.marginTop - properties.marginBottom ); this.path( 'b m % % a % % 25 4.71 4.72 true l % % l % % l % % l % % l % % c f %', shape.x, properties.marginTop, shape.x, shape.y, shape.x, properties.marginTop, this.canvas.width - properties.marginRight, properties.marginTop, this.canvas.width - properties.marginRight, this.canvas.height - properties.marginBottom, properties.marginLeft, this.canvas.height - properties.marginBottom, properties.marginLeft, properties.marginTop, properties.highlightFill ); // Draw a border around the circular cutout this.path( 'b a % % 25 0 6.29 false s % rs', shape.x, shape.y, properties.highlightStroke ); // Halo style highlighting } else if (properties.highlightStyle === 'halo') { var obj = shape.object, color = properties.colors[shape.dataset]; // Clear a space in white first for the tickmark obj.path( 'b a % % 13 0 6.2830 false f rgba(255,255,255,0.75)', shape.x, shape.y ); obj.path( 'ga 0.15 b a % % 13 0 6.2830 false f % ga 1', shape.x, shape.y, color ); obj.path( 'b a % % 7 0 6.2830 false f white', shape.x, shape.y ); obj.path( 'b a % % 5 0 6.2830 false f %', shape.x, shape.y, color ); } else { this.context.lineWidth = 1; RGraph.Highlight.point(this, shape); } } }; // // The getObjectByXY() worker method. Don't call this call: // // RGraph.ObjectRegistry.getObjectByXY(e) // // @param object e The event object // this.getObjectByXY = function (e) { var mouseXY = RGraph.getMouseXY(e); // The 5 is so that the cursor doesn't have to be over the graphArea to trigger the hotspot if ( (mouseXY[0] > properties.marginLeft - 5) && mouseXY[0] < (this.canvas.width - properties.marginRight + 5) && mouseXY[1] > ( properties.marginTop - 5) && mouseXY[1] < (this.canvas.height - properties.marginBottom + 5) ) { 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) { // Rounding the value to the given number of decimals make the chart step var value = Number(this.getValue(e)); var shape = RGraph.Registry.get('adjusting.shape'); if (shape) { RGraph.Registry.set('adjusting.shape', shape); this.original_data[shape.dataset][shape.index] = Number(value); RGraph.redrawCanvas(e.target); RGraph.fireCustomEvent(this, 'onadjust'); } } }; // // This function can be used when the canvas is clicked on (or similar - depending on the event) // to retrieve the relevant Y coordinate for a particular value. // // @param int value The value to get the Y coordinate for // this.getYCoord = function (value) { if (arguments[1] === true) { var allowOutOfBounds = true; } if (typeof value != 'number') { return null; } var y; var xaxispos = properties.xaxisPosition; if (xaxispos == 'top') { // Account for negative numbers //if (value < 0) { // value = Math.abs(value); //} y = ((value - this.min) / (this.max - this.min)) * this.grapharea; // Inverted Y labels if ( properties.yaxisScaleInvert) { y = this.grapharea - y; } y = y + this.marginTop } else if (xaxispos == 'center') { y = ((value - this.min) / (this.max - this.min)) * (this.grapharea / 2); y = (this.grapharea / 2) - y; y += this.marginTop; } else { if (!allowOutOfBounds && ((value < this.min || value > this.max) && properties.outofbounds == false) ) { return null; } y = ((value - this.min) / (this.max - this.min)) * this.grapharea; // Inverted Y labels if ( properties.yaxisScaleInvert) { y = this.grapharea - y; } y = this.canvas.height - this.marginBottom - y; } return y; }; // // This function draws a curvy line // // @param object context The 2D context // @param array coords The coordinates // this.drawSpline = function (context, coords, color, index) { this.coordsSpline[index] = []; var xCoords = []; var marginLeft = properties.marginLeft; var marginRight = properties.marginRight; var hmargin = properties.marginInner; var interval = (this.canvas.width - (marginLeft + marginRight) - (2 * hmargin)) / (coords.length - 1); this.context.strokeStyle = color; // // The drawSpline function takes an array of JUST Y // coords - not X/Y coords. So the line coords need // converting if we've been given X/Y pairs // for (var i=0,len=coords.length; i