// 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.Segmented = 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; } 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 = 'segmented'; this.min = RGraph.SVG.stringsToNumbers(conf.min); this.max = RGraph.SVG.stringsToNumbers(conf.max); this.value = RGraph.SVG.stringsToNumbers(conf.value); this.currentValue = null; // Used by animations 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.colorsParsed = false; this.originalColors = {}; this.gradientCounter = 1; this.nodes = {}; this.shadowNodes = []; this.firstDraw = true; // After the first draw this will be false // Bounds checking if (this.value > this.max) this.value = this.max; if (this.value < this.min) this.value = this.min; // Add this object to the ObjectRegistry RGraph.SVG.OR.add(this); // Set the DIV container to be inline-block this.container.style.display = 'inline-block'; this.properties = { radius: null, centerx: null, centery: null, width: null, marginLeft: 15, marginRight: 15, marginTop: 15, marginBottom: 15, backgroundColor: 'black', colors: ['red','white'], textFont: 'Arial, Verdana, sans-serif', textSize: 60, textColor: 'gray', textBold: false, textItalic: false, text: null, labelsCenter: true, labelsCenterSpecificFormattedDecimals: 0, labelsCenterSpecificFormattedPoint: '.', labelsCenterSpecificFormattedThousand: ',', labelsCenterSpecificFormattedUnitsPre: '', labelsCenterSpecificFormattedUnitsPost: '', labelsCenterFont: null, labelsCenterSize: null, labelsCenterColor: null, labelsCenterBold: null, labelsCenterItalic: null, labelsCenterUnitsPre: null, labelsCenterUnitsPost: null, labelsCenterDecimals: null, labelsCenterPoint: null, labelsCenterThousand: null, labelsCenterSpecific: null, labelsCenterOffsetx: 0, labelsCenterOffsety: 0, radialsCount: 36, adjustable: false, effectRoundrobinMultiplier: 1, 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'); // Reset this to prevent it from growing this.nodes = {}; // Should the first thing that's done inthe.draw() function // except for the onbeforedraw event this.width = Number(this.svg.getAttribute('width')); this.height = Number(this.svg.getAttribute('height')); // Create the defs tag if necessary RGraph.SVG.createDefs(this); // Add these this.graphWidth = this.width - properties.marginLeft - properties.marginRight; this.graphHeight = this.height - properties.marginTop - properties.marginBottom; // Work out the center point this.centerx = (this.graphWidth / 2) + properties.marginLeft; this.centery = (this.graphHeight / 2) + properties.marginTop; this.radius = Math.min(this.graphWidth / 2, this.graphHeight / 2); // Allow the user to override the calculated centerx/y/radius this.radius = typeof properties.radius === 'number' ? properties.radius : this.radius; this.centerx = typeof properties.centerx === 'number' ? properties.centerx : this.centerx; this.centery = typeof properties.centery === 'number' ? properties.centery : this.centery; // // Allow the centerx/centery/radius to be a plus/minus // if (typeof properties.radius === 'string' && properties.radius.match(/^\+|-\d+$/) ) this.radius += parseFloat(properties.radius); if (typeof properties.centerx === 'string' && properties.centerx.match(/^\+|-\d+$/) ) this.centerx += parseFloat(properties.centerx); if (typeof properties.centery === 'string' && properties.centery.match(/^\+|-\d+$/) ) this.centery += parseFloat(properties.centery); // Parse the colors for gradients RGraph.SVG.resetColorsToOriginalValues({object:this}); this.parseColors(); // Draw the segments this.path = this.drawMeter(); this.currentValue = this.value; // Install clipping if requested if (this.properties.clip) { this.clipid = RGraph.SVG.installClipping(this); // Add the clip ID to the all group this.svgAllGroup.setAttribute( 'clip-path', 'url(#{1})'.format(this.clipid) ); } else { // No clipping - so ensure that there's no clip-path // attribute this.clipid = null; this.svgAllGroup.removeAttribute('clip-path'); } // Draw the labels this.drawLabels(); // // Ajusting // if (properties.adjustable && !this.adjusting_event_listeners_installed) { this.adjusting_mousedown = false; var obj = this; var func = function (e) { var div = e.currentTarget, mouseX = e.offsetX, mouseY = e.offsetY; if (RGraph.SVG.ISFF) { mouseX = e.pageX - e.currentTarget.offsetLeft; mouseY = e.pageY - e.currentTarget.offsetTop; } var radius = RGraph.SVG.TRIG.getHypLength({ x1: mouseX, y1: mouseY, x2: obj.centerx, y2: obj.centery, object: obj }); if (radius > obj.radius) { return; } var value = obj.getValue(e); obj.value = value; obj.currentValue = value; RGraph.SVG.redraw(obj.svg); }; this.container.addEventListener('mousedown', function (e) { obj.adjusting_mousedown = true; func(e); // Fire the adjustbegin event RGraph.SVG.fireCustomEvent(obj, 'onadjustbegin'); }, false); this.container.addEventListener('mousemove', function (e) { if (obj.adjusting_mousedown) { func(e); // Fire the adjust event RGraph.SVG.fireCustomEvent(obj, 'onadjust'); } }, false); window.addEventListener('mouseup', function (e) { obj.adjusting_mousedown = false; // Fire the adjustend event RGraph.SVG.fireCustomEvent(obj, 'onadjustend'); }, false); this.adjusting_event_listeners_installed = true; } // // Allow the addition of custom text via the // text: property. // RGraph.SVG.addCustomText(this); // // Fire the onfirstdraw event // if (this.firstDraw) { this.firstDraw = false; RGraph.SVG.fireCustomEvent(this, 'onfirstdraw'); } // Fire the draw event RGraph.SVG.fireCustomEvent(this, 'ondraw'); // // Install any inline responsive configuration. This // should be last in the draw function - even after // the draw events. // RGraph.SVG.installInlineResponsive(this); return this; }; // // New create() shortcut function // For example: // this.create('rect,x:0,y:0,width:100,height:100'[,parent]); // // @param str string The tag definition to parse and create // @param object The (optional) parent element // @return object The new tag // this.create = function (str) { var def = RGraph.SVG.create.parseStr(this, str); def.svg = this.svg; // By default the parent is the SVG tag - but if // requested then change it to the tag that has // been given if (arguments[1]) { def.parent = arguments[1]; } return RGraph.SVG.create(def); }; // // Draws the meter // this.drawMeter = function () { var width = typeof properties.width === 'number' ? properties.width : this.radius / 2; // Allow for +/-xx style width if (typeof properties.width === 'string') { width += Number(properties.width); } // First thing to do is clear the canvas to the backgroundColor if (properties.backgroundColor) { var rect = RGraph.SVG.create({ svg: this.svg, type: 'rect', parent: this.svgAllGroup, attr: { fill: properties.backgroundColor, x: 0, y: 0, width: this.width, height: this.height } }); } // Draw the circle that becomes the non-indicator part of the chart var degrees = 360 / properties.radialsCount; degrees /= 2, colored = Math.round(((this.value - this.min) / (this.max - this.min)) * (properties.radialsCount * 2)); // Calculate the radial width of each segment var radialWidth = RGraph.SVG.TRIG.TWOPI / properties.radialsCount; // // Draw the background segments // for (var i=1; i<(properties.radialsCount * 2); i+=2) { var start = (RGraph.SVG.TRIG.toRadians({degrees: (i * degrees) - (degrees / 2)}) * properties.effectRoundrobinMultiplier), end = (RGraph.SVG.TRIG.toRadians({degrees: (i * degrees) + (degrees / 2)}) * properties.effectRoundrobinMultiplier); var path = RGraph.SVG.TRIG.getArcPath3({ cx: this.centerx, cy: this.centery, radius: this.radius, start: start, end: end, lineto: false }); var path2 = RGraph.SVG.TRIG.getArcPath3({ cx: this.centerx, cy: this.centery, radius: this.radius - width, start: end, end: start, anticlockwise: true }); // Draw the outer arc RGraph.SVG.create({ svg: this.svg, parent: this.svgAllGroup, type: 'path', attr: { d: path + ' ' + path2 + ' z', fill: i > colored ? properties.colors[1] : properties.colors[0] } }); } }; // // Draw the labels // this.drawLabels = function () { // Draw the center label if (properties.labelsCenter) { var center = RGraph.SVG.numberFormat({ object: this, num: (this.value * this.properties.effectRoundrobinMultiplier).toFixed(properties.labelsCenterDecimals), prepend: properties.labelsCenterUnitsPre, append: properties.labelsCenterUnitsPost, point: properties.labelsCenterPoint, thousand: properties.labelsCenterThousand, formatter: properties.labelsCenterFormatter }); if (properties.labelsCenterSpecific) { properties.labelsCenterSpecific = RGraph.SVG.labelSubstitution({ object: this, text: properties.labelsCenterSpecific, index: 0, value: this.value, decimals: properties.labelsCenterSpecificFormattedDecimals || 0, unitsPre: properties.labelsCenterSpecificFormattedUnitsPre || '', unitsPost: properties.labelsCenterSpecificFormattedUnitsPost || '', thousand: properties.labelsCenterSpecificFormattedThousand || ',', point: properties.labelsCenterSpecificFormattedPoint || '.' }); } // Get the text configuration var textConf = RGraph.SVG.getTextConf({ object: this, prefix: 'labelsCenter' }); var text = RGraph.SVG.text({ object: this, parent: this.svgAllGroup, tag: 'labels.center', text: typeof properties.labelsCenterSpecific === 'string' ? properties.labelsCenterSpecific : center, x: this.centerx + properties.labelsCenterOffsetx, y: this.centery + properties.labelsCenterOffsety, valign: 'center', halign: 'center', font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color }); // Store a reference to the center label this.nodes.labelsCenter = text; } }; // // This allows for easy specification of gradients // this.parseColors = function () { // Save the original colors so that they can be restored when the canvas is reset if (!Object.keys(this.originalColors).length) { this.originalColors = { colors: RGraph.SVG.arrayClone(properties.colors), backgroundColor: RGraph.SVG.arrayClone(properties.backgroundColor) } } // colors var colors = properties.colors; if (colors) { for (var i=0; i= frames) { this.currentValue = this.value; callback(obj); } else { RGraph.SVG.FX.update(iterator); } } iterator(); return this; }; // // RoundRobin // // This effect gradually increases the size of each segment // // @param object OPTIONAL Options for the effect // @param function OPTIONAL A callback function // this.roundrobin = this.roundRobin = function () { var obj = this, opt = arguments[0] || {}, callback = arguments[1] || function () {}, frame = 0, frames = opt.frames || 60, radius = this.radius; var iterator = function () { // Took this straight out of the common core file var multiplier = Math.sin((frame / frames) * RGraph.SVG.TRIG.HALFPI); obj.set('effectRoundrobinMultiplier',multiplier); RGraph.SVG.redraw(); if (frame++ >= frames) { this.currentValue = this.value; callback(obj); } else { RGraph.SVG.FX.update(iterator); } }; iterator(); return this; }; // // 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. // // @param string clipPath The clipPath object/tag // 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 a1 = this.getAngle(from), a2 = this.getAngle(to); // Change the radius if the number is "min" if (RegExp.$1 === 'min') { a1 = this.getAngle(this.min); } // Change the radius if the number is "max" if (RegExp.$2 === 'max') { a2 = this.getAngle(this.max); } var path = RGraph.SVG.TRIG.getArcPath3({ cx: this.centerx, cy: this.centery, radius: Math.max(this.width, this.height), start: a1 + RGraph.SVG.TRIG.HALFPI, end: a2 + RGraph.SVG.TRIG.HALFPI, anticlockwise: false }); RGraph.SVG.create({ svg: this.svg, parent: clipPath, type: 'path', attr: { d: 'M {1} {2} {3} z'.format( this.centerx, this.centery, path ) } }); // Now set the clip-path attribute on the // 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);