// 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.Scatter = 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; // If setting the colors, update the originalColors // property too if (name === 'colors') { this.originalColors = RGraph.SVG.arrayClone(value); this.colorsParsed = false; } // BC for labelsAboveSeperator if (name === 'labelsAboveSeperator') { name = labelsAboveSeparator; } this.properties[name] = value; } 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 = 'scatter'; 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.clipid = null; // Used to clip the canvas this.isRGraph = true; this.isrgraph = true; this.rgraph = true; this.width = Number(this.svg.getAttribute('width')); this.height = Number(this.svg.getAttribute('height')); this.data = conf.data; this.coords = []; this.coords2 = []; this.colorsParsed = false; this.originalColors = {}; this.gradientCounter = 1; this.sequential = 0; this.line_groups = []; this.firstDraw = true; // After the first draw this will be false // 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, backgroundColor: null, backgroundImage: null, backgroundImageAspect: 'none', backgroundImageStretch: true, 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, tickmarksStyle: 'cross', tickmarksSize: 7, colors: ['black'], line: false, lineColors: null, lineLinewidth: 1, errorbarsColor: 'black', errorbarsLinewidth: 1, errorbarsCapwidth: 10, 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, 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, xaxisLabelsPosition: 'section', xaxisLabelsPositionEdgeTickmarksCount: 10, xaxisColor: 'black', xaxisLabelsOffsetx: 0, xaxisLabelsOffsety: 0, xaxisLabelsCount: 10, xaxisLabelsFont: null, xaxisLabelsSize: null, xaxisLabelsColor: null, xaxisLabelsBold: null, xaxisLabelsItalic: null, xaxisScaleUnitsPre: '', xaxisScaleUnitsPost: '', xaxisScaleMax: null, xaxisScaleMin: 0, xaxisScalePoint: '.', xaxisRound: false, xaxisScaleThousand: ',', xaxisScaleDecimals: 0, xaxisScaleFormatter: null, 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, labelsAboveFont: null, labelsAboveSize: null, labelsAboveBold: null, labelsAboveItalic: null, labelsAboveColor: null, labelsAboveBackground: 'rgba(255,255,255,0.7)', labelsAboveBackgroundPadding: 2, labelsAboveXUnitsPre: null, labelsAboveXUnitsPost: null, labelsAboveXPoint: null, labelsAboveXThousand: null, labelsAboveXFormatter: null, labelsAboveXDecimals: null, labelsAboveYUnitsPre: null, labelsAboveYUnitsPost: null, labelsAboveYPoint: null, labelsAboveYThousand: null, labelsAboveYFormatter: null, labelsAboveYDecimals: null, labelsAboveOffsetx: 0, labelsAboveOffsety: -10, labelsAboveHalign: 'center', labelsAboveValign: 'bottom', labelsAboveSeparator: ',', 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, highlightStroke: 'rgba(0,0,0,0)', highlightFill: 'rgba(255,255,255,0.7)', highlightLinewidth: 1, 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, key: null, keyColors: null, keyOffsetx: 0, keyOffsety: 0, keyLabelsOffsetx: 0, keyLabelsOffsety: -1, keyLabelsFont: null, keyLabelsSize: null, keyLabelsColor: null, keyLabelsBold: null, keyLabelsItalic: null, bubble: false, bubbleMaxValue: null, bubbleMaxRadius: null, bubbleColorsSolid: false, errorbars: null, errorbarsColor: 'black', errorbarsLinewidth: 1, errorbarsCapwidth: 10, trendline: false, trendlineColors: ['gray'], trendlineLinewidth: 1, trendlineMargin: 15, trendlineDashed: true, trendlineDotted: false, trendlineDashArray: null, trendlineClipping: null, outofbounds: true, 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); // // Set the options that the user has provided // for (i in conf.options) { if (typeof i === 'string') { this.set(i, conf.options[i]); } } // Handles the data that was supplied to the object. If only one dataset // was given, convert it into into a multiple dataset style array if (this.data[0] && !RGraph.SVG.isArray(this.data[0])) { this.data = []; this.data[0] = conf.data; } // Go through the data converting the various X and Y options // from strings to numbers if necessary // // THIS MESSES UP DATETIME CHART // //for (var i=0; i tag that the datapoints are added to // var group = RGraph.SVG.create({ svg: this.svg, type: 'g', parent: group, attr: { className: 'scatter_dataset_' + index + '_' + this.uid } }); // Loop through the data for (var i=0; i tag the points are added to sequential: this.sequential }); // Add the coordinates to the coords arrays this.coords.push({ x: ret.x, y: ret.y, z: ret.size, type: ret.type, element: ret.mark, object: this }); this.coords2[index][i] = { x: ret.x, y: ret.y, z: ret.size, type: ret.type, element: ret.mark, object: this }; this.sequential++ } // // Add tooltip highlight to the point // if ( (typeof data[i].tooltip === 'string' && data[i].tooltip) || (typeof data[i].tooltip === 'number') || (typeof properties.tooltips === 'string') ) { // Convert the tooltip to a string data[i].tooltip = String(data[i].tooltip); // Make the tooltipsEvent default to click if (properties.tooltipsEvent !== 'mousemove') { properties.tooltipsEvent = 'click'; } if (!group_tooltip_hotspots) { var group_tooltip_hotspots = RGraph.SVG.create({ svg: this.svg, parent: this.svgAllGroup, type: 'g', attr: { className: 'rgraph-scatter-tooltip-hotspots' } }); } var rect = RGraph.SVG.create({ svg: this.svg, parent: this.svgAllGroup, type: 'rect', parent: group_tooltip_hotspots, attr: { x: ret.x - (ret.size / 2), y: ret.y - (ret.size / 2), width: ret.size, height: ret.size, fill: 'transparent', stroke: 'transparent', 'stroke-width': 3 }, style: { cursor: 'pointer' } }); // Add the hotspot to the original tickmark ret.mark.hotspot = rect; (function (dataset, index, seq, obj) { rect.addEventListener(properties.tooltipsEvent, function (e) { var tooltip = RGraph.SVG.REG.get('tooltip'); if (tooltip && tooltip.__dataset__ === dataset && tooltip.__index__ === index && tooltip.__object__.uid === obj.uid) { return; } obj.removeHighlight(); // Show the tooltip RGraph.SVG.tooltip({ object: obj, dataset: dataset, index: index, sequentialIndex: seq, text: typeof properties.tooltips === 'string' ? properties.tooltips : obj.data[dataset][index].tooltip, event: e }); // Highlight the shape that has been clicked on if (RGraph.SVG.REG.get('tooltip')) { obj.highlight(this); } }, false); // Install the event listener that changes the // cursor if necessary if (properties.tooltipsEvent === 'click') { rect.addEventListener('mousemove', function (e) { e.target.style.cursor = 'pointer'; }, false); } }(index, i, this.sequential - 1, this)); } } }; // // Draws a single point on the chart // this.drawSinglePoint = function (opt) { var dataset = opt.dataset, datasetIdx = opt.datasetIdx, seq = opt.sequential, point = opt.point, index = opt.index, valueX = opt.point.x, valueY = opt.point.y, conf = opt.point || {}, group = opt.group, coordX = opt.coordx = this.getXCoord(valueX), coordY = opt.coordy = this.getYCoord(valueY); // Get the above label if (conf.labelsAbove) { var above = true; } else if (conf.labelAbove) { var above = true; } else if (conf.above) { var above = true; } // Allow shape to be synonym for type if (typeof conf.type === 'undefined' && typeof conf.shape !== 'undefined') { conf.type = conf.shape; } // set the type to the default if its not set if (typeof conf.type !== 'string') { if (typeof properties.tickmarksStyle === 'string') { conf.type = properties.tickmarksStyle; } else if (typeof properties.tickmarksStyle === 'object' && typeof properties.tickmarksStyle[datasetIdx] === 'string') { conf.type = properties.tickmarksStyle[datasetIdx]; } else { conf.type = 'cross'; } } // set the size to the default if its not set if (typeof conf.size !== 'number' && typeof properties.tickmarksSize === 'number') { conf.size = properties.tickmarksSize; } else if (typeof conf.size !== 'number' && typeof properties.tickmarksSize === 'object' && typeof properties.tickmarksSize[datasetIdx] === 'number') { conf.size = properties.tickmarksSize[datasetIdx]; } // Set the color to the default if its not set and then blacck if thats not set either if (typeof conf.color === 'string') { // nada } else if (typeof properties.colors[datasetIdx] === 'string') { conf.color = properties.colors[datasetIdx]; } else { conf.color = 'black'; } // Set the opacity of this point if (typeof conf.opacity === 'undefined') { conf.opacity = 1; } else if (typeof conf.opacity === 'number') { // nada } // Draw the errorbar here // // First convert the errorbar information in the data into an array in the properties // properties.errorbars = []; for (var ds=0,max=0; ds properties.xaxisScaleMax) { return null; } if (value < properties.xaxisScaleMin) { return null; } x = ((value - properties.xaxisScaleMin) / (properties.xaxisScaleMax - properties.xaxisScaleMin)); x *= (this.width - properties.marginLeft - properties.marginRight); x = properties.marginLeft + x; return x; }; // // This function can be used 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 (value > this.scale.max && properties.outofbounds === false) { return null; } var y, xaxispos = properties.xaxispos; if (value < this.scale.min && properties.outofbounds === false) { 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 // // @param object rect The rectangle to highlight // this.highlight = function (rect) { var cx = parseFloat(rect.getAttribute('x')) + (parseFloat(rect.getAttribute('width')) / 2), cy = parseFloat(rect.getAttribute('y')) + (parseFloat(rect.getAttribute('height')) / 2), radius = parseFloat(rect.getAttribute('width')) + 1; var highlight = RGraph.SVG.create({ svg: this.svg, type: 'circle', parent: this.svgAllGroup, attr: { stroke: properties.highlightStroke, fill: properties.highlightFill, cx: cx, cy: cy, r: radius, 'stroke-width': properties.highlightLinewidth }, style: { pointerEvents: 'none' } }); // Store the highlight rect in the rebistry so // it can be cleared later RGraph.SVG.REG.set('highlight', highlight); //rect.setAttribute('stroke', properties.highlightStroke); //rect.setAttribute('stroke-width', properties.highlightLinewidth); //rect.setAttribute('fill', properties.highlightFill); // Store the highlight rect in the registry so // it can be reset later //RGraph.SVG.REG.set('highlight', rect); }; // // Draws the labelsAbove // // @param opt An object that consists of various arguments to the function // this.drawLabelsAbove = function (opt) { var conf = opt.point, coordX = opt.coordX, coordY = opt.coordY; // Facilitate labelsAboveSpecific if (typeof conf.above === 'string') { var str = conf.above; } else { conf.x = RGraph.SVG.numberFormat({ object: this, num: conf.x.toFixed(properties.labelsAboveXDecimals ), prepend: typeof properties.labelsAboveXUnitsPre === 'string' ? properties.labelsAboveXUnitsPre : null, append: typeof properties.labelsAboveXUnitsPost === 'string' ? properties.labelsAboveXUnitsPost : null, point: typeof properties.labelsAboveXPoint === 'string' ? properties.labelsAboveXPoint : null, thousand: typeof properties.labelsAboveXThousand === 'string' ? properties.labelsAboveXThousand : null, formatter: typeof properties.labelsAboveXFormatter === 'function' ? properties.labelsAboveXFormatter : null }); conf.y = RGraph.SVG.numberFormat({ object: this, num: conf.y.toFixed(properties.labelsAboveYDecimals ), prepend: typeof properties.labelsAboveYUnitsPre === 'string' ? properties.labelsAboveYUnitsPre : null, append: typeof properties.labelsAboveYUnitsPost === 'string' ? properties.labelsAboveYUnitsPost : null, point: typeof properties.labelsAboveYPoint === 'string' ? properties.labelsAboveYPoint : null, thousand: typeof properties.labelsAboveYThousand === 'string' ? properties.labelsAboveYThousand : null, formatter: typeof properties.labelsAboveYFormatter === 'function' ? properties.labelsAboveYFormatter : null }); var str = '{1}{2}{3}'.format( conf.x, properties.labelsAboveSeparator, conf.y ); } // Get the text configuration var textConf = RGraph.SVG.getTextConf({ object: this, prefix: 'labelsAbove' }); // Add the text to the scene var text = RGraph.SVG.text({ object: this, parent: this.svgAllGroup, tag: 'labels.above', text: str, x: parseFloat(coordX) + properties.labelsAboveOffsetx, y: parseFloat(coordY) + properties.labelsAboveOffsety, halign: properties.labelsAboveHalign, valign: properties.labelsAboveValign, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color, background: properties.labelsAboveBackground || null, padding: properties.labelsAboveBackgroundPadding || 0 }); if (this.isTrace) { text.setAttribute( 'clip-path', 'url(#trace-effect-clip)' ); } }; // // This allows for easy specification of gradients // this.parseColors = function () { // TODO Loop thru the data parsing the color for gradients too // Save the original colors so that they can be restored when // the canvas is cleared if (!Object.keys(this.originalColors).length) { this.originalColors = { colors: RGraph.SVG.arrayClone(properties.colors), backgroundGridColor: RGraph.SVG.arrayClone(properties.backgroundGridColor), highlightFill: RGraph.SVG.arrayClone(properties.highlightFill), backgroundColor: RGraph.SVG.arrayClone(properties.backgroundColor) } } // colors var colors = properties.colors; // IMPORTANT: Bubble chart gradients are parse in the drawBubble() // function below if (colors && !properties.bubble) { for (var 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 + ')' ); }; return this; }; // End module pattern })(window, document);