// 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}; RGraph.SVG = RGraph.SVG || {}; // Module pattern (function (win, doc, undefined) { RGraph.SVG.Line = function (conf) { // // A setter that the constructor uses (at the end) // to set all of the properties // // @param string name The name of the property to set // @param string value The value to set the property to // this.set = function (name, value) { if (arguments.length === 1 && typeof name === 'object') { for (i in arguments[0]) { if (typeof i === 'string') { name = ret.name; value = ret.value; this.set(name, value); } } } else { // Go through all of the properties and make sure // that they're using the correct capitalisation name = this.properties_lowercase_map[name.toLowerCase()] || name; var ret = RGraph.SVG.commonSetter({ object: this, name: name, value: value }); name = ret.name; value = ret.value; this.properties[name] = value; // If setting the colors, update the originalColors // property too if (name === 'colors') { this.originalColors = RGraph.SVG.arrayClone(value); this.colorsParsed = false; } } return this; }; // // A getter. // // @param name string The name of the property to get // this.get = function (name) { // Go through all of the properties and make sure // that they're using the correct capitalisation name = this.properties_lowercase_map[name.toLowerCase()] || name; return this.properties[name]; }; this.type = 'line'; this.id = conf.id; this.uid = RGraph.SVG.createUID(); this.container = document.getElementById(this.id); this.layers = {}; // MUST be before the SVG tag is created! this.svg = RGraph.SVG.createSVG({ object: this, container: this.container }); this.svgAllGroup = RGraph.SVG.createAllGroup(this); this.isRGraph = true; this.isrgraph = true; this.rgraph = true; this.width = Number(this.svg.getAttribute('width')); this.height = Number(this.svg.getAttribute('height')); this.firstDraw = true; // After the first draw this will be false this.clipid = null; // Used to clip the canvas // Convert strings to numbers conf.data = RGraph.SVG.stringsToNumbers(conf.data); // Convert single datasets to a multi-dimensional format if (RGraph.SVG.isArray(conf.data) && RGraph.SVG.isArray(conf.data[0])) { this.data = RGraph.SVG.arrayClone(conf.data); } else if (RGraph.SVG.isArray(conf.data)) { this.data = [RGraph.SVG.arrayClone(conf.data)]; } else { this.data = [[]]; } this.coords = []; this.coords2 = []; this.coordsSpline = []; this.hasMultipleDatasets = typeof this.data[0] === 'object' && typeof this.data[1] === 'object' ? true : false; this.colorsParsed = false; this.originalColors = {}; this.gradientCounter = 1; this.originalData = RGraph.SVG.arrayClone(this.data); this.filledGroups = []; // Add this object to the ObjectRegistry RGraph.SVG.OR.add(this); this.container.style.display = 'inline-block'; this.properties = { marginLeft: 35, marginRight: 35, marginTop: 35, marginBottom: 35, marginInner: 0, backgroundColor: null, backgroundImage: null, backgroundImageStretch: true, backgroundImageAspect: 'none', backgroundImageOpacity: null, backgroundImageX: null, backgroundImageY: null, backgroundImageW: null, backgroundImageH: null, backgroundGrid: true, backgroundGridColor: '#ddd', backgroundGridLinewidth: 1, backgroundGridHlines: true, backgroundGridHlinesCount: null, backgroundGridVlines: true, backgroundGridVlinesCount: null, backgroundGridBorder: true, backgroundGridDashed: false, backgroundGridDotted: false, backgroundGridDashArray: null, colors: ['red', '#0f0', 'blue', '#ff0', '#0ff', 'green'], filled: false, filledDualColor: false, filledColors: [], filledClick: null, filledOpacity: 1, filledAccumulative: false, yaxis: true, yaxisLinewidth: 1, yaxisTickmarks: true, yaxisTickmarksLength: 3, yaxisColor: 'black', yaxisScale: true, yaxisLabels: null, yaxisLabelsOffsetx: 0, yaxisLabelsOffsety: 0, yaxisLabelsCount: 5, yaxisScaleUnitsPre: '', yaxisScaleUnitsPost: '', yaxisScaleStrict: false, yaxisScaleDecimals: 0, yaxisScalePoint: '.', yaxisScaleThousand: ',', yaxisScaleRound: false, yaxisScaleMax: null, yaxisScaleMin: 0, yaxisScaleFormatter: null, yaxisLabelsFont: null, yaxisLabelsSize: null, yaxisLabelsColor: null, yaxisLabelsBold: null, yaxisLabelsItalic: null, yaxisTitle: '', yaxisTitleBold: null, yaxisTitleSize: null, yaxisTitleFont: null, yaxisTitleColor: null, yaxisTitleItalic: null, yaxisTitleOffsetx: 0, yaxisTitleOffsety: 0, yaxisTitleX: null, yaxisTitleY: null, yaxisTitleHalign: null, yaxisTitleValign: null, xaxis: true, xaxisLinewidth: 1, xaxisTickmarks: true, xaxisTickmarksLength: 5, xaxisLabels: null, xaxisLabelsOffsetx: 0, xaxisLabelsOffsety: 0, xaxisLabelsPosition: 'edge', xaxisLabelsPositionEdgeTickmarksCount: null, xaxisColor: 'black', xaxisLabelsFont: null, xaxisLabelsSize: null, xaxisLabelsColor: null, xaxisLabelsBold: null, xaxisLabelsItalic: null, xaxisLabelsFormattedDecimals: 0, xaxisLabelsFormattedPoint: '.', xaxisLabelsFormattedThousand: ',', xaxisLabelsFormattedUnitsPre: '', xaxisLabelsFormattedUnitsPost: '', xaxisTitle: '', xaxisTitleBold: null, xaxisTitleSize: null, xaxisTitleFont: null, xaxisTitleColor: null, xaxisTitleItalic: null, xaxisTitleOffsetx: 0, xaxisTitleOffsety: 0, xaxisTitleX: null, xaxisTitleY: null, xaxisTitleHalign: null, xaxisTitleValign: null, textColor: 'black', textFont: 'Arial, Verdana, sans-serif', textSize: 12, textBold: false, textItalic: false, text: null, linewidth: 1, linejoin: 'round', linecap: 'round', tooltips: null, tooltipsOverride: null, tooltipsEffect: 'fade', tooltipsCssClass: 'RGraph_tooltip', tooltipsCss: null, tooltipsEvent: 'mousemove', tooltipsFormattedThousand: ',', tooltipsFormattedPoint: '.', tooltipsFormattedDecimals: 0, tooltipsFormattedUnitsPre: '', tooltipsFormattedUnitsPost: '', tooltipsFormattedKeyColors: null, tooltipsFormattedKeyColorsShape: 'square', tooltipsFormattedKeyLabels: [], tooltipsFormattedTableHeaders: null, tooltipsFormattedTableData: null, tooltipsPointer: true, tooltipsPointerOffsetx: 0, tooltipsPointerOffsety: 0, tooltipsPositionStatic: true, tooltipsDataset: null, tooltipsHotspotSize: 5, //highlightStroke: 'rgba(0,0,0,0)', //highlightFill: 'rgba(255,255,255,0.7)', //highlightLinewidth: 1, tickmarksStyle: 'none', tickmarksSize: 5, tickmarksFill: 'white', tickmarksLinewidth: 1, labelsAbove: false, labelsAboveFont: null, labelsAboveSize: null, labelsAboveBold: null, labelsAboveItalic: null, labelsAboveColor: null, labelsAboveBackground: 'rgba(255,255,255,0.7)', labelsAboveBackgroundPadding: 2, labelsAboveUnitsPre: null, labelsAboveUnitsPost: null, labelsAbovePoint: null, labelsAboveThousand: null, labelsAboveFormatter: null, labelsAboveDecimals: null, labelsAboveOffsetx: 0, labelsAboveOffsety: -10, labelsAboveHalign: 'center', labelsAboveValign: 'bottom', labelsAboveSpecific: null, shadow: false, shadowOffsetx: 2, shadowOffsety: 2, shadowBlur: 2, shadowColor: 'rgba(0,0,0,0.25)', spline: false, stepped: false, title: '', titleX: null, titleY: null, titleHalign: 'center', titleValign: null, titleSize: null, titleColor: null, titleFont: null, titleBold: null, titleItalic: null, titleSubtitle: null, titleSubtitleSize: null, titleSubtitleColor: '#aaa', titleSubtitleFont: null, titleSubtitleBold: null, titleSubtitleItalic: null, errorbars: null, errorbarsColor: 'black', errorbarsLinewidth: 1, errorbarsCapwidth: 10, key: null, keyColors: null, keyOffsetx: 0, keyOffsety: 0, keyLabelsOffsetx: 0, keyLabelsOffsety: -1, keyLabelsSize: null, keyLabelsBold: null, keyLabelsItalic: null, keyLabelsFont: null, keyLabelsColor: null, dasharray: null, dashed: false, dotted: false, highlightFill: null, 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: null, clip: null }; // // 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; } } // // Copy the global object properties to this instance // RGraph.SVG.getGlobals(this); // // "Decorate" the object with the generic effects if the effects library has been included // if (RGraph.SVG.FX && typeof RGraph.SVG.FX.decorate === 'function') { RGraph.SVG.FX.decorate(this); } // Add the responsive function to the object this.responsive = RGraph.SVG.responsive; var properties = this.properties; // // The draw method draws the Bar chart // this.draw = function () { // Fire the beforedraw event RGraph.SVG.fireCustomEvent(this, 'onbeforedraw'); // Should the first thing that's done in the.draw() function // except for the onbeforedraw event and the // installation of clipping. this.width = Number(this.svg.getAttribute('width')); this.height = Number(this.svg.getAttribute('height')); // Create the defs tag RGraph.SVG.createDefs(this); this.graphWidth = this.width - properties.marginLeft - properties.marginRight; this.graphHeight = this.height - properties.marginTop - properties.marginBottom; // Parse the colors for gradients RGraph.SVG.resetColorsToOriginalValues({object:this}); this.parseColors(); // Clear the coords arrays this.coords = []; this.coords2 = []; this.coordsSpline = []; // Reset the data back to the original this.data = RGraph.SVG.arrayClone(this.originalData); // Set this to zero this.tooltipsSequentialIndex = 0; // If the line is set to be dotted or dashed then set the dash array if (properties.dashed) { properties.dasharray = [5,5]; } if (properties.dotted) { properties.dasharray = [1,4]; } // Make the data sequential first this.data_seq = RGraph.SVG.arrayLinearize(this.data); // This allows the errorbars to be a variety of formats and convert // them all into an array of objects which have the min and max // properties set if (properties.errorbars) { // Go through the error bars and convert numbers to objects for (var i=0; i 0 && properties.filledAccumulative ? (properties.spline ? this.coordsSpline[index - 1][this.coordsSpline[index - 1].length - 1][1] : this.coords2[index - 1][this.coords2[index - 1].length - 1][1]) : this.getYCoord(properties.yaxisScaleMin > 0 ? properties.yaxisScaleMin : 0) + (properties.xaxis ? 0 : 1) )); if (index > 0 && properties.filledAccumulative) { var path2 = RGraph.SVG.arrayClone(path); if (index > 0 && properties.filledAccumulative) { if (properties.spline) { for (var i=this.coordsSpline[index - 1].length-1; i>=0; --i) { fillPath.push('L{1} {2}'.format( this.coordsSpline[index - 1][i][0], this.coordsSpline[index - 1][i][1] )); } } else { for (var i=this.coords2[index - 1].length-1; i>=0; --i) { fillPath.push('L{1} {2}'.format( this.coords2[index - 1][i][0], this.coords2[index - 1][i][1] )); // For STEPPED charts if (properties.stepped && i > 0) { fillPath.push('L{1} {2}'.format( this.coords2[index - 1][i][0], this.coords2[index - 1][i - 1][1] )); } } } } } else { // This is the bottom left corner. The +1 is so that // the fill doesn't go over the axis fillPath.push('L{1} {2}'.format( this.coords2[index][0][0] + (properties.yaxis ? 0 : 0), // this.coords2[index][0][0] + (properties.yaxis ? 1 : 0), this.getYCoord(properties.yaxisScaleMin > 0 ? properties.yaxisScaleMin : 0) + (properties.xaxis ? 0 : 1) )); } // Find the first none-null value and use that // values X value fillPath.push('L{1} {2}'.format( this.coords2[index][0][0] + (properties.yaxis ? 1 : 0), this.coords2[index][0][1] )); for (var i=0; i 0) { for (var i=this.coords2[index - 1].length-1; i>=0; --i) { path2.push('L{1} {2}'.format( this.coords2[index - 1][i][0], this.coords2[index - 1][i][1] )); } } path2 = path2.join(' '); var line = RGraph.SVG.create({ svg: this.svg, parent: properties.filled ? this.filledGroups[index] : this.svgAllGroup, type: 'path', attr: { d: path2, stroke: properties.colors[index], 'fill':'none', 'stroke-dasharray': properties.dasharray ? properties.dasharray : '', 'stroke-width': this.hasMultipleDatasets && properties.filled && properties.filledAccumulative ? 0.1 : (RGraph.SVG.isArray(properties.linewidth) ? properties.linewidth[index]: properties.linewidth + 0.01), 'stroke-linecap': this.getLinecap({index: index}), 'stroke-linejoin': this.getLinejoin({index: index}), filter: properties.shadow ? 'url(#dropShadow)' : '', 'clip-path': this.isTrace ? 'url(#trace-effect-clip)' : '' } }); } if (properties.tooltips && properties.tooltips.length) { if (!document.getElementsByClassName('rgraph_hotspots').length) { var group = RGraph.SVG.create({ svg: this.svg, // Taken out on 11/12/17 so that hotspots // can sit in this group // // Put back in on 4/2/2024 so that clipping // works correctly // parent: this.svgAllGroup, type: 'g', attr: { fill: 'transparent', className: "rgraph_hotspots" }, style: { cursor: 'pointer' } }); // Store the group so it can be easily got // to later on this.svgAllGroup.line_tooltip_hotspots = group; } else { group = this.svgAllGroup.line_tooltip_hotspots; } //for (var i=0; i this.scale.max) { return null; } if (!allowOutOfBounds && value < this.scale.min) { return null; } y = ((value - this.scale.min) / (this.scale.max - this.scale.min)); y *= (this.height - properties.marginTop - properties.marginBottom); y = this.height - properties.marginBottom - y; return y; }; // // This function can be used to highlight a bar on the chart // // TODO This function looks like its needs updating // // @param object rect The rectangle to highlight // this.highlight = function (rect) { var x = rect.getAttribute('x'), y = rect.getAttribute('y'); }; // // Remove highlight from the chart (tooltips) // this.removeHighlight = function () { //var highlight = RGraph.SVG.REG.get('highlight'); //if (highlight && highlight.parentNode) { // highlight.parentNode.removeChild(highlight); // //// The highlight is an array //} else if (highlight && RGraph.SVG.isArray(highlight)) { // for (var i=0; i 0) { // Draw the UPPER vertical line var errorbarLine = RGraph.SVG.create({ svg: this.svg, type: 'line', parent: this.svgAllGroup, attr: { x1: x, y1: y, x2: x, y2: y1, stroke: color, 'stroke-width': linewidth } }); // Draw the cap to the UPPER line var errorbarCap = RGraph.SVG.create({ svg: this.svg, type: 'line', parent: this.svgAllGroup, attr: { x1: x - halfCapWidth, y1: y1, x2: x + halfCapWidth, y2: y1, stroke: color, 'stroke-width': linewidth } }); } // Draw the minimum errorbar if necessary if (typeof min === 'number') { var errorbarLine = RGraph.SVG.create({ svg: this.svg, type: 'line', parent: this.svgAllGroup, attr: { x1: x, y1: y, x2: x, y2: y3, stroke: color, 'stroke-width': linewidth } }); // Draw the cap to the UPPER line var errorbarCap = RGraph.SVG.create({ svg: this.svg, type: 'line', parent: this.svgAllGroup, attr: { x1: x - halfCapWidth, y1: y3, x2: x + halfCapWidth, y2: y3, stroke: color, 'stroke-width': linewidth } }); } }; // // A trace effect // // @param object Options to give to the effect // @param function A function to call when the effect has completed // this.trace = function () { var opt = arguments[0] || {}, frame = 1, frames = opt.frames || 60, obj = this; this.isTrace = true; this.draw(); // Create the clip area var clippath = RGraph.SVG.create({ svg: this.svg, parent: this.svg.defs, type: 'clipPath', attr: { id: 'trace-effect-clip' } }); var clippathrect = RGraph.SVG.create({ svg: this.svg, parent: clippath, type: 'rect', attr: { x: 0, y: 0, width: 0, height: this.height } }); var iterator = function () { var width = (frame++) / frames * obj.width; clippathrect.setAttribute("width", width); if (frame <= frames) { RGraph.SVG.FX.update(iterator); } else { // Remove the clippath clippath.parentNode.removeChild(clippath); if (opt.callback) { (opt.callback)(obj); } } }; iterator(); return this; }; // // A worker function that handles Bar chart specific tooltip substitutions // this.tooltipSubstitutions = function (opt) { var indexes = RGraph.SVG.sequentialIndexToGrouped(opt.index, this.data); // Create the values array which contains each datasets value for (var i=0,values=[]; i node var els = this.svg.getElementsByClassName('rgraph_background_grid'), grid = els[0]; // Remove the trendline from the DOM obj.svgAllGroup.removeChild(line); // Now re-add it immedately after the background grid grid.insertAdjacentElement('afterend', line); }; // // This is the code the adds lines across null gaps in the // Line chart // // @param number datasetIdx The index of the dataset // @param array data The dataset // this.nullBridge = function (datasetIdx, data) { var readData = false; // // Now add the connecting lines // for (var i=0; i 0) { var previous_dataset = coords[i-1]; path += RGraph.SVG.create.pathString(previous_dataset.toReversed(), 'L'); } // Create the line the goes over the line as a // cover var node = RGraph.SVG.create({ svg: this.svg, type: 'path', parent: this.svgAllGroup, attr: { fill: properties.filled ? '#0000' : 'none', d: path, stroke: '#0000', 'stroke-width': properties.filled ? 0 : Math.max(5, properties.linewidth), 'stroke-linecap':'round' }, style: { cursor: 'pointer' } }); // // Now add the dataset event listener that causes // the dataset toolotip to be shown // var obj = this; (function (dataset) { node.addEventListener(properties.tooltipsEvent, function (e) { if (RGraph.SVG.REG.get('tooltip') && RGraph.SVG.REG.get('tooltip').__dataset__ === dataset && RGraph.SVG.REG.get('tooltip').__object__.uid === obj.uid) { // Added on the 27th/6/2019 return; } // Lose any previous highlighting var previous_highlight = RGraph.SVG.REG.get('tooltip-dataset-highlight'); if (previous_highlight) { previous_highlight.setAttribute(obj.properties.filled ? 'fill' : 'stroke', 'transparent'); RGraph.SVG.REG.set('tooltip-dataset-highlight', null); } // // Calculate a sequential index to give // the tooltip // var seq = 0; for (let i=0; i node // this.clipToScaleWorker = function (clipPath) { // The Regular expression is actually done by the // calling RGraph.clipTo.start() function in the core // library if (RegExp.$1 === 'min') from = this.min; else from = Number(RegExp.$1); if (RegExp.$2 === 'max') to = this.max; else to = Number(RegExp.$2); var width = this.width, y1 = this.getYCoord(from), y2 = this.getYCoord(to), height = Math.abs(y2 - y1), x = 0, y = Math.min(y1, y2); // Increase the height if the maximum value is "max" if (RegExp.$2 === 'max') { y = 0; height += this.properties.marginTop; } // Increase the height if the minimum value is "min" if (RegExp.$1 === 'min') { height += this.properties.marginBottom; } RGraph.SVG.create({ svg: this.svg, type: 'rect', parent: clipPath, attr: { x: x, y: y, width: width, height: height } }); // Now set the clip-path attribute on the first // Line charts all-elements group this.svgAllGroup.setAttribute( 'clip-path', 'url(#' + clipPath.id + ')' ); }; // // Set the options that the user has provided // for (i in conf.options) { if (typeof i === 'string') { this.set(i, conf.options[i]); } } } return this; // End module pattern })(window, document);