/** * @preserve CanvasJS HTML5 & JavaScript Charts - v1.8.0 Beta 3 - http://canvasjs.com/ * Copyright 2013 fenopix */ /* * CanvasJS Charts follows Dual Licensing Model as mentioned below. * * ---------------------Free for Non-Commercial Use-------------------- * * For non-commercial purposes you can use the software for free under Creative Commons Attribution-NonCommercial 3.0 License. Refer to the following link for further details on the same. * http://creativecommons.org/licenses/by-nc/3.0/deed.en_US * * ---------------------Commercial License-------------------- * Commercial use of CanvasJS requires you to purchase a license. Without a commercial license you can use it for evaluation purposes only. Please refer to the following link for further details. * http://canvasjs.com/ * */ /* jshint -W099 */ //Ignore warning "Mixed Spaces and Tabs" (function () { var isDebugMode = false; var isCanvasSupported = !!document.createElement("canvas").getContext; //isCanvasSupported = false; //Default values for all Chart Elements that can be set by the user. CanvasJSObject.setOptions looks into this while setting the default/user-defined values. var defaultOptions = { Chart: { width: 500, height: 400, zoomEnabled: false, zoomType: "x", backgroundColor: "white", theme: "theme1", animationEnabled: false, animationDuration: 1200, dataPointMaxWidth: null, colorSet: "colorSet1", culture: "en", creditText: "CanvasJS.com", interactivityEnabled: true, exportEnabled: false, exportFileName: "Chart", rangeChanging: null, rangeChanged: null }, Title: { padding: 0, text: null, verticalAlign: "top",//top, center, bottom horizontalAlign: "center",//left, center, right fontSize: 20,//in pixels fontFamily: "Calibri", fontWeight: "normal", //normal, bold, bolder, lighter, fontColor: "black", fontStyle: "normal", // normal, italic, oblique borderThickness: 0, borderColor: "black", cornerRadius: 0, backgroundColor: null, margin: 5, wrap: true, maxWidth: null, dockInsidePlotArea: false //toolTipContent: null//string - To be implemented (TBI) }, Subtitle: { padding: 0, text: null, verticalAlign: "top",//top, center, bottom horizontalAlign: "center",//left, center, right fontSize: 14,//in pixels fontFamily: "Calibri", fontWeight: "normal", //normal, bold, bolder, lighter, fontColor: "black", fontStyle: "normal", // normal, italic, oblique borderThickness: 0, borderColor: "black", cornerRadius: 0, backgroundColor: null, margin: 2, wrap: true, maxWidth: null, dockInsidePlotArea: false //toolTipContent: null//string - To be implemented (TBI) }, Legend: { name: null, verticalAlign: "center", horizontalAlign: "right", fontSize: 14,//in pixels fontFamily: "calibri", fontWeight: "normal", //normal, bold, bolder, lighter, fontColor: "black", fontStyle: "normal", // normal, italic, oblique cursor: null, itemmouseover: null, itemmouseout: null, itemmousemove: null, itemclick: null, dockInsidePlotArea: false, reversed: false, maxWidth: null, maxHeight: null, itemMaxWidth: null, itemWidth: null, itemWrap: true, itemTextFormatter: null }, ToolTip: { enabled: true, shared: false, animationEnabled: true, content: null, contentFormatter: null, reversed: false, backgroundColor: null, borderColor: null, borderThickness: 2, //in pixels cornerRadius: 5, // in pixels fontSize: 14, // in pixels fontColor: "#000000", fontFamily: "Calibri, Arial, Georgia, serif;", fontWeight: "normal", //normal, bold, bolder, lighter, fontStyle: "italic" // normal, italic, oblique }, Axis: { minimum: null, //Minimum value to be shown on the Axis maximum: null, //Minimum value to be shown on the Axis viewportMinimum: null, viewportMaximum: null, interval: null, // Interval for tick marks and grid lines intervalType: null, //number, millisecond, second, minute, hour, day, month, year //reversed: false, title: null, // string titleFontColor: "black", titleFontSize: 20, titleFontFamily: "arial", titleFontWeight: "normal", titleFontStyle: "normal", labelAngle: 0, labelFontFamily: "arial", labelFontColor: "black", labelFontSize: 12, labelFontWeight: "normal", labelFontStyle: "normal", labelAutoFit: false, labelWrap: true, labelMaxWidth: null,//null for auto labelFormatter: null, prefix: "", suffix: "", includeZero: true, //Applies only for axisY. Ignored in axisX. tickLength: 5, tickColor: "black", tickThickness: 1, lineColor: "black", lineThickness: 1, lineDashType: "solid", gridColor: "A0A0A0", gridThickness: 0, gridDashType: "solid", interlacedColor: null, valueFormatString: null, margin: 2, stripLines: [] // Just a placeholder. Does not have any effect on the actual number of striplines }, StripLine: { value: null, startValue: null, endValue: null, color: "orange", opacity: null, thickness: 2, lineDashType: "solid", label: "", labelBackgroundColor: "#EEEEEE", labelFontFamily: "arial", labelFontColor: "orange", labelFontSize: 12, labelFontWeight: "normal", labelFontStyle: "normal", labelFormatter: null, showOnTop: false }, DataSeries: { name: null, dataPoints: null, label: "", bevelEnabled: false, highlightEnabled: true, cursor: null, indexLabel: "", indexLabelPlacement: "auto", //inside, outside, auto indexLabelOrientation: "horizontal", indexLabelFontColor: "black", indexLabelFontSize: 12, indexLabelFontStyle: "normal", // italic ,oblique, normal indexLabelFontFamily: "Arial", // fx: Arial Verdana "Courier New" Serif indexLabelFontWeight: "normal", // bold ,bolder, lighter, normal indexLabelBackgroundColor: null, indexLabelLineColor: null, indexLabelLineThickness: 1, indexLabelLineDashType: "solid", indexLabelMaxWidth: null, indexLabelWrap: true, indexLabelFormatter: null, lineThickness: 2, lineDashType: "solid", color: null, risingColor: "white", fillOpacity: null, startAngle: 0, radius: null, innerRadius: null, type: "column", //line, column, bar, area, scatter stackedColumn, stackedBar, stackedArea, stackedColumn100, stackedBar100, stackedArea100, pie, doughnut xValueType: "number", //number, dateTime axisYType: "primary", xValueFormatString: null, yValueFormatString: null, zValueFormatString: null, percentFormatString: null, showInLegend: null, legendMarkerType: null, legendMarkerColor: null, legendText: null, legendMarkerBorderColor: null, legendMarkerBorderThickness: null, markerType: "circle", //none, circle, square, cross, triangle, line markerColor: null, markerSize: null, markerBorderColor: null, markerBorderThickness: null, //animationEnabled: true, mouseover: null, mouseout: null, mousemove: null, click: null, toolTipContent: null, visible: true }, //Private TextBlock: { x: 0, y: 0, width: null,//read only height: null,//read only maxWidth: null, maxHeight: null, padding: 0, angle: 0, text: "", horizontalAlign: "center",//left, center, right fontSize: 12,//in pixels fontFamily: "calibri", fontWeight: "normal", //normal, bold, bolder, lighter, fontColor: "black", fontStyle: "normal", // normal, italic, oblique borderThickness: 0, borderColor: "black", cornerRadius: 0, backgroundColor: null, textBaseline: "top" }, CultureInfo: { decimalSeparator: ".", digitGroupSeparator: ",", zoomText: "Zoom", panText: "Pan", resetText: "Reset", menuText: "More Options", saveJPGText: "Save as JPG", savePNGText: "Save as PNG", days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] } }; //#region Cultures var cultures = { "en": { //Derives from the default options }//, //"es": { // decimalSeparator: ",", // digitGroupSeparator: ".", // zoomText: "zoom", // panText: "pan", // resetText: "reset", // days: ["domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado"], //} }; //#endregion Cultures //#region Themes var colorSets = { "colorSet1": [ "#369EAD", "#C24642", "#7F6084", //"#96C412", "#86B402", "#A2D1CF", //"#D8C641", "#C8B631", "#6DBCEB", //"#4A4946", "#52514E", "#4F81BC", "#A064A1", "#F79647" ], "colorSet2": [ "#4F81BC", "#C0504E", "#9BBB58", "#23BFAA", //"#FAA586", "#8064A1", "#4AACC5", "#F79647", //"#77AA33", //"#7F6084" "#33558B" ], "colorSet3": [ "#8CA1BC", "#36845C", "#017E82", "#8CB9D0", "#708C98", "#94838D", "#F08891", "#0366A7", "#008276", "#EE7757", "#E5BA3A", "#F2990B", "#03557B", "#782970" ]//, //"colorSet4": [ // "#3698C5", // "#009B8D", // "#F1D691", // "#F8B90C", // "#0081B8", // "#5B5A96", // "#ACBDD1", // "#88A891", // "#39969D", // "#AECEDD", // "#A0B2BC", // "#BBAEB7", // "#A0C65F", // "#EEA6AA", // "#3798C5" //], //"colorSet5": [ // "#88ADBF", // "#84C336", // "#7B91C3", // "#4661EE", // "#EC5657", // "#1BCDD1", // "#8FAABB", // "#B08BEB", // "#3EA0DD", // "#F5A52A", // "#23BFAA", // "#FAA586", // "#EB8CC6" //] }; var themes = { "theme1": { Chart: { colorSet: "colorSet1" }, Title: { fontFamily: isCanvasSupported ? "Calibri, Optima, Candara, Verdana, Geneva, sans-serif" : "calibri", fontSize: 33, fontColor: "#3A3A3A", fontWeight: "bold", verticalAlign: "top", margin: 5 }, Subtitle: { fontFamily: isCanvasSupported ? "Calibri, Optima, Candara, Verdana, Geneva, sans-serif" : "calibri", fontSize: 16, fontColor: "#3A3A3A", fontWeight: "bold", verticalAlign: "top", margin: 5 }, Axis: { titleFontSize: 26, //titleFontColor: "rgb(98,98,98)", titleFontColor: "#666666", //titleFontFamily: "arial black", //titleFontFamily: "Verdana, Geneva, Calibri, sans-serif", titleFontFamily: isCanvasSupported ? "Calibri, Optima, Candara, Verdana, Geneva, sans-serif" : "calibri", //titleFontWeight: "bold", //labelFontFamily: "Times New Roman, Times, serif", labelFontFamily: isCanvasSupported ? "Calibri, Optima, Candara, Verdana, Geneva, sans-serif" : "calibri", //labelFontFamily: "Helvetica Neue, Helvetica", labelFontSize: 18, labelFontColor: "grey", //labelFontWeight: "bold", tickColor: "#BBBBBB", tickThickness: 2, gridThickness: 2, gridColor: "#BBBBBB", lineThickness: 2, lineColor: "#BBBBBB" }, Legend: { verticalAlign: "bottom", horizontalAlign: "center", fontFamily: isCanvasSupported ? "monospace, sans-serif,arial black" : "calibri" }, DataSeries: { //bevelEnabled: true, indexLabelFontColor: "grey", //indexLabelFontFamily: "Trebuchet MS, monospace, Courier New, Courier", indexLabelFontFamily: isCanvasSupported ? "Calibri, Optima, Candara, Verdana, Geneva, sans-serif" : "calibri", //indexLabelFontWeight: "bold", indexLabelFontSize: 18, //indexLabelLineColor: "lightgrey", indexLabelLineThickness: 1 } }, "theme2": { Chart: { colorSet: "colorSet2" }, Title: { fontFamily: "impact, charcoal, arial black, sans-serif", fontSize: 32,//fontColor: "rgb(58,58,58)", fontColor: "#333333", verticalAlign: "top", margin: 5 }, Subtitle: { fontFamily: "impact, charcoal, arial black, sans-serif", fontSize: 14,//fontColor: "rgb(58,58,58)", fontColor: "#333333", verticalAlign: "top", margin: 5 }, Axis: { titleFontSize: 22, titleFontColor: "rgb(98,98,98)", //titleFontFamily: "arial black", titleFontFamily: isCanvasSupported ? "monospace, sans-serif,arial black" : "arial", titleFontWeight: "bold", labelFontFamily: isCanvasSupported ? "monospace, Courier New, Courier" : "arial", //labelFontFamily: "Helvetica Neue, Helvetica", labelFontSize: 16, labelFontColor: "grey", labelFontWeight: "bold", tickColor: "grey", tickThickness: 2, gridThickness: 2, gridColor: "grey", lineColor: "grey", lineThickness: 0 }, Legend: { verticalAlign: "bottom", horizontalAlign: "center", fontFamily: isCanvasSupported ? "monospace, sans-serif,arial black" : "arial" }, DataSeries: { indexLabelFontColor: "grey", //indexLabelFontFamily: "Trebuchet MS, monospace, Courier New, Courier", indexLabelFontFamily: isCanvasSupported ? "Courier New, Courier, monospace" : "arial", indexLabelFontWeight: "bold", indexLabelFontSize: 18, //indexLabelLineColor: "lightgrey", indexLabelLineThickness: 1 } }, "theme3": { Chart: { colorSet: "colorSet1" }, Title: { fontFamily: isCanvasSupported ? "Candara, Optima, Trebuchet MS, Helvetica Neue, Helvetica, Trebuchet MS, serif" : "calibri", fontSize: 32, fontColor: "#3A3A3A", fontWeight: "bold", verticalAlign: "top", margin: 5 }, Subtitle: { fontFamily: isCanvasSupported ? "Candara, Optima, Trebuchet MS, Helvetica Neue, Helvetica, Trebuchet MS, serif" : "calibri", fontSize: 16, fontColor: "#3A3A3A", fontWeight: "bold", verticalAlign: "top", margin: 5 }, Axis: { titleFontSize: 22, titleFontColor: "rgb(98,98,98)", //titleFontFamily: "arial black", titleFontFamily: isCanvasSupported ? "Verdana, Geneva, Calibri, sans-serif" : "calibri", //titleFontWeight: "bold", //labelFontFamily: "Times New Roman, Times, serif", labelFontFamily: isCanvasSupported ? "Calibri, Optima, Candara, Verdana, Geneva, sans-serif" : "calibri", //labelFontFamily: "Helvetica Neue, Helvetica", labelFontSize: 18, labelFontColor: "grey", //labelFontWeight: "bold", tickColor: "grey", tickThickness: 2, gridThickness: 2, gridColor: "grey", lineThickness: 2, lineColor: "grey" }, Legend: { verticalAlign: "bottom", horizontalAlign: "center", fontFamily: isCanvasSupported ? "monospace, sans-serif,arial black" : "calibri" }, DataSeries: { bevelEnabled: true, indexLabelFontColor: "grey", //indexLabelFontFamily: "Trebuchet MS, monospace, Courier New, Courier", indexLabelFontFamily: isCanvasSupported ? "Candara, Optima, Calibri, Verdana, Geneva, sans-serif" : "calibri", //indexLabelFontWeight: "bold", indexLabelFontSize: 18, indexLabelLineColor: "lightgrey", indexLabelLineThickness: 2 } } }; //#endregion Themes var constants = { numberDuration: 1, yearDuration: 1000 * 60 * 60 * 24 * 364, monthDuration: 1000 * 60 * 60 * 24 * 30, weekDuration: 1000 * 60 * 60 * 24 * 7, dayDuration: 1000 * 60 * 60 * 24, hourDuration: 1000 * 60 * 60, minuteDuration: 1000 * 60, secondDuration: 1000, millisecondDuration: 1, dayOfWeekFromInt: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] }; //#region Static Methods & variables function extend(derived, base) { derived.prototype = inherit(base.prototype); derived.prototype.constructor = derived; derived.base = base.prototype; } function inherit(proto) { function F() { } F.prototype = proto; return new F(); } function addToDateTime(dateTime, num, type) { if (type === "millisecond") dateTime.setMilliseconds(dateTime.getMilliseconds() + 1 * num); else if (type === "second") dateTime.setSeconds(dateTime.getSeconds() + 1 * num); else if (type === "minute") dateTime.setMinutes(dateTime.getMinutes() + 1 * num); else if (type === "hour") dateTime.setHours(dateTime.getHours() + 1 * num); else if (type === "day") dateTime.setDate(dateTime.getDate() + 1 * num); else if (type === "week") dateTime.setDate(dateTime.getDate() + 7 * num); else if (type === "month") dateTime.setMonth(dateTime.getMonth() + 1 * num); else if (type === "year") dateTime.setFullYear(dateTime.getFullYear() + 1 * num); return dateTime; } function convertToNumber(num, type) { return constants[type + "Duration"] * num; } function pad(value, length) { var isNegative = false; if (value < 0) { isNegative = true; value *= -1; } value = "" + value; length = !length ? 1 : length; while (value.length < length) value = "0" + value; return isNegative ? "-" + value : value; } function trimString(str) { if (!str) return str; str = str.replace(/^\s\s*/, ''); var ws = /\s/; var i = str.length; while (ws.test(str.charAt(--i))) { } return str.slice(0, i + 1); } function extendCtx(context) { context.roundRect = function (x, y, width, height, radius, borderThickness, backgroundColor, borderColor) { /// ///Creates a rounded rectangle with given fill/stroke parameters ///x value ///y value ///Border Width ///Border Height ///Border CornerRadius ///Border Thickess ///Background Color ///Border Color /// if (backgroundColor) { this.fillStyle = backgroundColor; } if (borderColor) { this.strokeStyle = borderColor; } //if (typeof stroke == "undefined") { // stroke = true; //} if (typeof radius === "undefined") { radius = 5; } this.lineWidth = borderThickness; this.beginPath(); this.moveTo(x + radius, y); this.lineTo(x + width - radius, y); this.quadraticCurveTo(x + width, y, x + width, y + radius); this.lineTo(x + width, y + height - radius); this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); this.lineTo(x + radius, y + height); this.quadraticCurveTo(x, y + height, x, y + height - radius); this.lineTo(x, y + radius); this.quadraticCurveTo(x, y, x + radius, y); this.closePath(); if (backgroundColor) { this.fill(); } if (borderColor && borderThickness > 0) { this.stroke(); } }; } function compareNumbers(a, b) { return a - b; } function compareDataPointX(dataPoint1, dataPoint2) { return dataPoint1.x - dataPoint2.x; } function intToHexColorString(num) { var r = ((num & 0xFF0000) >> 16).toString(16); var g = ((num & 0x00FF00) >> 8).toString(16); var b = ((num & 0x0000FF) >> 0).toString(16); r = r.length < 2 ? "0" + r : r; g = g.length < 2 ? "0" + g : g; b = b.length < 2 ? "0" + b : b; return "#" + r + g + b; } function RGBToInt(r, g, b) { var num = (r << 16) | (g << 8) | (b); return num; } function intToRGB(num) { var rgb = []; var r = ((num & 0xFF0000) >> 16); var g = ((num & 0x00FF00) >> 8); var b = ((num & 0x0000FF) >> 0); //r = r.length < 2 ? "0" + r : r; //g = g.length < 2 ? "0" + g : g; //b = b.length < 2 ? "0" + b : b; rgb[0] = r; rgb[1] = g; rgb[2] = b; return rgb; } function arrayIndexOf(elt /*, from*/) { var len = this.length >>> 0; var from = Number(arguments[1]) || 0; from = (from < 0) ? Math.ceil(from) : Math.floor(from); if (from < 0) from += len; for (; from < len; from++) { if (from in this && this[from] === elt) return from; } return -1; }; //IE8- Fix: indexOf is not supported in IE8- for arrays function addArrayIndexOf(obj) { if (!obj.indexOf) { obj.indexOf = arrayIndexOf; } return obj; } var fontHeightInPixels = {}; var textMeasureEl = null; function getFontHeightInPixels(fontFamily, fontSize, fontWeight) { //return fontSize; fontWeight = fontWeight || "normal"; var entry = fontFamily + "_" + fontSize + "_" + fontWeight; var height = fontHeightInPixels[entry]; if (isNaN(height)) { try { var style = "position:absolute; left:0px; top:-20000px; padding:0px;margin:0px;border:none;white-space:pre;line-height:normal;" + "font-family:" + fontFamily + "; " + "font-size:" + fontSize + "px; font-weight:" + fontWeight + ";"; //console.log(style); if (!textMeasureEl) { var body = document.body; textMeasureEl = document.createElement("span"); textMeasureEl.innerHTML = ""; var textNode = document.createTextNode("Mpgyi"); textMeasureEl.appendChild(textNode); body.appendChild(textMeasureEl); } textMeasureEl.style.display = ""; textMeasureEl.setAttribute("style", style); height = Math.round(textMeasureEl.offsetHeight); textMeasureEl.style.display = "none"; //body.removeChild(tempDiv); //if (window.console) // window.console.log(fontSize + ": " + height); } catch (e) { height = Math.ceil(fontSize * 1.1); } height = Math.max(height, fontSize); fontHeightInPixels[entry] = height; } return height; } function getLineDashArray(lineDashType, lineThickness) { var lineDashArray = []; lineDashType = lineDashType || "solid"; lineDashTypeMap = { "solid": [], "shortDash": [3, 1], "shortDot": [1, 1], "shortDashDot": [3, 1, 1, 1], "shortDashDotDot": [3, 1, 1, 1, 1, 1], "dot": [1, 2], "dash": [4, 2], "dashDot": [4, 2, 1, 2], "longDash": [8, 2], "longDashDot": [8, 2, 1, 2], "longDashDotDot": [8, 2, 1, 2, 1, 2] }; lineDashArray = lineDashTypeMap[lineDashType]; if (lineDashArray) { for (var i = 0; i < lineDashArray.length; i++) { lineDashArray[i] *= lineThickness; } } else lineDashArray = []; return lineDashArray; } //userCapture is optional. Defaults to false function addEvent(obj, eventType, fn, useCapture) { if (obj.addEventListener) { obj.addEventListener(eventType, fn, useCapture || false); } else if (obj.attachEvent) { obj.attachEvent("on" + eventType, function (e) { e = e || window.event; e.preventDefault = e.preventDefault || function () { e.returnValue = false; }; e.stopPropagation = e.stopPropagation || function () { e.cancelBubble = true; }; fn.call(obj, e); }); } else return false; } //#region formatting functions/methods var dateFormat = function () { var reg = /D{1,4}|M{1,4}|Y{1,4}|h{1,2}|H{1,2}|m{1,2}|s{1,2}|f{1,3}|t{1,2}|T{1,2}|K|z{1,3}|"[^"]*"|'[^']*'/g; var defDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; var defShortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; var defMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; var defShortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; var timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g; var timezoneClip = /[^-+\dA-Z]/g; return function (dt, formatString, cultureInfo) { var days = cultureInfo ? cultureInfo.days : defDays; var months = cultureInfo ? cultureInfo.months : defMonths; var shortDays = cultureInfo ? cultureInfo.shortDays : defShortDays; var shortMonths = cultureInfo ? cultureInfo.shortMonths : defShortMonths; var result = ""; var utc = false; dt = dt && dt.getTime ? dt : dt ? new Date(dt) : new Date; if (isNaN(dt)) throw SyntaxError("invalid date"); if (formatString.slice(0, 4) === "UTC:") { formatString = formatString.slice(4); utc = true; } var pre = utc ? "getUTC" : "get"; var date = dt[pre + "Date"](); var day = dt[pre + "Day"](); var month = dt[pre + "Month"](); var year = dt[pre + "FullYear"](); var hours = dt[pre + "Hours"](); var minutes = dt[pre + "Minutes"](); var seconds = dt[pre + "Seconds"](); var milliseconds = dt[pre + "Milliseconds"](); var offset = utc ? 0 : dt.getTimezoneOffset(); result = formatString.replace(reg, function (key) { switch (key) { case "D": return date; case "DD": return pad(date, 2); case "DDD": return shortDays[day]; case "DDDD": return days[day]; case "M": return month + 1; case "MM": return pad(month + 1, 2); case "MMM": return shortMonths[month]; case "MMMM": return months[month]; case "Y": return parseInt(String(year).slice(-2)); case "YY": return pad(String(year).slice(-2), 2); case "YYY": return pad(String(year).slice(-3), 3); case "YYYY": return pad(year, 4); case "h": return hours % 12 || 12; case "hh": return pad(hours % 12 || 12, 2); case "H": return hours; case "HH": return pad(hours, 2); case "m": return minutes; case "mm": return pad(minutes, 2); case "s": return seconds; case "ss": return pad(seconds, 2); case "f": return String(milliseconds).slice(0, 1); case "ff": return pad(String(milliseconds).slice(0, 2), 2); case "fff": return pad(String(milliseconds).slice(0, 3), 3); case "t": return hours < 12 ? "a" : "p"; case "tt": return hours < 12 ? "am" : "pm"; case "T": return hours < 12 ? "A" : "P"; case "TT": return hours < 12 ? "AM" : "PM"; case "K": return utc ? "UTC" : (String(dt).match(timezone) || [""]).pop().replace(timezoneClip, ""); // Time Zone; case "z": return (offset > 0 ? "-" : "+") + Math.floor(Math.abs(offset) / 60); // Hour Offset from UTC without padding case "zz": return (offset > 0 ? "-" : "+") + pad(Math.floor(Math.abs(offset) / 60), 2); // Hour Offset from UTC with padding case "zzz": return (offset > 0 ? "-" : "+") + pad(Math.floor(Math.abs(offset) / 60), 2) + pad(Math.abs(offset) % 60, 2); // Hour and Minute Offset from UTC with padding default: return key.slice(1, key.length - 1); } }); return result; }; }(); var numberFormat = function (v, fs, cultureInfo) { if (v === null) return ""; v = Number(v); var isNegative = v < 0 ? true : false; if (isNegative) v *= -1; var decimalSeparator = cultureInfo ? cultureInfo.decimalSeparator : "."; var digitGroupSeparator = cultureInfo ? cultureInfo.digitGroupSeparator : ","; var vString = ""; fs = String(fs); var multiplier = 1; var temp; var result = ""; var matches = ""; var decimalPosition = -1; var fsBeforeDecimal = []; var fsAfterDecimal = []; var noPhBeforeDecimal = 0; // Number of Placeholders before Decimal var noPhAfterDecimal = 0; // Number of Placeholders after Decimal var noComma = 0; var isScientificNotation = false; var exponent = 0; matches = fs.match(/"[^"]*"|'[^']*'|[eE][+-]*[0]+|[,]+[.]|‰|./g); //window.console.log(matches + " = " + matches.length); var match = null; for (var i = 0; matches && i < matches.length; i++) { match = matches[i]; if (match === "." && decimalPosition < 0) { decimalPosition = i; continue; } else if (match === "%") { multiplier *= 100; } else if (match === "‰") { multiplier *= 1000; continue; } else if (match[0] === "," && match[match.length - 1] === ".") { multiplier /= Math.pow(1000, match.length - 1); decimalPosition = i + match.length - 1; continue; } else if ((match[0] === "E" || match[0] === "e") && match[match.length - 1] === "0") { isScientificNotation = true; } if (decimalPosition < 0) { fsBeforeDecimal.push(match); if (match === "#" || match === "0") noPhBeforeDecimal++; else if (match === ",") noComma++; } else { fsAfterDecimal.push(match); if (match === "#" || match === "0") noPhAfterDecimal++; } } if (isScientificNotation) { var integer = Math.floor(v); exponent = (integer === 0 ? "" : String(integer)).length - noPhBeforeDecimal; multiplier /= Math.pow(10, exponent); } v *= multiplier; if (decimalPosition < 0) decimalPosition = i; vString = v.toFixed(noPhAfterDecimal); var split = vString.split("."); //window.console.log(split); var vStringBeforeDecimal = (split[0] + "").split(""); var vStringAfterDecimal = (split[1] + "").split(""); if (vStringBeforeDecimal && vStringBeforeDecimal[0] === "0") vStringBeforeDecimal.shift(); //window.console.log(fsBeforeDecimal + "<---------->" + fsAfterDecimal + " & " + vStringBeforeDecimal + "<---------->" + vStringAfterDecimal); var noPhProcessed = 0; var noDigitsAdded = 0; var noCommaAdded = 0; var commaDistance = 0; var distanceFromLastComma = 0; while (fsBeforeDecimal.length > 0) { match = fsBeforeDecimal.pop(); if (match === "#" || match === "0") { noPhProcessed++; if (noPhProcessed === noPhBeforeDecimal) { var digits = vStringBeforeDecimal; vStringBeforeDecimal = []; if (match === "0") { //var totalDigits = result.match(/[0-9]/g).length; var toPad = noPhBeforeDecimal - noDigitsAdded - (digits ? digits.length : 0); while (toPad > 0) { digits.unshift("0"); toPad--; } } while (digits.length > 0) { result = digits.pop() + result; distanceFromLastComma++; if (distanceFromLastComma % commaDistance === 0 && noCommaAdded === noComma && digits.length > 0) result = digitGroupSeparator + result; } if (isNegative) result = "-" + result; } else { if (vStringBeforeDecimal.length > 0) { result = vStringBeforeDecimal.pop() + result; noDigitsAdded++; distanceFromLastComma++; } else if (match === "0") { result = "0" + result; noDigitsAdded++; distanceFromLastComma++; } if (distanceFromLastComma % commaDistance === 0 && noCommaAdded === noComma && vStringBeforeDecimal.length > 0) result = digitGroupSeparator + result; } } else if ((match[0] === "E" || match[0] === "e") && match[match.length - 1] === "0" && /[eE][+-]*[0]+/.test(match)) { if (exponent < 0) match = match.replace("+", "").replace("-", ""); else match = match.replace("-", ""); result += match.replace(/[0]+/, function ($0) { return pad(exponent, $0.length); }); } else { if (match === ",") { noCommaAdded++; commaDistance = distanceFromLastComma; distanceFromLastComma = 0; if (vStringBeforeDecimal.length > 0) result = digitGroupSeparator + result; } else if (match.length > 1 && ((match[0] === "\"" && match[match.length - 1] === "\"") || (match[0] === "'" && match[match.length - 1] === "'"))) { result = match.slice(1, match.length - 1) + result; } else result = match + result; } } var charCount = 0; var resultAfterDecimal = ""; var addDecimalSeparator = false; while (fsAfterDecimal.length > 0) { match = fsAfterDecimal.shift(); if (match === "#" || match === "0") { if (vStringAfterDecimal.length > 0 && Number(vStringAfterDecimal.join("")) !== 0) { resultAfterDecimal += vStringAfterDecimal.shift(); addDecimalSeparator = true; } else if (match === "0") { resultAfterDecimal += "0"; addDecimalSeparator = true; } } else if (match.length > 1 && ((match[0] === "\"" && match[match.length - 1] === "\"") || (match[0] === "'" && match[match.length - 1] === "'"))) { resultAfterDecimal += match.slice(1, match.length - 1); //addDecimalSeparator = true; } else if ((match[0] === "E" || match[0] === "e") && match[match.length - 1] === "0" && /[eE][+-]*[0]+/.test(match)) { if (exponent < 0) match = match.replace("+", "").replace("-", ""); else match = match.replace("-", ""); resultAfterDecimal += match.replace(/[0]+/, function ($0) { return pad(exponent, $0.length); }); } else { resultAfterDecimal += match; //addDecimalSeparator = true; } } result += (addDecimalSeparator ? decimalSeparator : "") + resultAfterDecimal; //window.console.log(result); return result; }; //#endregion formatting functions/methods function getObjectId(x, y, ctx) { x *= devicePixelBackingStoreRatio; y *= devicePixelBackingStoreRatio; var pixels = ctx.getImageData(x, y, 2, 2).data; var isObject = true; for (var i = 0; i < 4; i++) { if (pixels[i] !== pixels[i + 4] | pixels[i] !== pixels[i + 8] | pixels[i] !== pixels[i + 12]) { isObject = false; break; } } if (isObject) { return RGBToInt(pixels[0], pixels[1], pixels[2]); } else { return 0; } //window.console.log(pixels); } //extracts mouse coordinates from the event parameters var getMouseCoordinates = function (ev) { var x = 0; var y = 0; ev = ev || window.event; if (ev.offsetX || ev.offsetX === 0) { x = ev.offsetX; y = ev.offsetY; } else if (ev.layerX || ev.layerX == 0) { // Firefox x = ev.layerX; y = ev.layerY; } else { x = ev.pageX - ev.target.offsetLeft; y = ev.pageY - ev.target.offsetTop; } return { x: x, y: y }; }; function getFontString(prefix, object, fallbackObject) { var fontString = ""; var fontStyleString = prefix ? prefix + "FontStyle" : "fontStyle"; var fontWeightString = prefix ? prefix + "FontWeight" : "fontWeight"; var fontSizeString = prefix ? prefix + "FontSize" : "fontSize"; var fontFamilyString = prefix ? prefix + "FontFamily" : "fontFamily"; fontString += object[fontStyleString] ? object[fontStyleString] + " " : (fallbackObject && fallbackObject[fontStyleString]) ? (fallbackObject[fontStyleString] + " ") : ""; fontString += object[fontWeightString] ? object[fontWeightString] + " " : (fallbackObject && fallbackObject[fontWeightString]) ? (fallbackObject[fontWeightString] + " ") : ""; fontString += object[fontSizeString] ? object[fontSizeString] + "px " : (fallbackObject && fallbackObject[fontSizeString]) ? (fallbackObject[fontSizeString] + "px ") : ""; var fontFamily = object[fontFamilyString] ? object[fontFamilyString] + "" : (fallbackObject && fallbackObject[fontFamilyString]) ? (fallbackObject[fontFamilyString] + "") : ""; if (!isCanvasSupported && fontFamily) { var firstFontFamily = fontFamily.split(",")[0]; if (firstFontFamily[0] !== "'" && firstFontFamily[0] !== "\"") firstFontFamily = "'" + firstFontFamily + "'"; fontString += firstFontFamily; } else fontString += fontFamily; return fontString; } function getProperty(propertyName, object, fallbackObject) { var value = propertyName in object ? object[propertyName] : fallbackObject[propertyName]; return value; } var optimizeForHiDPI = true; //optimizeForHiDPI = false; var devicePixelRatio = window.devicePixelRatio || 1; var backingStoreRatio = 1; var devicePixelBackingStoreRatio = optimizeForHiDPI ? devicePixelRatio / backingStoreRatio : 1; function setCanvasSize(canvas, width, height) { if (isCanvasSupported && !!optimizeForHiDPI) { var ctx = canvas.getContext("2d"); backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; devicePixelBackingStoreRatio = devicePixelRatio / backingStoreRatio; canvas.width = width * devicePixelBackingStoreRatio; canvas.height = height * devicePixelBackingStoreRatio; if (devicePixelRatio !== backingStoreRatio) { canvas.style.width = width + 'px'; canvas.style.height = height + 'px'; ctx.scale(devicePixelBackingStoreRatio, devicePixelBackingStoreRatio); } //window.alert(backingStoreRatio); //window.alert(devicePixelRatio); } else { canvas.width = width; canvas.height = height; } } function createCanvas(width, height) { var canvas = document.createElement("canvas"); canvas.setAttribute("class", "canvasjs-chart-canvas"); setCanvasSize(canvas, width, height); if (!isCanvasSupported && typeof (G_vmlCanvasManager) !== "undefined") { G_vmlCanvasManager.initElement(canvas); } return canvas; } function exportCanvas(canvas, format, fileName) { if (!canvas || !format || !fileName) return; var fullFileName = fileName + "." + (format === "jpeg" ? "jpg" : format); var mimeType = "image/" + format; var img = canvas.toDataURL(mimeType); var saved = false; var downloadLink = document.createElement("a"); downloadLink.download = fullFileName; downloadLink.href = img; downloadLink.target = "_blank"; var e; if (typeof (Blob) !== "undefined" && !!new Blob()) { //alert("blob"); var imgData = img.replace(/^data:[a-z/]*;base64,/, ''); var byteString = atob(imgData); var buffer = new ArrayBuffer(byteString.length); var intArray = new Uint8Array(buffer); for (var i = 0; i < byteString.length; i++) { intArray[i] = byteString.charCodeAt(i); } var blob = new Blob([buffer], { type: "image/" + format }); // Save the blob try { window.navigator.msSaveBlob(blob, fullFileName); saved = true; } catch (e) { downloadLink.dataset.downloadurl = [mimeType, downloadLink.download, downloadLink.href].join(':'); downloadLink.href = window.URL.createObjectURL(blob); } } if (!saved) { try { event = document.createEvent("MouseEvents"); event.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); if (downloadLink.dispatchEvent) { //alert("dispatchEvent"); downloadLink.dispatchEvent(event); } else if (downloadLink.fireEvent) { //alert("fireEvent"); downloadLink.fireEvent("onclick"); } } catch (e) { var win = window.open(); //alert("
Please right click on the image and save it to your device
"); win.document.close(); } } } var base64Images = { reset: { image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAcCAYAAAAAwr0iAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAKRSURBVEiJrdY/iF1FFMfxzwnZrGISUSR/JLGIhoh/QiRNBLWxMLIWEkwbgiAoFgoW2mhlY6dgpY2IlRBRxBSKhSAKIklWJRYuMZKAhiyopAiaTY7FvRtmZ+/ed9/zHRjezLw5v/O9d86cuZGZpmURAfdn5o9DfdZNLXpjz+LziPgyIl6MiG0jPTJzZBuyDrP4BVm0P/AKbljTb4ToY/gGewYA7KyCl+1b3DUYANvwbiHw0gCAGRzBOzjTAXEOu0cC4Ch+r5x/HrpdrcZmvIDFSucMtnYCYC++6HmNDw8FKDT34ETrf639/azOr5vwRk/g5fbeuABtgC04XWk9VQLciMP4EH/3AFzErRNC7MXlQmsesSoHsGPE23hmEoBW+61K66HMXFmIMvN8myilXS36R01ub+KfYvw43ZXwYDX+AHP4BAci4pFJomfmr/ihmNofESsBImJGk7mlncrM45n5JPbhz0kAWpsv+juxaX21YIPmVJS2uNzJMS6ZNexC0d+I7fUWXLFyz2kSZlpWPvASlmqAf/FXNXf3FAF2F/1LuFifAlionB6dRuSI2IwHi6lzmXmp6xR8XY0fiIh7psAwh+3FuDkRHQVjl+a8lkXjo0kLUKH7XaV5oO86PmZ1FTzyP4K/XGl9v/zwfbW7BriiuETGCP5ch9bc9f97HF/vcFzCa5gdEPgWq+t/4v0V63oE1uF4h0DiFJ7HnSWMppDdh1dxtsPvJ2wcBNAKbsJXa0Ck5opdaBPsRNu/usba09i1KsaAVzmLt3sghrRjuK1Tf4xkegInxwy8gKf7dKMVH2QRsV5zXR/Cftyu+aKaKbbkQrsdH+PTzLzcqzkOQAVzM+7FHdiqqe2/YT4zF/t8S/sPmawyvC974vcAAAAASUVORK5CYII=" }, pan: { image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAJVSURBVFiFvZe7a1RBGMV/x2hWI4JpfKCIiSBKOoOCkID/wP4BFqIIFkE02ChIiC8QDKlSiI3YqRBsBVGwUNAUdiIEUgjiAzQIIsuKJsfizsXr5t7d+8jmwLDfzHz3nLOzc7+ZxTZlGyDgZiWOCuJ9wH2gCUyuqQFgF/AGcKJNrYkBYBj40CIet+muGQi/96kM4WS7C/Tm5VUg7whJg8BkEGkCR4BDYfodsADUgP6wErO5iCtswsuJb32hdbXy8qzL5TIdmzJinHdZoZIBZcSFkGlAKs1Z3YCketZcBtouuaQNkrblMiBpBrhme7mAgU4wMCvpcFsDkq4C54DFVRTH9h+i6vlE0r5UA5ImgCuh28jB28iIs7BIVCOeStoZD64P4uPAjUTygKSx2FsK2TIwkugfk9Qkfd/E+yMWHQCeSRqx/R3gOp3LazfaS2C4B5gHDgD7U9x3E3uAH7KNpC3AHHAwTL4FHgM9GQ8vAaPA0dB/Abxqk2/gBLA9MXba9r1k/d4LfA3JtwueBeM58ucS+edXnAW23wP10N3advEi9CXizTnyN4bPS7Zn4sH/dq3t18AY4e1YLYSy3g/csj2VnFshZPuOpOeSKHCodUINuGj7YetE6je1PV9QoNPJ9StNHKodx7nRbiWrGHBGXAi5DUiqtQwtpcWK0Jubt8CltA5MEV1IfwO7+VffPwGfia5m34CT4bXujIIX0Qna1/cGMNqV/wUJE2czxD8CQ4X5Sl7Jz7SILwCDpbjKPBRMHAd+EtX4HWV5Spdc2w8kDQGPbH8py/MXMygM69/FKz4AAAAASUVORK5CYII=" }, zoom: { image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAMqSURBVFiFvdfbj91TFMDxz57U6GUEMS1aYzyMtCSSDhWjCZMInpAI3khE/QHtgzdRkXgSCS8SES9epKLi0oRKNETjRahREq2KS1stdRujtDPtbA97n5zdn9+5zJxTK9k5v3POXmt991p7r71+IcaoGwkhTOIebMRqzOBTvIG3Y4zTXRmqSoyx5cAKbMJOHMFJnMZ8/jyFaXyMR7G6nb1aH22cP4BvcBxziG3GKfyTIR9D6BYg1KUghPBCDveFlb/24Av8iuUYw41YVsz5G7uxKcZ4aMEpwGt5NY3V/YbHsQ6rcAHOw/kYxigewr5CZw4fYGxBKcCLOFEYehXrMdRhr5yLETxVScsOLOkKAPfn1TYMPIvLFrShUlS2FDZm8XRHACzFAWl3R2xbqPMCYhmeLCAOYEMngAczbcTvuHYxzguIy/FesR9e6gSwU/OoPYHBHgHgviIKX2Flq7k34KhmcVnbi/PC8JX4MgMcxb118wZwdz5aISscqx7VRcox7MrPQ7i+btIAJrAkf9+bI9EPmZY2IAxiTSuAldLq4Y9+AcSUh78KP0tbAcwU35cXMD1JCIFUoGiehlqAz6TNB1f1C0DK+0h+nsNPrQC2a4bqGmlD9kOGcWt+Po6pVgDvSxfJaSkFd4UQBvoAsBYbCoB3a2flM7slA0R8iyt6rAFDeDPbm8eOTpVwGD9qVq7nLbIaZnmksPU1JtsCZMXNmpdRxFasWITzh6Xj3LCzra1OxcD2QjHiGVzdpfORnMqZio2PcF23ABdJF1Np4BPptlyPi6WzPYBzpJZtHe7A6xW9cnyP8TqA//SEIYRL8Bxul7rihvwgtVn78WcGGZXa9HGd5TDujDHuOePXNiHdKjWgZX/YbsxLx/ktqbjVzTlcjUSnvI5JrdlUVp6WesZZ6R1hRrpq9+EVTGS9jTjYAuKIouGpbcurEkIYxC051KNSamazsc+xK8b4S0VnEi/j0hqTP+M27O258egQwZuzs7pI7Mf4WQXIEDc5s9sux+5+1Py2EmP8UOq6GvWhIScxfdYjUERiAt9Jd84J6a16zf8JEKT3yCm8g1UxRv8CC4pyRhzR1uUAAAAASUVORK5CYII=" }, menu: { image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgCAYAAAAbifjMAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAAWdEVYdENyZWF0aW9uIFRpbWUAMDcvMTUvMTTPsvU0AAAAP0lEQVRIie2SMQoAIBDDUvH/X667g8sJJ9KOhYYOkW0qGaU1MPdC0vGSbV19EACo3YMPAFH5BUBUjsqfAPpVXtNgGDfxEDCtAAAAAElFTkSuQmCC" } } function setButtonState(chart, button, state) { if (button.getAttribute("state") !== state) { button.setAttribute("state", state); button.setAttribute("type", 'button'); button.style.position = "relative"; button.style.margin = "0px 0px 0px 0px"; button.style.padding = "3px 4px 0px 4px"; button.style.cssFloat = "left"; button.setAttribute("title", chart._cultureInfo[state + "Text"]); button.innerHTML = "" + chart._cultureInfo[state + "Text"] + ""; } } function show() { var element = null; for (var i = 0; i < arguments.length; i++) { element = arguments[i]; if (element.style) element.style.display = "inline"; } } function hide() { var element = null; for (var i = 0; i < arguments.length; i++) { element = arguments[i]; if (element && element.style) element.style.display = "none"; } } //#endregion Static Methods & variables //#region Class Definitions //#region Class CanvasJSObject function CanvasJSObject(defaultsKey, options, theme, parent) { this._defaultsKey = defaultsKey; this.parent = parent; this._eventListeners = [];//Multidimentional array with an array for each event type var currentThemeOptions = {}; if (theme && themes[theme] && themes[theme][defaultsKey]) currentThemeOptions = themes[theme][defaultsKey]; this._options = options ? options : {}; this.setOptions(this._options, currentThemeOptions); } CanvasJSObject.prototype.setOptions = function (options, currentThemeOptions) { if (!defaultOptions[this._defaultsKey]) { if (isDebugMode && window.console) console.log("defaults not set"); } else { var defaults = defaultOptions[this._defaultsKey]; for (var prop in defaults) { if (defaults.hasOwnProperty(prop)) { if (options && prop in options) this[prop] = options[prop]; else if (currentThemeOptions && prop in currentThemeOptions) this[prop] = currentThemeOptions[prop]; else this[prop] = defaults[prop]; //if (typeof this[prop] === "function") { // alert("function"); // this[prop] = this[prop](); //} } } } }; // Update options. Returns true if changed or else false CanvasJSObject.prototype.updateOption = function (prop) { if (!defaultOptions[this._defaultsKey] && isDebugMode && window.console) console.log("defaults not set"); var defaults = defaultOptions[this._defaultsKey]; var theme = this._options.theme ? this._options.theme : (this.chart && this.chart._options.theme) ? this.chart._options.theme : "theme1"; var currentThemeOptions = {}; var newValue = this[prop]; if (theme && themes[theme] && themes[theme][this._defaultsKey]) currentThemeOptions = themes[theme][this._defaultsKey]; if (prop in defaults) { if (prop in this._options) newValue = this._options[prop]; else if (currentThemeOptions && prop in currentThemeOptions) newValue = currentThemeOptions[prop]; else newValue = defaults[prop]; } if (newValue === this[prop]) return false; this[prop] = newValue; return true; } //Stores values in _oldOptions so that it can be tracked for any changes CanvasJSObject.prototype.trackChanges = function (option) { if (!this.sessionVariables) throw "Session Variable Store not set"; this.sessionVariables[option] = this._options[option]; }; CanvasJSObject.prototype.isBeingTracked = function (option) { if (!this._options._oldOptions) this._options._oldOptions = {}; if (this._options._oldOptions[option]) return true; else return false; }; CanvasJSObject.prototype.hasOptionChanged = function (option) { if (!this.sessionVariables) throw "Session Variable Store not set"; var hasChanged = !(this.sessionVariables[option] === this._options[option]); return hasChanged; }; CanvasJSObject.prototype.addEventListener = function (eventName, eventHandler, context) { if (!eventName || !eventHandler) return; context = context || this; this._eventListeners[eventName] = this._eventListeners[eventName] || []; this._eventListeners[eventName].push({ context: context, eventHandler: eventHandler }); } CanvasJSObject.prototype.removeEventListener = function (eventName, eventHandler) { if (!eventName || !eventHandler || !this._eventListeners[eventName]) return; var listeners = this._eventListeners[eventName]; for (var i = 0; i < listeners.length; i++) { if (listeners[i].eventHandler === eventHandler) { listeners[i].splice(i, 1); break; } } } CanvasJSObject.prototype.removeAllEventListeners = function () { this._eventListeners = []; } CanvasJSObject.prototype.dispatchEvent = function (eventName, eventParameter, context) { //For Internal Events if (eventName && this._eventListeners[eventName]) { eventParameter = eventParameter || {}; var listeners = this._eventListeners[eventName]; for (var i = 0; i < listeners.length; i++) { listeners[i].eventHandler.call(listeners[i].context, eventParameter); } } //External Events do not require registering as the property name is suffient to fire the event. if (typeof (this[eventName]) === "function") { this[eventName].call(context || this.chart._publicChartReference, eventParameter); } } //#endregion Class CanvasJSObject //#region Class Chart function Chart(containerId, options, publicChartReference) { this._publicChartReference = publicChartReference; options = options || {}; Chart.base.constructor.call(this, "Chart", options, options.theme ? options.theme : "theme1"); var _this = this; this._containerId = containerId; this._objectsInitialized = false; this.ctx = null; this.overlaidCanvasCtx = null; this._indexLabels = []; this._panTimerId = 0; this._lastTouchEventType = ""; this._lastTouchData = null; this.isAnimating = false; this.renderCount = 0; this.animatedRender = false; this.disableToolTip = false; this.panEnabled = false; this._defaultCursor = "default"; this.plotArea = { canvas: null, ctx: null, x1: 0, y1: 0, x2: 0, y2: 0, width: 0, height: 0 }; this._dataInRenderedOrder = []; this._container = typeof (this._containerId) === "string" ? document.getElementById(this._containerId) : this._containerId; if (!this._container) { if (window.console) window.console.log("CanvasJS Error: Chart Container with id \"" + this._containerId + "\" was not found"); return; } this._container.innerHTML = ""; var width = 0; var height = 0; if (this._options.width) width = this.width; else width = this._container.clientWidth > 0 ? this._container.clientWidth : this.width; if (this._options.height) height = this.height; else height = this._container.clientHeight > 0 ? this._container.clientHeight : this.height; this.width = width; this.height = height; this.x1 = this.y1 = 0; this.x2 = this.width; this.y2 = this.height; this._selectedColorSet = typeof (colorSets[this.colorSet]) !== "undefined" ? colorSets[this.colorSet] : colorSets["colorSet1"]; this._canvasJSContainer = document.createElement("div"); this._canvasJSContainer.setAttribute("class", "canvasjs-chart-container"); this._canvasJSContainer.style.position = "relative"; this._canvasJSContainer.style.textAlign = "left"; this._canvasJSContainer.style.cursor = "auto"; if (!isCanvasSupported) { this._canvasJSContainer.style.height = "0px";//In IE6 toolTip doesn't show at proper position if not set. } this._container.appendChild(this._canvasJSContainer); this.canvas = createCanvas(width, height); this.canvas.style.position = "absolute"; if (this.canvas.getContext) { //try { // this.canvas.style.background = this.backgroundColor; //} catch (e) { } this._canvasJSContainer.appendChild(this.canvas); this.ctx = this.canvas.getContext("2d"); this.ctx.textBaseline = "top"; extendCtx(this.ctx); } else return; //this.canvas.style.cursor = "pointer"; if (!isCanvasSupported) { this.plotArea.canvas = createCanvas(width, height); this.plotArea.canvas.style.position = "absolute"; this.plotArea.canvas.setAttribute("class", "plotAreaCanvas"); this._canvasJSContainer.appendChild(this.plotArea.canvas); this.plotArea.ctx = this.plotArea.canvas.getContext("2d"); } else { this.plotArea.ctx = this.ctx; } this.overlaidCanvas = createCanvas(width, height); this.overlaidCanvas.style.position = "absolute"; this._canvasJSContainer.appendChild(this.overlaidCanvas); this.overlaidCanvasCtx = this.overlaidCanvas.getContext("2d"); this.overlaidCanvasCtx.textBaseline = "top"; this._eventManager = new EventManager(this); addEvent(window, "resize", function () { //this._container.addEventListener("DOMSubtreeModified", function () { if (_this._updateSize()) _this.render(); }); this._toolBar = document.createElement("div"); this._toolBar.setAttribute("class", "canvasjs-chart-toolbar"); this._toolBar.style.cssText = "position: absolute; right: 1px; top: 1px;"; this._canvasJSContainer.appendChild(this._toolBar); this.bounds = { x1: 0, y1: 0, x2: this.width, y2: this.height }; addEvent(this.overlaidCanvas, 'click', function (e) { _this._mouseEventHandler(e); }); addEvent(this.overlaidCanvas, 'mousemove', function (e) { _this._mouseEventHandler(e); }); addEvent(this.overlaidCanvas, 'mouseup', function (e) { _this._mouseEventHandler(e); }); addEvent(this.overlaidCanvas, 'mousedown', function (e) { _this._mouseEventHandler(e); hide(_this._dropdownMenu); }); addEvent(this.overlaidCanvas, 'mouseout', function (e) { _this._mouseEventHandler(e); }); addEvent(this.overlaidCanvas, window.navigator.msPointerEnabled ? "MSPointerDown" : "touchstart", function (e) { _this._touchEventHandler(e); }); addEvent(this.overlaidCanvas, window.navigator.msPointerEnabled ? "MSPointerMove" : 'touchmove', function (e) { _this._touchEventHandler(e); }); addEvent(this.overlaidCanvas, window.navigator.msPointerEnabled ? "MSPointerUp" : 'touchend', function (e) { _this._touchEventHandler(e); }); addEvent(this.overlaidCanvas, window.navigator.msPointerEnabled ? "MSPointerCancel" : 'touchcancel', function (e) { _this._touchEventHandler(e); }); if (!this._creditLink) { this._creditLink = document.createElement("a"); this._creditLink.setAttribute("class", "canvasjs-chart-credit"); this._creditLink.setAttribute("style", "outline:none;margin:0px;position:absolute;right:3px;top:" + (this.height - 14) + "px;color:dimgrey;text-decoration:none;font-size:10px;font-family:Lucida Grande, Lucida Sans Unicode, Arial, sans-serif"); this._creditLink.setAttribute("tabIndex", -1); this._creditLink.setAttribute("target", "_blank"); } this._toolTip = new ToolTip(this, this._options.toolTip, this.theme); this.data = null; this.axisX = null; this.axisY = null; this.axisY2 = null; this.sessionVariables = { axisX: {}, axisY: {}, axisY2: {} }; } extend(Chart, CanvasJSObject); //Update Chart Properties Chart.prototype._updateOptions = function () { var _this = this; this.updateOption("width"); this.updateOption("height"); this.updateOption("dataPointMaxWidth"); this.updateOption("interactivityEnabled"); this.updateOption("theme"); if (this.updateOption("colorSet")) this._selectedColorSet = typeof (colorSets[this.colorSet]) !== "undefined" ? colorSets[this.colorSet] : colorSets["colorSet1"]; this.updateOption("backgroundColor"); if (!this.backgroundColor) this.backgroundColor = "rgba(0,0,0,0)"; this.updateOption("culture"); this._cultureInfo = new CultureInfo(this._options.culture); this.updateOption("animationEnabled"); this.animationEnabled = this.animationEnabled && isCanvasSupported; this.updateOption("animationDuration"); this.updateOption("rangeChanging"); this.updateOption("rangeChanged"); this.updateOption("exportEnabled"); this.updateOption("exportFileName"); this.updateOption("zoomType"); //Need to check this._options.zoomEnabled because this.zoomEnabled is used internally to keep track of state - and hence changes. if (this._options.zoomEnabled) { if (!this._zoomButton) { hide(this._zoomButton = document.createElement("button")); setButtonState(this, this._zoomButton, "pan"); this._toolBar.appendChild(this._zoomButton); addEvent(this._zoomButton, "click", function () { if (_this.zoomEnabled) { _this.zoomEnabled = false; _this.panEnabled = true; setButtonState(_this, _this._zoomButton, "zoom"); } else { _this.zoomEnabled = true; _this.panEnabled = false; setButtonState(_this, _this._zoomButton, "pan"); } _this.render(); }); } if (!this._resetButton) { hide(this._resetButton = document.createElement("button")); setButtonState(this, this._resetButton, "reset"); this._toolBar.appendChild(this._resetButton); addEvent(this._resetButton, "click", function () { _this._toolTip.hide(); if (_this.zoomEnabled || _this.panEnabled) { _this.zoomEnabled = true; _this.panEnabled = false; setButtonState(_this, _this._zoomButton, "pan"); _this._defaultCursor = "default"; _this.overlaidCanvas.style.cursor = _this._defaultCursor; } else { _this.zoomEnabled = false; _this.panEnabled = false; } //Reset axisX if (_this.sessionVariables.axisX) { _this.sessionVariables.axisX.newViewportMinimum = null; _this.sessionVariables.axisX.newViewportMaximum = null; } //Reset axisY if (_this.sessionVariables.axisY) { _this.sessionVariables.axisY.newViewportMinimum = null; _this.sessionVariables.axisY.newViewportMaximum = null; } //Reset axisY2 if (_this.sessionVariables.axisY2) { _this.sessionVariables.axisY2.newViewportMinimum = null; _this.sessionVariables.axisY2.newViewportMaximum = null; } _this.resetOverlayedCanvas(); hide(_this._zoomButton, _this._resetButton); _this._dispatchRangeEvent("rangeChanging", "reset"); _this.render(); _this._dispatchRangeEvent("rangeChanged", "reset"); }); this.overlaidCanvas.style.cursor = _this._defaultCursor; } if (!this.zoomEnabled && !this.panEnabled) { if (!this._zoomButton) { this.zoomEnabled = true; this.panEnabled = false; } else { if (_this._zoomButton.getAttribute("state") === _this._cultureInfo.zoomText) { this.panEnabled = true; this.zoomEnabled = false; } else { this.zoomEnabled = true; this.panEnabled = false; } show(_this._zoomButton, _this._resetButton); } } } else { this.zoomEnabled = false; this.panEnabled = false; } if (this._menuButton) { if (this.exportEnabled) show(this._menuButton); else hide(this._menuButton); } else if (this.exportEnabled && isCanvasSupported) { this._menuButton = document.createElement("button"); setButtonState(this, this._menuButton, "menu"); this._toolBar.appendChild(this._menuButton); addEvent(this._menuButton, "click", function () { if (_this._dropdownMenu.style.display === "none") { if (_this._dropDownCloseTime && ((new Date()).getTime() - _this._dropDownCloseTime.getTime() <= 500)) return; _this._dropdownMenu.style.display = "block"; _this._menuButton.blur(); _this._dropdownMenu.focus(); } }, true); } if (!this._dropdownMenu && this.exportEnabled && isCanvasSupported) { this._dropdownMenu = document.createElement("div"); this._dropdownMenu.setAttribute("tabindex", -1); this._dropdownMenu.style.cssText = "position: absolute; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; cursor: pointer;right: 1px;top: 25px;min-width: 120px;outline: 0;border: 1px solid silver;font-size: 14px;font-family: Calibri, Verdana, sans-serif;padding: 5px 0px 5px 0px;text-align: left;background-color: #fff;line-height: 20px;box-shadow: 2px 2px 10px #888888;"; _this._dropdownMenu.style.display = "none"; this._toolBar.appendChild(this._dropdownMenu); addEvent(this._dropdownMenu, "blur", function () { hide(_this._dropdownMenu); _this._dropDownCloseTime = new Date(); }, true); var exportOption = document.createElement("div"); exportOption.style.cssText = "padding: 2px 15px 2px 10px" exportOption.innerHTML = this._cultureInfo.saveJPGText; this._dropdownMenu.appendChild(exportOption); addEvent(exportOption, "mouseover", function () { this.style.backgroundColor = "#EEEEEE"; }, true); addEvent(exportOption, "mouseout", function () { this.style.backgroundColor = "transparent"; }, true); addEvent(exportOption, "click", function () { exportCanvas(_this.canvas, "jpg", _this.exportFileName); hide(_this._dropdownMenu); }, true); var exportOption = document.createElement("div"); exportOption.style.cssText = "padding: 2px 15px 2px 10px" exportOption.innerHTML = this._cultureInfo.savePNGText; this._dropdownMenu.appendChild(exportOption); addEvent(exportOption, "mouseover", function () { this.style.backgroundColor = "#EEEEEE"; }, true); addEvent(exportOption, "mouseout", function () { this.style.backgroundColor = "transparent"; }, true); addEvent(exportOption, "click", function () { exportCanvas(_this.canvas, "png", _this.exportFileName); hide(_this._dropdownMenu); }, true); } if (this._toolBar.style.display !== "none" && this._zoomButton) { this.panEnabled ? setButtonState(_this, _this._zoomButton, "zoom") : setButtonState(_this, _this._zoomButton, "pan"); if (_this._resetButton.getAttribute("state") !== _this._cultureInfo.resetText) setButtonState(_this, _this._resetButton, "reset"); } if (typeof (defaultOptions.Chart.creditHref) === "undefined") { this.creditHref = "http://canvasjs.com/"; this.creditText = "CanvasJS.com"; } else { var creditTextChanged = this.updateOption("creditText"); var creditHrefChanged = this.updateOption("creditHref"); } if (this.renderCount === 0 || (creditTextChanged || creditHrefChanged)) { this._creditLink.setAttribute("href", this.creditHref); this._creditLink.innerHTML = this.creditText; } if (this.creditHref && this.creditText) { if (!this._creditLink.parentElement) this._canvasJSContainer.appendChild(this._creditLink); } else if (this._creditLink.parentElement) this._canvasJSContainer.removeChild(this._creditLink); if (this._options.toolTip && this._toolTip._options !== this._options.toolTip) this._toolTip._options = this._options.toolTip for (var prop in this._toolTip._options) { if (this._toolTip._options.hasOwnProperty(prop)) { this._toolTip.updateOption(prop); } } } Chart.prototype._updateSize = function () { var width = 0; var height = 0; if (this._options.width) width = this.width; else this.width = width = this._container.clientWidth > 0 ? this._container.clientWidth : this.width; if (this._options.height) height = this.height; else this.height = height = this._container.clientHeight > 0 ? this._container.clientHeight : this.height; if (this.canvas.width !== width * devicePixelBackingStoreRatio || this.canvas.height !== height * devicePixelBackingStoreRatio) { setCanvasSize(this.canvas, width, height); setCanvasSize(this.overlaidCanvas, width, height); setCanvasSize(this._eventManager.ghostCanvas, width, height); return true; } return false; } // initialize chart objects Chart.prototype._initialize = function () { /// ///Initializes Chart objects/state. Creates DataSeries class instance for each DataSeries provided by ther user. Sets the Axis Type based on the user data /// //this.width = this.width; if (!this._animator) this._animator = new Animator(this); else { this._animator.cancelAllAnimations(); } this.removeAllEventListeners(); this.disableToolTip = false; this._axes = []; this.pieDoughnutClickHandler = null; //this._touchCurrentCoordinates = null; if (this.animationRequestId) this.cancelRequestAnimFrame.call(window, this.animationRequestId); this._updateOptions(); this.animatedRender = isCanvasSupported && this.animationEnabled && (this.renderCount === 0); this._updateSize(); //this._selectedColorSet = colorSets["colorSet2"]; //this.ctx.clearRect(0, 0, this.width, this.height); this.clearCanvas(); this.ctx.beginPath(); this.axisX = null; this.axisY = null; this.axisY2 = null; this._indexLabels = []; this._dataInRenderedOrder = []; this._events = []; if (this._eventManager) this._eventManager.reset(); this.plotInfo = { axisPlacement: null, axisXValueType: null, plotTypes: []//array of plotType: {type:"", axisYType: "primary", dataSeriesIndexes:[]} }; this.layoutManager = new LayoutManager(0, 0, this.width, this.height, 2); if (this.plotArea.layoutManager) this.plotArea.layoutManager.reset(); this.data = []; var dataSeriesIndex = 0; for (var series = 0; series < this._options.data.length; series++) { //for (series in this._options.data) { dataSeriesIndex++; if (!(!this._options.data[series].type || Chart._supportedChartTypes.indexOf(this._options.data[series].type) >= 0)) continue; var dataSeries = new DataSeries(this, this._options.data[series], this.theme, dataSeriesIndex - 1, ++this._eventManager.lastObjectId); if (dataSeries.name === null) dataSeries.name = "DataSeries " + (dataSeriesIndex); if (dataSeries.color === null) { if (this._options.data.length > 1) { dataSeries._colorSet = [this._selectedColorSet[dataSeries.index % this._selectedColorSet.length]]; dataSeries.color = this._selectedColorSet[dataSeries.index % this._selectedColorSet.length]; } else { if (dataSeries.type === "line" || dataSeries.type === "stepLine" || dataSeries.type === "spline" || dataSeries.type === "area" || dataSeries.type === "stepArea" || dataSeries.type === "splineArea" || dataSeries.type === "stackedArea" || dataSeries.type === "stackedArea100" || dataSeries.type === "rangeArea" || dataSeries.type === "rangeSplineArea" || dataSeries.type === "candlestick" || dataSeries.type === "ohlc") { dataSeries._colorSet = [this._selectedColorSet[0]]; } else dataSeries._colorSet = this._selectedColorSet; } } else { dataSeries._colorSet = [dataSeries.color]; } if (dataSeries.markerSize === null) { if (((dataSeries.type === "line" || dataSeries.type === "stepLine" || dataSeries.type === "spline") && dataSeries.dataPoints && dataSeries.dataPoints.length < this.width / 16) || dataSeries.type === "scatter") { //if (dataSeries.type === "line") { dataSeries.markerSize = 8; } } if ((dataSeries.type === "bubble" || dataSeries.type === "scatter") && dataSeries.dataPoints) { dataSeries.dataPoints.sort(compareDataPointX) } //if (dataSeries.markerBorderThickness === null && dataSeries.type === "scatter") { // dataSeries.markerBorderThickness = 2; //} //if (dataSeries.markerType === null) { // if (dataSeries.type === "line" & dataSeries.dataPoints.length < 500) { // dataSeries.markerType = "circle"; // } //} this.data.push(dataSeries); var seriesAxisPlacement = dataSeries.axisPlacement; //if (isDebugMode && window.console) // window.console.log(dataSeries.type); var errorMessage; if (seriesAxisPlacement === "normal") { if (this.plotInfo.axisPlacement === "xySwapped") { errorMessage = "You cannot combine \"" + dataSeries.type + "\" with bar chart"; } else if (this.plotInfo.axisPlacement === "none") { errorMessage = "You cannot combine \"" + dataSeries.type + "\" with pie chart"; } else if (this.plotInfo.axisPlacement === null) this.plotInfo.axisPlacement = "normal"; } else if (seriesAxisPlacement === "xySwapped") { if (this.plotInfo.axisPlacement === "normal") { errorMessage = "You cannot combine \"" + dataSeries.type + "\" with line, area, column or pie chart"; } else if (this.plotInfo.axisPlacement === "none") { errorMessage = "You cannot combine \"" + dataSeries.type + "\" with pie chart"; } else if (this.plotInfo.axisPlacement === null) this.plotInfo.axisPlacement = "xySwapped"; } else if (seriesAxisPlacement == "none") { if (this.plotInfo.axisPlacement === "normal") { errorMessage = "You cannot combine \"" + dataSeries.type + "\" with line, area, column or bar chart"; } else if (this.plotInfo.axisPlacement === "xySwapped") { errorMessage = "You cannot combine \"" + dataSeries.type + "\" with bar chart"; } else if (this.plotInfo.axisPlacement === null) this.plotInfo.axisPlacement = "none"; } if (errorMessage && window.console) { window.console.log(errorMessage); return; } } //if (isDebugMode && window.console) { // window.console.log("xMin: " + this.plotInfo.viewPortXMin + "; xMax: " + this.plotInfo.viewPortXMax + "; yMin: " + this.plotInfo.yMin + "; yMax: " + this.plotInfo.yMax); //} this._objectsInitialized = true; } //indexOf is not supported in IE8- Chart._supportedChartTypes = addArrayIndexOf(["line", "stepLine", "spline", "column", "area", "stepArea", "splineArea", "bar", "bubble", "scatter", "stackedColumn", "stackedColumn100", "stackedBar", "stackedBar100", "stackedArea", "stackedArea100", "candlestick", "ohlc", "rangeColumn", "rangeBar", "rangeArea", "rangeSplineArea", "pie", "doughnut", "funnel" ]); Chart.prototype.render = function (options) { if (options) this._options = options; this._initialize(); var plotAreaElements = []; //Elements to be rendered inside the plotArea //Create Primary and Secondary axis and assign them to the series for (var i = 0; i < this.data.length; i++) { if (this.plotInfo.axisPlacement === "normal" || this.plotInfo.axisPlacement === "xySwapped") { if (!this.data[i].axisYType || this.data[i].axisYType === "primary") { if (!this.axisY) { if (this.plotInfo.axisPlacement === "normal") { this._axes.push(this.axisY = new Axis(this, this._options.axisY, "axisY", "left")); } else if (this.plotInfo.axisPlacement === "xySwapped") { this._axes.push(this.axisY = new Axis(this, this._options.axisY, "axisY", "bottom")); } } this.data[i].axisY = this.axisY; } else if (this.data[i].axisYType === "secondary") { if (!this.axisY2) { if (this.plotInfo.axisPlacement === "normal") { this._axes.push(this.axisY2 = new Axis(this, this._options.axisY2, "axisY", "right")); } else if (this.plotInfo.axisPlacement === "xySwapped") { this._axes.push(this.axisY2 = new Axis(this, this._options.axisY2, "axisY", "top")); } } this.data[i].axisY = this.axisY2; } if (!this.axisX) { if (this.plotInfo.axisPlacement === "normal") { this._axes.push(this.axisX = new Axis(this, this._options.axisX, "axisX", "bottom")); } else if (this.plotInfo.axisPlacement === "xySwapped") { this._axes.push(this.axisX = new Axis(this, this._options.axisX, "axisX", "left")); } } this.data[i].axisX = this.axisX; } } //If Both Primary and Secondary axis are present, disable gridlines for one of them unless the user has set value for both if (this.axisY && this.axisY2) { if (this.axisY.gridThickness > 0 && typeof (this.axisY2._options.gridThickness) === "undefined") this.axisY2.gridThickness = 0; else if (this.axisY2.gridThickness > 0 && typeof (this.axisY._options.gridThickness) === "undefined") this.axisY.gridThickness = 0; } //Show toolBar when viewportMinimum/viewportMaximum are set var showToolBar = false; if (this._axes.length > 0 && (this.zoomEnabled || this.panEnabled)) { for (var i = 0; i < this._axes.length; i++) { if (this._axes[i].viewportMinimum !== null || this._axes[i].viewportMaximum !== null) { showToolBar = true; break; } } } if (showToolBar) { show(this._zoomButton, this._resetButton); } else { hide(this._zoomButton, this._resetButton); } this._processData();// Categorises the dataSeries and calculates min, max and other values if (this._options.title) { this._title = new Title(this, this._options.title); if (!this._title.dockInsidePlotArea) this._title.render(); else plotAreaElements.push(this._title); } if (this._options.subtitles) { for (var i = 0; i < this._options.subtitles.length; i++) { this.subtitles = []; var subtitle = new Subtitle(this, this._options.subtitles[i]); this.subtitles.push(subtitle); if (!subtitle.dockInsidePlotArea) subtitle.render(); else plotAreaElements.push(subtitle); } } this.legend = new Legend(this, this._options.legend, this.theme); for (var i = 0; i < this.data.length; i++) { if (this.data[i].showInLegend || this.data[i].type === "pie" || this.data[i].type === "doughnut") { this.legend.dataSeries.push(this.data[i]); } } if (!this.legend.dockInsidePlotArea) this.legend.render(); else plotAreaElements.push(this.legend); //TBI: Revisit and check if the functionality is enough. if (this.plotInfo.axisPlacement === "normal" || this.plotInfo.axisPlacement === "xySwapped") { //var freeSpace = this.layoutManager.getFreeSpace(); Axis.setLayoutAndRender(this.axisX, this.axisY, this.axisY2, this.plotInfo.axisPlacement, this.layoutManager.getFreeSpace()); } else if (this.plotInfo.axisPlacement === "none") { //In case of charts with axis this method is called inside setLayoutAndRender this.preparePlotArea(); } else { return; } for (var index = 0; index < plotAreaElements.length; index++) { plotAreaElements[index].render(); } var animations = []; if (this.animatedRender) { var initialState = createCanvas(this.width, this.height); var initialStateCtx = initialState.getContext("2d"); initialStateCtx.drawImage(this.canvas, 0, 0, this.width, this.height); } for (var i = 0; i < this.plotInfo.plotTypes.length; i++) { var plotType = this.plotInfo.plotTypes[i]; for (var j = 0; j < plotType.plotUnits.length; j++) { var plotUnit = plotType.plotUnits[j]; var animationInfo = null; plotUnit.targetCanvas = null; //In case chart updates before the animation is complete, previous canvases need to be removed if (this.animatedRender) { plotUnit.targetCanvas = createCanvas(this.width, this.height); plotUnit.targetCanvasCtx = plotUnit.targetCanvas.getContext("2d"); } if (plotUnit.type === "line") animationInfo = this.renderLine(plotUnit); else if (plotUnit.type === "stepLine") animationInfo = this.renderStepLine(plotUnit); else if (plotUnit.type === "spline") animationInfo = this.renderSpline(plotUnit); else if (plotUnit.type === "column") animationInfo = this.renderColumn(plotUnit); else if (plotUnit.type === "bar") animationInfo = this.renderBar(plotUnit); else if (plotUnit.type === "area") animationInfo = this.renderArea(plotUnit); else if (plotUnit.type === "stepArea") animationInfo = this.renderStepArea(plotUnit); else if (plotUnit.type === "splineArea") animationInfo = this.renderSplineArea(plotUnit); else if (plotUnit.type === "stackedColumn") animationInfo = this.renderStackedColumn(plotUnit); else if (plotUnit.type === "stackedColumn100") animationInfo = this.renderStackedColumn100(plotUnit); else if (plotUnit.type === "stackedBar") animationInfo = this.renderStackedBar(plotUnit); else if (plotUnit.type === "stackedBar100") animationInfo = this.renderStackedBar100(plotUnit); else if (plotUnit.type === "stackedArea") animationInfo = this.renderStackedArea(plotUnit); else if (plotUnit.type === "stackedArea100") animationInfo = this.renderStackedArea100(plotUnit); else if (plotUnit.type === "bubble") animationInfo = animationInfo = this.renderBubble(plotUnit); else if (plotUnit.type === "scatter") animationInfo = this.renderScatter(plotUnit); else if (plotUnit.type === "pie") this.renderPie(plotUnit); else if (plotUnit.type === "doughnut") this.renderPie(plotUnit); else if (plotUnit.type === "candlestick") animationInfo = this.renderCandlestick(plotUnit); else if (plotUnit.type === "ohlc") animationInfo = this.renderCandlestick(plotUnit); else if (plotUnit.type === "rangeColumn") animationInfo = this.renderRangeColumn(plotUnit); else if (plotUnit.type === "rangeBar") animationInfo = this.renderRangeBar(plotUnit); else if (plotUnit.type === "rangeArea") animationInfo = this.renderRangeArea(plotUnit); else if (plotUnit.type === "rangeSplineArea") animationInfo = this.renderRangeSplineArea(plotUnit); for (var k = 0; k < plotUnit.dataSeriesIndexes.length; k++) { this._dataInRenderedOrder.push(this.data[plotUnit.dataSeriesIndexes[k]]); } if (this.animatedRender && animationInfo) animations.push(animationInfo); } } if (this.animatedRender && this._indexLabels.length > 0) { var indexLabelCanvas = createCanvas(this.width, this.height); var indexLabelCanvasCtx = indexLabelCanvas.getContext("2d"); animations.push(this.renderIndexLabels(indexLabelCanvasCtx)); } var _this = this; if (animations.length > 0) { //var animationCount = 0; _this.disableToolTip = true; _this._animator.animate(200, _this.animationDuration, function (fractionComplete) { //console.log(fractionComplete); //animationCount++; _this.ctx.clearRect(0, 0, _this.width, _this.height); // _this.ctx.drawImage(initialState, 0, 0, _this.width * devicePixelBackingStoreRatio, _this.height * devicePixelBackingStoreRatio, 0, 0, _this.width, _this.height); _this.ctx.drawImage(initialState, 0, 0, Math.floor(_this.width * devicePixelBackingStoreRatio), Math.floor(_this.height * devicePixelBackingStoreRatio), 0, 0, _this.width, _this.height); for (var l = 0; l < animations.length; l++) { animationInfo = animations[l]; if (fractionComplete < 1 && typeof (animationInfo.startTimePercent) !== "undefined") { if (fractionComplete >= animationInfo.startTimePercent) { //animationInfo.animationCallback(AnimationHelper.easing.linear(fractionComplete - animationInfo.startTimePercent, 0, 1, 1 - animationInfo.startTimePercent), animationInfo); animationInfo.animationCallback(animationInfo.easingFunction(fractionComplete - animationInfo.startTimePercent, 0, 1, 1 - animationInfo.startTimePercent), animationInfo); } } else { animationInfo.animationCallback(animationInfo.easingFunction(fractionComplete, 0, 1, 1), animationInfo); } } _this.dispatchEvent("dataAnimationIterationEnd", { chart: _this }); }, function () { animations = []; var count = 0; //Delete all render target canvases used for animation. for (var i = 0; i < _this.plotInfo.plotTypes.length; i++) { var plotType = _this.plotInfo.plotTypes[i]; for (var j = 0; j < plotType.plotUnits.length; j++) { var plotUnit = plotType.plotUnits[j]; plotUnit.targetCanvas = null; } } initialState = null; _this.disableToolTip = false; //console.log("*********** Animation Complete - " + animationCount + " ***********"); }); } else { if (_this._indexLabels.length > 0) _this.renderIndexLabels(); _this.dispatchEvent("dataAnimationIterationEnd", { chart: _this }); } this.attachPlotAreaEventHandlers(); if (!this.zoomEnabled && !this.panEnabled && this._zoomButton && this._zoomButton.style.display !== "none") { hide(this._zoomButton, this._resetButton); } this._toolTip._updateToolTip(); this.renderCount++; //if (window.console) { // window.console.log(new Date().getTime() - dt); //} if (isDebugMode) { var _this = this; setTimeout(function () { var ghostCanvasCopy = document.getElementById("ghostCanvasCopy"); if (ghostCanvasCopy) { //console.log(ghostCanvasCopy.clientWidth); setCanvasSize(ghostCanvasCopy, _this.width, _this.height); var ghostCanvasCopyCtx = ghostCanvasCopy.getContext("2d"); //ghostCanvasCopyCtx.scale(1, 1); //var imageData = this._eventManager.ghostCtx.getImageData(0, 0, this._container.clientWidth, this._container.clientHeight); //this._eventManager.ghostCtx.drawImage(this._eventManager.ghostCanvas, 0, 0); //this.ctx.drawImage(this._eventManager.ghostCanvas, 0, 0); ghostCanvasCopyCtx.drawImage(_this._eventManager.ghostCanvas, 0, 0); //_this._canvasJSContainer.appendChild(_this._eventManager.ghostCanvas); //_this.overlaidCanvasCtx.drawImage(_this._eventManager.ghostCanvas, 0, 0); } }, 2000); } } Chart.prototype.attachPlotAreaEventHandlers = function () { //this._toolBar.style.display = "inline"; this.attachEvent({ context: this, chart: this, mousedown: this._plotAreaMouseDown, mouseup: this._plotAreaMouseUp, mousemove: this._plotAreaMouseMove, cursor: this.zoomEnabled ? "col-resize" : "move", cursor: this.panEnabled ? "move" : "default", capture: true, bounds: this.plotArea }); } Chart.prototype.categoriseDataSeries = function () { var dataSeries = ""; for (var i = 0; i < this.data.length; i++) { dataSeries = this.data[i] if (!dataSeries.dataPoints || dataSeries.dataPoints.length === 0 || !dataSeries.visible) continue; if (Chart._supportedChartTypes.indexOf(dataSeries.type) >= 0) { var plotType = null; var plotTypeExists = false; var plotUnit = null; var plotUnitExists = false; for (var j = 0; j < this.plotInfo.plotTypes.length; j++) { if (this.plotInfo.plotTypes[j].type === dataSeries.type) { plotTypeExists = true; var plotType = this.plotInfo.plotTypes[j]; break; } } if (!plotTypeExists) { plotType = { type: dataSeries.type, totalDataSeries: 0, plotUnits: [] }; this.plotInfo.plotTypes.push(plotType) } for (var j = 0; j < plotType.plotUnits.length; j++) { if (plotType.plotUnits[j].axisYType === dataSeries.axisYType) { plotUnitExists = true; var plotUnit = plotType.plotUnits[j]; break; } } if (!plotUnitExists) { plotUnit = { type: dataSeries.type, previousDataSeriesCount: 0, //to be set next index: plotType.plotUnits.length, plotType: plotType, axisYType: dataSeries.axisYType, axisY: dataSeries.axisYType === "primary" ? this.axisY : this.axisY2, axisX: this.axisX, dataSeriesIndexes: [], //index of dataSeries yTotals: [] } plotType.plotUnits.push(plotUnit); } plotType.totalDataSeries++; plotUnit.dataSeriesIndexes.push(i); dataSeries.plotUnit = plotUnit; } } for (var i = 0; i < this.plotInfo.plotTypes.length; i++) { var plotType = this.plotInfo.plotTypes[i]; var previousDataSeriesCount = 0; for (var j = 0; j < plotType.plotUnits.length; j++) { plotType.plotUnits[j].previousDataSeriesCount = previousDataSeriesCount; previousDataSeriesCount += plotType.plotUnits[j].dataSeriesIndexes.length; } } } Chart.prototype.assignIdToDataPoints = function () { for (var i = 0; i < this.data.length; i++) { var dataSeries = this.data[i]; if (!dataSeries.dataPoints) continue; var length = dataSeries.dataPoints.length; for (var j = 0; j < length; j++) { dataSeries.dataPointIds[j] = ++this._eventManager.lastObjectId; } } } Chart.prototype._processData = function () { this.assignIdToDataPoints(); this.categoriseDataSeries(); for (var i = 0; i < this.plotInfo.plotTypes.length; i++) { var plotType = this.plotInfo.plotTypes[i]; for (var j = 0; j < plotType.plotUnits.length; j++) { var plotUnit = plotType.plotUnits[j]; if (plotUnit.type === "line" || plotUnit.type === "stepLine" || plotUnit.type === "spline" || plotUnit.type === "column" || plotUnit.type === "area" || plotUnit.type === "stepArea" || plotUnit.type === "splineArea" || plotUnit.type === "bar" || plotUnit.type === "bubble" || plotUnit.type === "scatter") this._processMultiseriesPlotUnit(plotUnit); else if (plotUnit.type === "stackedColumn" || plotUnit.type === "stackedBar" || plotUnit.type === "stackedArea") this._processStackedPlotUnit(plotUnit); else if (plotUnit.type === "stackedColumn100" || plotUnit.type === "stackedBar100" || plotUnit.type === "stackedArea100") this._processStacked100PlotUnit(plotUnit); else if (plotUnit.type === "candlestick" || plotUnit.type === "ohlc" || plotUnit.type === "rangeColumn" || plotUnit.type === "rangeBar" || plotUnit.type === "rangeArea" || plotUnit.type === "rangeSplineArea") this._processMultiYPlotUnit(plotUnit); } } } Chart.prototype._processMultiseriesPlotUnit = function (plotUnit) { if (!plotUnit.dataSeriesIndexes || plotUnit.dataSeriesIndexes.length < 1) return; var axisYDataInfo = plotUnit.axisY.dataInfo; var axisXDataInfo = plotUnit.axisX.dataInfo; var dataPointX, dataPointY; var isDateTime = false; for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeries = this.data[plotUnit.dataSeriesIndexes[j]]; var i = 0; var isFirstDPInViewPort = false; var isLastDPInViewPort = false; if (dataSeries.axisPlacement === "normal" || dataSeries.axisPlacement === "xySwapped") { var plotAreaXMin = this.sessionVariables.axisX.newViewportMinimum ? this.sessionVariables.axisX.newViewportMinimum : (this._options.axisX && this._options.axisX.viewportMinimum) ? this._options.axisX.viewportMinimum : (this._options.axisX && this._options.axisX.minimum) ? this._options.axisX.minimum : -Infinity; var plotAreaXMax = this.sessionVariables.axisX.newViewportMaximum ? this.sessionVariables.axisX.newViewportMaximum : (this._options.axisX && this._options.axisX.viewportMaximum) ? this._options.axisX.viewportMaximum : (this._options.axisX && this._options.axisX.maximum) ? this._options.axisX.maximum : Infinity; } if (dataSeries.dataPoints[i].x && dataSeries.dataPoints[i].x.getTime || dataSeries.xValueType === "dateTime") { isDateTime = true; } for (i = 0; i < dataSeries.dataPoints.length; i++) { if (typeof dataSeries.dataPoints[i].x === "undefined") { dataSeries.dataPoints[i].x = i; } if (dataSeries.dataPoints[i].x.getTime) { isDateTime = true; dataPointX = dataSeries.dataPoints[i].x.getTime();//dataPointX is used so that getTime is called only once in case of dateTime values } else dataPointX = dataSeries.dataPoints[i].x; dataPointY = dataSeries.dataPoints[i].y; if (dataPointX < axisXDataInfo.min) axisXDataInfo.min = dataPointX; if (dataPointX > axisXDataInfo.max) axisXDataInfo.max = dataPointX; if (dataPointY < axisYDataInfo.min) axisYDataInfo.min = dataPointY; if (dataPointY > axisYDataInfo.max) axisYDataInfo.max = dataPointY; if (i > 0) { var xDiff = dataPointX - dataSeries.dataPoints[i - 1].x; xDiff < 0 && (xDiff = xDiff * -1); //If Condition shortcut if (axisXDataInfo.minDiff > xDiff && xDiff !== 0) { axisXDataInfo.minDiff = xDiff; } if (dataPointY !== null && dataSeries.dataPoints[i - 1].y !== null) { var yDiff = dataPointY - dataSeries.dataPoints[i - 1].y; yDiff < 0 && (yDiff = yDiff * -1); //If Condition shortcut if (axisYDataInfo.minDiff > yDiff && yDiff !== 0) { axisYDataInfo.minDiff = yDiff; } } } // This section makes sure that partially visible dataPoints are included in the begining if (dataPointX < plotAreaXMin && !isFirstDPInViewPort) { continue; } else if (!isFirstDPInViewPort) { isFirstDPInViewPort = true; if (i > 0) { i -= 2; continue; } } // This section makes sure that partially visible dataPoints are included at the end if (dataPointX > plotAreaXMax && !isLastDPInViewPort) { isLastDPInViewPort = true; } else if (dataPointX > plotAreaXMax && isLastDPInViewPort) { continue; } if (dataSeries.dataPoints[i].label) plotUnit.axisX.labels[dataPointX] = dataSeries.dataPoints[i].label; if (dataPointX < axisXDataInfo.viewPortMin) axisXDataInfo.viewPortMin = dataPointX; if (dataPointX > axisXDataInfo.viewPortMax) axisXDataInfo.viewPortMax = dataPointX; if (dataPointY === null) continue; if (dataPointY < axisYDataInfo.viewPortMin) axisYDataInfo.viewPortMin = dataPointY; if (dataPointY > axisYDataInfo.viewPortMax) axisYDataInfo.viewPortMax = dataPointY; } this.plotInfo.axisXValueType = dataSeries.xValueType = isDateTime ? "dateTime" : "number"; } //this.dataPoints.sort(compareDataPointX); //this.dataPoints.sort(function (dataPoint1, dataPoint2) { return dataPoint1.x - dataPoint2.x; }); } Chart.prototype._processStackedPlotUnit = function (plotUnit) { if (!plotUnit.dataSeriesIndexes || plotUnit.dataSeriesIndexes.length < 1) return; var axisYDataInfo = plotUnit.axisY.dataInfo; var axisXDataInfo = plotUnit.axisX.dataInfo; var dataPointX, dataPointY; var isDateTime = false; var dataPointYPositiveSums = []; var dataPointYNegativeSums = []; for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeries = this.data[plotUnit.dataSeriesIndexes[j]]; var i = 0; var isFirstDPInViewPort = false; var isLastDPInViewPort = false; if (dataSeries.axisPlacement === "normal" || dataSeries.axisPlacement === "xySwapped") { var plotAreaXMin = this.sessionVariables.axisX.newViewportMinimum ? this.sessionVariables.axisX.newViewportMinimum : (this._options.axisX && this._options.axisX.viewportMinimum) ? this._options.axisX.viewportMinimum : (this._options.axisX && this._options.axisX.minimum) ? this._options.axisX.minimum : -Infinity; var plotAreaXMax = this.sessionVariables.axisX.newViewportMaximum ? this.sessionVariables.axisX.newViewportMaximum : (this._options.axisX && this._options.axisX.viewportMaximum) ? this._options.axisX.viewportMaximum : (this._options.axisX && this._options.axisX.maximum) ? this._options.axisX.maximum : Infinity; } if (dataSeries.dataPoints[i].x && dataSeries.dataPoints[i].x.getTime || dataSeries.xValueType === "dateTime") { isDateTime = true; } for (i = 0; i < dataSeries.dataPoints.length; i++) { // Requird when no x values are provided if (typeof dataSeries.dataPoints[i].x === "undefined") { dataSeries.dataPoints[i].x = i; } if (dataSeries.dataPoints[i].x.getTime) { isDateTime = true; dataPointX = dataSeries.dataPoints[i].x.getTime();//dataPointX is used so that getTime is called only once in case of dateTime values } else dataPointX = dataSeries.dataPoints[i].x; dataPointY = dataSeries.dataPoints[i].y; if (dataPointX < axisXDataInfo.min) axisXDataInfo.min = dataPointX; if (dataPointX > axisXDataInfo.max) axisXDataInfo.max = dataPointX; if (i > 0) { var xDiff = dataPointX - dataSeries.dataPoints[i - 1].x; xDiff < 0 && (xDiff = xDiff * -1); //If Condition shortcut if (axisXDataInfo.minDiff > xDiff && xDiff !== 0) { axisXDataInfo.minDiff = xDiff; } if (dataPointY !== null && dataSeries.dataPoints[i - 1].y !== null) { var yDiff = dataPointY - dataSeries.dataPoints[i - 1].y; yDiff < 0 && (yDiff = yDiff * -1); //If Condition shortcut if (axisYDataInfo.minDiff > yDiff && yDiff !== 0) { axisYDataInfo.minDiff = yDiff; } } } // This section makes sure that partially visible dataPoints are included in the begining if (dataPointX < plotAreaXMin && !isFirstDPInViewPort) { continue; } else if (!isFirstDPInViewPort) { isFirstDPInViewPort = true; if (i > 0) { i -= 2; continue; } } // This section makes sure that partially visible dataPoints are included at the end if (dataPointX > plotAreaXMax && !isLastDPInViewPort) { isLastDPInViewPort = true; } else if (dataPointX > plotAreaXMax && isLastDPInViewPort) { continue; } if (dataSeries.dataPoints[i].label) plotUnit.axisX.labels[dataPointX] = dataSeries.dataPoints[i].label; if (dataPointX < axisXDataInfo.viewPortMin) axisXDataInfo.viewPortMin = dataPointX; if (dataPointX > axisXDataInfo.viewPortMax) axisXDataInfo.viewPortMax = dataPointX; if (dataPointY === null) continue; plotUnit.yTotals[dataPointX] = (!plotUnit.yTotals[dataPointX] ? 0 : plotUnit.yTotals[dataPointX]) + Math.abs(dataPointY); if (dataPointY >= 0) { if (dataPointYPositiveSums[dataPointX]) dataPointYPositiveSums[dataPointX] += dataPointY; else dataPointYPositiveSums[dataPointX] = dataPointY; } else { if (dataPointYNegativeSums[dataPointX]) dataPointYNegativeSums[dataPointX] += dataPointY; else dataPointYNegativeSums[dataPointX] = dataPointY; } } this.plotInfo.axisXValueType = dataSeries.xValueType = isDateTime ? "dateTime" : "number"; } for (i in dataPointYPositiveSums) { if (dataPointYPositiveSums.hasOwnProperty(i)) { if (isNaN(i)) { continue; } var ySum = dataPointYPositiveSums[i]; if (ySum < axisYDataInfo.min) axisYDataInfo.min = ySum; if (ySum > axisYDataInfo.max) axisYDataInfo.max = ySum; if (i < axisXDataInfo.viewPortMin || i > axisXDataInfo.viewPortMax) continue; if (ySum < axisYDataInfo.viewPortMin) axisYDataInfo.viewPortMin = ySum; if (ySum > axisYDataInfo.viewPortMax) axisYDataInfo.viewPortMax = ySum; } } for (i in dataPointYNegativeSums) { if (dataPointYNegativeSums.hasOwnProperty(i)) { if (isNaN(i)) { continue; } var ySum = dataPointYNegativeSums[i]; if (ySum < axisYDataInfo.min) axisYDataInfo.min = ySum; if (ySum > axisYDataInfo.max) axisYDataInfo.max = ySum; if (i < axisXDataInfo.viewPortMin || i > axisXDataInfo.viewPortMax) continue; if (ySum < axisYDataInfo.viewPortMin) axisYDataInfo.viewPortMin = ySum; if (ySum > axisYDataInfo.viewPortMax) axisYDataInfo.viewPortMax = ySum; } } //this.dataPoints.sort(compareDataPointX); //this.dataPoints.sort(function (dataPoint1, dataPoint2) { return dataPoint1.x - dataPoint2.x; }); //window.console.log("viewPortYMin: " + plotInfo.viewPortYMin + "; viewPortYMax: " + plotInfo.viewPortYMax); } Chart.prototype._processStacked100PlotUnit = function (plotUnit) { if (!plotUnit.dataSeriesIndexes || plotUnit.dataSeriesIndexes.length < 1) return; var axisYDataInfo = plotUnit.axisY.dataInfo; var axisXDataInfo = plotUnit.axisX.dataInfo; var dataPointX, dataPointY; var isDateTime = false; var containsPositiveY = false; var containsNegativeY = false; var dataPointYSums = []; for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeries = this.data[plotUnit.dataSeriesIndexes[j]]; var i = 0; var isFirstDPInViewPort = false; var isLastDPInViewPort = false; if (dataSeries.axisPlacement === "normal" || dataSeries.axisPlacement === "xySwapped") { var plotAreaXMin = this.sessionVariables.axisX.newViewportMinimum ? this.sessionVariables.axisX.newViewportMinimum : (this._options.axisX && this._options.axisX.viewportMinimum) ? this._options.axisX.viewportMinimum : (this._options.axisX && this._options.axisX.minimum) ? this._options.axisX.minimum : -Infinity; var plotAreaXMax = this.sessionVariables.axisX.newViewportMaximum ? this.sessionVariables.axisX.newViewportMaximum : (this._options.axisX && this._options.axisX.viewportMaximum) ? this._options.axisX.viewportMaximum : (this._options.axisX && this._options.axisX.maximum) ? this._options.axisX.maximum : Infinity; } if (dataSeries.dataPoints[i].x && dataSeries.dataPoints[i].x.getTime || dataSeries.xValueType === "dateTime") { isDateTime = true; } for (i = 0; i < dataSeries.dataPoints.length; i++) { // Requird when no x values are provided if (typeof dataSeries.dataPoints[i].x === "undefined") { dataSeries.dataPoints[i].x = i; } if (dataSeries.dataPoints[i].x.getTime) { isDateTime = true; dataPointX = dataSeries.dataPoints[i].x.getTime();//dataPointX is used so that getTime is called only once in case of dateTime values } else dataPointX = dataSeries.dataPoints[i].x; dataPointY = dataSeries.dataPoints[i].y; if (dataPointX < axisXDataInfo.min) axisXDataInfo.min = dataPointX; if (dataPointX > axisXDataInfo.max) axisXDataInfo.max = dataPointX; if (i > 0) { var xDiff = dataPointX - dataSeries.dataPoints[i - 1].x; xDiff < 0 && (xDiff = xDiff * -1); //If Condition shortcut if (axisXDataInfo.minDiff > xDiff && xDiff !== 0) { axisXDataInfo.minDiff = xDiff; } if (dataPointY !== null && dataSeries.dataPoints[i - 1].y !== null) { var yDiff = dataPointY - dataSeries.dataPoints[i - 1].y; yDiff < 0 && (yDiff = yDiff * -1); //If Condition shortcut if (axisYDataInfo.minDiff > yDiff && yDiff !== 0) { axisYDataInfo.minDiff = yDiff; } } } // This section makes sure that partially visible dataPoints are included in the begining if (dataPointX < plotAreaXMin && !isFirstDPInViewPort) { continue; } else if (!isFirstDPInViewPort) { isFirstDPInViewPort = true; if (i > 0) { i -= 2; continue; } } // This section makes sure that partially visible dataPoints are included at the end if (dataPointX > plotAreaXMax && !isLastDPInViewPort) { isLastDPInViewPort = true; } else if (dataPointX > plotAreaXMax && isLastDPInViewPort) { continue; } if (dataSeries.dataPoints[i].label) plotUnit.axisX.labels[dataPointX] = dataSeries.dataPoints[i].label; if (dataPointX < axisXDataInfo.viewPortMin) axisXDataInfo.viewPortMin = dataPointX; if (dataPointX > axisXDataInfo.viewPortMax) axisXDataInfo.viewPortMax = dataPointX; if (dataPointY === null) continue; plotUnit.yTotals[dataPointX] = (!plotUnit.yTotals[dataPointX] ? 0 : plotUnit.yTotals[dataPointX]) + Math.abs(dataPointY); if (dataPointY >= 0) { containsPositiveY = true; } else { containsNegativeY = true; } if (dataPointYSums[dataPointX]) dataPointYSums[dataPointX] += Math.abs(dataPointY); else dataPointYSums[dataPointX] = Math.abs(dataPointY); } this.plotInfo.axisXValueType = dataSeries.xValueType = isDateTime ? "dateTime" : "number"; } if (containsPositiveY && !containsNegativeY) { axisYDataInfo.max = 99; axisYDataInfo.min = 1; } else if (containsPositiveY && containsNegativeY) { axisYDataInfo.max = 99; axisYDataInfo.min = -99; } else if (!containsPositiveY && containsNegativeY) { axisYDataInfo.max = -1; axisYDataInfo.min = -99; } axisYDataInfo.viewPortMin = axisYDataInfo.min; axisYDataInfo.viewPortMax = axisYDataInfo.max; plotUnit.dataPointYSums = dataPointYSums; //this.dataPoints.sort(compareDataPointX); //this.dataPoints.sort(function (dataPoint1, dataPoint2) { return dataPoint1.x - dataPoint2.x; }); //window.console.log("viewPortYMin: " + plotInfo.viewPortYMin + "; viewPortYMax: " + plotInfo.viewPortYMax); } Chart.prototype._processMultiYPlotUnit = function (plotUnit) { if (!plotUnit.dataSeriesIndexes || plotUnit.dataSeriesIndexes.length < 1) return; var axisYDataInfo = plotUnit.axisY.dataInfo; var axisXDataInfo = plotUnit.axisX.dataInfo; var dataPointX, dataPointY, dataPointYMin, dataPointYMax; var isDateTime = false; for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeries = this.data[plotUnit.dataSeriesIndexes[j]]; var i = 0; var isFirstDPInViewPort = false; var isLastDPInViewPort = false; if (dataSeries.axisPlacement === "normal" || dataSeries.axisPlacement === "xySwapped") { var plotAreaXMin = this.sessionVariables.axisX.newViewportMinimum ? this.sessionVariables.axisX.newViewportMinimum : (this._options.axisX && this._options.axisX.viewportMinimum) ? this._options.axisX.viewportMinimum : (this._options.axisX && this._options.axisX.minimum) ? this._options.axisX.minimum : -Infinity; var plotAreaXMax = this.sessionVariables.axisX.newViewportMaximum ? this.sessionVariables.axisX.newViewportMaximum : (this._options.axisX && this._options.axisX.viewportMaximum) ? this._options.axisX.viewportMaximum : (this._options.axisX && this._options.axisX.maximum) ? this._options.axisX.maximum : Infinity; } if (dataSeries.dataPoints[i].x && dataSeries.dataPoints[i].x.getTime || dataSeries.xValueType === "dateTime") { isDateTime = true; } for (i = 0; i < dataSeries.dataPoints.length; i++) { if (typeof dataSeries.dataPoints[i].x === "undefined") { dataSeries.dataPoints[i].x = i; } if (dataSeries.dataPoints[i].x.getTime) { isDateTime = true; dataPointX = dataSeries.dataPoints[i].x.getTime();//dataPointX is used so that getTime is called only once in case of dateTime values } else dataPointX = dataSeries.dataPoints[i].x; dataPointY = dataSeries.dataPoints[i].y; if (dataPointY && dataPointY.length) { dataPointYMin = Math.min.apply(null, dataPointY); dataPointYMax = Math.max.apply(null, dataPointY); } if (dataPointX < axisXDataInfo.min) axisXDataInfo.min = dataPointX; if (dataPointX > axisXDataInfo.max) axisXDataInfo.max = dataPointX; if (dataPointYMin < axisYDataInfo.min) axisYDataInfo.min = dataPointYMin; if (dataPointYMax > axisYDataInfo.max) axisYDataInfo.max = dataPointYMax; if (i > 0) { var xDiff = dataPointX - dataSeries.dataPoints[i - 1].x; xDiff < 0 && (xDiff = xDiff * -1); //If Condition shortcut if (axisXDataInfo.minDiff > xDiff && xDiff !== 0) { axisXDataInfo.minDiff = xDiff; } if (dataPointY[0] !== null && dataSeries.dataPoints[i - 1].y[0] !== null) { var yDiff = dataPointY[0] - dataSeries.dataPoints[i - 1].y[0]; yDiff < 0 && (yDiff = yDiff * -1); //If Condition shortcut if (axisYDataInfo.minDiff > yDiff && yDiff !== 0) { axisYDataInfo.minDiff = yDiff; } } } // This section makes sure that partially visible dataPoints are included in the begining if (dataPointX < plotAreaXMin && !isFirstDPInViewPort) { continue; } else if (!isFirstDPInViewPort) { isFirstDPInViewPort = true; if (i > 0) { i -= 2; continue; } } // This section makes sure that partially visible dataPoints are included at the end if (dataPointX > plotAreaXMax && !isLastDPInViewPort) { isLastDPInViewPort = true; } else if (dataPointX > plotAreaXMax && isLastDPInViewPort) { continue; } if (dataSeries.dataPoints[i].label) plotUnit.axisX.labels[dataPointX] = dataSeries.dataPoints[i].label; if (dataPointX < axisXDataInfo.viewPortMin) axisXDataInfo.viewPortMin = dataPointX; if (dataPointX > axisXDataInfo.viewPortMax) axisXDataInfo.viewPortMax = dataPointX; if (dataPointY === null) continue; if (dataPointYMin < axisYDataInfo.viewPortMin) axisYDataInfo.viewPortMin = dataPointYMin; if (dataPointYMax > axisYDataInfo.viewPortMax) axisYDataInfo.viewPortMax = dataPointYMax; } this.plotInfo.axisXValueType = dataSeries.xValueType = isDateTime ? "dateTime" : "number"; } //this.dataPoints.sort(compareDataPointX); //this.dataPoints.sort(function (dataPoint1, dataPoint2) { return dataPoint1.x - dataPoint2.x; }); } //getClosest returns objects nearby and hence shouldn't be used for events like click, mouseover, mousemove, etc which require object that is exactly under the mouse. Chart.prototype.getDataPointAtXY = function (mouseX, mouseY, getClosest) { getClosest = getClosest || false; var results = []; for (var i = this._dataInRenderedOrder.length - 1; i >= 0; i--) { var dataSeries = this._dataInRenderedOrder[i]; var result = null; result = dataSeries.getDataPointAtXY(mouseX, mouseY, getClosest); if (result) results.push(result); } var closestResult = null; var onlyLineAreaTypes = false; for (var m = 0; m < results.length; m++) { if (results[m].dataSeries.type === "line" || results[m].dataSeries.type === "stepLine" || results[m].dataSeries.type === "area" || results[m].dataSeries.type === "stepArea") { var markerSize = getProperty("markerSize", results[m].dataPoint, results[m].dataSeries) || 8; if (results[m].distance <= markerSize / 2) { onlyLineAreaTypes = true; break; } } } for (m = 0; m < results.length; m++) { if (onlyLineAreaTypes && results[m].dataSeries.type !== "line" && results[m].dataSeries.type !== "stepLine" && results[m].dataSeries.type !== "area" && results[m].dataSeries.type !== "stepArea") continue; if (!closestResult) { closestResult = results[m]; } else if (results[m].distance <= closestResult.distance) { closestResult = results[m]; } } return closestResult; } Chart.prototype.getObjectAtXY = function (mouseX, mouseY, getClosest) { getClosest = getClosest || false; var id = null; var dataPointInfo = this.getDataPointAtXY(mouseX, mouseY, getClosest); if (dataPointInfo) { id = dataPointInfo.dataSeries.dataPointIds[dataPointInfo.dataPointIndex]; } else if (isCanvasSupported) {//IE9+ id = getObjectId(mouseX, mouseY, this._eventManager.ghostCtx); } else { for (var i = 0; i < this.legend.items.length; i++) { var item = this.legend.items[i]; if (mouseX >= item.x1 && mouseX <= item.x2 && mouseY >= item.y1 && mouseY <= item.y2) { id = item.id; } } } return id; } /// Calculates Font Size based on standardSize and Chart Size /// Standard font size for a Chart with min(width,height) = 400px /// The area. Chart.prototype.getAutoFontSize = function (standardSize, width, height) { width = width || this.width; height = height || this.height; var fontSizeScaleFactor = standardSize / 400; return Math.round(Math.min(this.width, this.height) * fontSizeScaleFactor); } //#region Events Chart.prototype.resetOverlayedCanvas = function () { //var width = this.overlaidCanvas.width; //this.overlaidCanvas.width = 0; //this.overlaidCanvas.width = width; this.overlaidCanvasCtx.clearRect(0, 0, this.width, this.height); } Chart.prototype.clearCanvas = function () { this.ctx.clearRect(0, 0, this.width, this.height); if (this.backgroundColor) { this.ctx.fillStyle = this.backgroundColor; this.ctx.fillRect(0, 0, this.width, this.height); } } Chart.prototype.attachEvent = function (param) { this._events.push(param); } Chart.prototype._touchEventHandler = function (ev) { if (!ev.changedTouches || !this.interactivityEnabled) return; var mouseEvents = []; var touches = ev.changedTouches; var first = touches ? touches[0] : ev; var touchCurrentCoordinates = null; //window.console.log(touches.length); switch (ev.type) { case "touchstart": case "MSPointerDown": mouseEvents = ["mousemove", "mousedown"]; this._lastTouchData = getMouseCoordinates(first); this._lastTouchData.time = new Date(); break; case "touchmove": case "MSPointerMove": mouseEvents = ["mousemove"]; break; case "touchend": case "MSPointerUp": mouseEvents = (this._lastTouchEventType === "touchstart" || this._lastTouchEventType === "MSPointerDown") ? ["mouseup", "click"] : ["mouseup"]; break; default: return; } if (touches && touches.length > 1) return; touchCurrentCoordinates = getMouseCoordinates(first); touchCurrentCoordinates.time = new Date(); try { var dy = touchCurrentCoordinates.y - this._lastTouchData.y; var dx = touchCurrentCoordinates.x - this._lastTouchData.x; var dt = touchCurrentCoordinates.time - this._lastTouchData.time; if (Math.abs(dy) > 15 && (!!this._lastTouchData.scroll || dt < 200)) { //this._lastTouchData.y = touchCurrentCoordinates.y; this._lastTouchData.scroll = true; var win = window.parent || window; if (win && win.scrollBy) win.scrollBy(0, -dy); } } catch (e) { }; this._lastTouchEventType = ev.type; if (!!this._lastTouchData.scroll && this.zoomEnabled) { if (this.isDrag) this.resetOverlayedCanvas(); this.isDrag = false; return; } for (var i = 0; i < mouseEvents.length; i++) { var type = mouseEvents[i]; var simulatedEvent = document.createEvent("MouseEvent"); simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0, null); first.target.dispatchEvent(simulatedEvent); if (ev.preventManipulation) { //alert("preventManipulation"); ev.preventManipulation(); } if (ev.preventDefault) { //alert("preventDefault"); ev.preventDefault(); } } } Chart.prototype._dispatchRangeEvent = function (eventName, triggerSource) { var eventParameter = {}; eventParameter.chart = this._publicChartReference; eventParameter.type = eventName; eventParameter.trigger = triggerSource; var axes = []; if (this.axisX) axes.push("axisX"); if (this.axisY) axes.push("axisY"); if (this.axisY2) axes.push("axisY2"); for (var i = 0; i < axes.length; i++) { eventParameter[axes[i]] = { viewportMinimum: this[axes[i]].sessionVariables.newViewportMinimum, viewportMaximum: this[axes[i]].sessionVariables.newViewportMaximum } } this.dispatchEvent(eventName, eventParameter, this._publicChartReference); } Chart.prototype._mouseEventHandler = function (ev) { if (!this.interactivityEnabled) return; if (this._ignoreNextEvent) { this._ignoreNextEvent = false; return; } // stop panning and zooming so we can draw if (ev.preventManipulation) { //alert("preventManipulation"); ev.preventManipulation(); } // we are handling this event if (ev.preventDefault) { //alert("preventDefault"); ev.preventDefault(); } //IE8- uses srcElement instead of target. So instead of checking this condition everytime, its better to create a reference called target. if (typeof (ev.target) === "undefined" && ev.srcElement) ev.target = ev.srcElement; //console.log(ev.type); var xy = getMouseCoordinates(ev); var type = ev.type; var eventParam; var rightclick; if (!ev) var e = window.event; if (ev.which) rightclick = (ev.which == 3); else if (ev.button) rightclick = (ev.button == 2); //window.console.log(type + " --> x: " + xy.x + "; y:" + xy.y); //if (type === "mouseout") { // this._toolTip.hide(); //} if (isDebugMode && window.console) { window.console.log(type + " --> x: " + xy.x + "; y:" + xy.y); if (rightclick) window.console.log(ev.which); if (type === "mouseup") window.console.log("mouseup"); } if (rightclick) return; //if (this.plotInfo.axisPlacement === "xySwapped") { // //var temp = xy.x; // //xy.x = xy.y; // //xy.y = temp; // xy = {x: xy.y, y: xy.x}; //} if (Chart.capturedEventParam) { eventParam = Chart.capturedEventParam; if (type === "mouseup") { Chart.capturedEventParam = null; if (eventParam.chart.overlaidCanvas.releaseCapture) eventParam.chart.overlaidCanvas.releaseCapture(); else document.body.removeEventListener("mouseup", eventParam.chart._mouseEventHandler, false); } if (eventParam.hasOwnProperty(type)) eventParam[type].call(eventParam.context, xy.x, xy.y); } else if (this._events) { for (var i = 0; i < this._events.length; i++) { if (!this._events[i].hasOwnProperty(type)) continue; eventParam = this._events[i]; var bounds = eventParam.bounds; if (xy.x >= bounds.x1 && xy.x <= bounds.x2 && xy.y >= bounds.y1 && xy.y <= bounds.y2) { eventParam[type].call(eventParam.context, xy.x, xy.y); if (type === "mousedown" && eventParam.capture === true) { Chart.capturedEventParam = eventParam; if (this.overlaidCanvas.setCapture) this.overlaidCanvas.setCapture(); else { document.body.addEventListener("mouseup", this._mouseEventHandler, false); //addEvent(document.body, "mouseup", this._mouseEventHandler); } } else if (type === "mouseup") { if (eventParam.chart.overlaidCanvas.releaseCapture) eventParam.chart.overlaidCanvas.releaseCapture(); else document.body.removeEventListener("mouseup", this._mouseEventHandler, false); } break; } else eventParam = null; } if (eventParam && eventParam.cursor) { ev.target.style.cursor = eventParam.cursor; } else ev.target.style.cursor = this._defaultCursor; //eventParam = } if (this._toolTip && this._toolTip.enabled) { var plotArea = this.plotArea; if (xy.x < plotArea.x1 || xy.x > plotArea.x2 || xy.y < plotArea.y1 || xy.y > plotArea.y2) this._toolTip.hide(); } if ((!this.isDrag || !this.zoomEnabled) && this._eventManager) { this._eventManager.mouseEventHandler(ev); //this._updateToolTip(ev.x, ev.y); } //if (this._toolTip.enabled) // this._toolTip.mouseMoveHandler(ev.x, ev.y); } Chart.prototype._plotAreaMouseDown = function (x, y) { this.isDrag = true; this.dragStartPoint = { x: x, y: y }; } Chart.prototype._plotAreaMouseUp = function (x, y) { if (this.plotInfo.axisPlacement === "normal" || this.plotInfo.axisPlacement === "xySwapped") { if (this.isDrag) { var dragDelta = 0, dragDeltaPY = y - this.dragStartPoint.y, dragDeltaPX = x - this.dragStartPoint.x, zoomPX = this.zoomType.indexOf("x") >= 0, //Whether to zoom horizontally zoomPY = this.zoomType.indexOf("y") >= 0, //Whether to zoom vertically reRender = false; this.resetOverlayedCanvas(); if (this.plotInfo.axisPlacement === "xySwapped") { var temp = zoomPY; zoomPY = zoomPX; zoomPX = temp; } if (this.panEnabled || this.zoomEnabled) { if (this.panEnabled) { var overflow = 0; for (var i = 0; i < this._axes.length; i++) { var axis = this._axes[i]; if (axis.viewportMinimum < axis.minimum) { overflow = axis.minimum - axis.viewportMinimum; axis.sessionVariables.newViewportMinimum = axis.viewportMinimum + overflow; axis.sessionVariables.newViewportMaximum = axis.viewportMaximum + overflow; reRender = true; } else if (axis.viewportMaximum > axis.maximum) { overflow = axis.viewportMaximum - axis.maximum; axis.sessionVariables.newViewportMinimum = axis.viewportMinimum - overflow; axis.sessionVariables.newViewportMaximum = axis.viewportMaximum - overflow; reRender = true; } } } else if (((!zoomPX || Math.abs(dragDeltaPX) > 2) && (!zoomPY || Math.abs(dragDeltaPY) > 2)) && this.zoomEnabled) { if (!this.dragStartPoint) return; var selectedRegion = { x1: zoomPX ? this.dragStartPoint.x : this.plotArea.x1, y1: zoomPY ? this.dragStartPoint.y : this.plotArea.y1, x2: zoomPX ? x : this.plotArea.x2, y2: zoomPY ? y : this.plotArea.y2 }; if (Math.abs(selectedRegion.x1 - selectedRegion.x2) > 2 && Math.abs(selectedRegion.y1 - selectedRegion.y2) > 2) { if (this._zoomPanToSelectedRegion(selectedRegion.x1, selectedRegion.y1, selectedRegion.x2, selectedRegion.y2)) { reRender = true; } } } if (reRender) { this._ignoreNextEvent = true;//Required so that click event doesn't fire after zooming into a section of the chart. this._dispatchRangeEvent("rangeChanging", "zoom"); this.render(); this._dispatchRangeEvent("rangeChanged", "zoom"); if (reRender && this.zoomEnabled && this._zoomButton.style.display === "none") { show(this._zoomButton, this._resetButton); setButtonState(this, this._zoomButton, "pan"); setButtonState(this, this._resetButton, "reset"); } } } } } this.isDrag = false; } Chart.prototype._plotAreaMouseMove = function (x, y) { if (this.isDrag && this.plotInfo.axisPlacement !== "none") { var dragDeltaPX = 0, dragDeltaPY = 0, alpha = null, selectedRegion = null, zoomPX = this.zoomType.indexOf("x") >= 0, //Whether to zoom horizontally zoomPY = this.zoomType.indexOf("y") >= 0, //Whether to zoom vertically _this = this; if (this.plotInfo.axisPlacement === "xySwapped") { var temp = zoomPY; zoomPY = zoomPX; zoomPX = temp; } dragDeltaPX = this.dragStartPoint.x - x; dragDeltaPY = this.dragStartPoint.y - y; if (Math.abs(dragDeltaPX) > 2 && Math.abs(dragDeltaPX) < 8 && (this.panEnabled || this.zoomEnabled)) { this._toolTip.hide(); } else if (!this.panEnabled && !this.zoomEnabled) { this._toolTip.mouseMoveHandler(x, y); } if (((!zoomPX || Math.abs(dragDeltaPX) > 2) || (!zoomPY || Math.abs(dragDeltaPY) > 2)) && (this.panEnabled || this.zoomEnabled)) { if (this.panEnabled) { selectedRegion = { x1: zoomPX ? this.plotArea.x1 + dragDeltaPX : this.plotArea.x1, y1: zoomPY ? this.plotArea.y1 + dragDeltaPY : this.plotArea.y1, x2: zoomPX ? this.plotArea.x2 + dragDeltaPX : this.plotArea.x2, y2: zoomPY ? this.plotArea.y2 + dragDeltaPY : this.plotArea.y2 }; if (this._zoomPanToSelectedRegion(selectedRegion.x1, selectedRegion.y1, selectedRegion.x2, selectedRegion.y2, true)) { this._dispatchRangeEvent("rangeChanging", "pan"); this.render(); this._dispatchRangeEvent("rangeChanged", "pan"); this.dragStartPoint.x = x; this.dragStartPoint.y = y; //clearTimeout(this._panTimerId); //this._panTimerId = setTimeout(function () { // _this._dispatchRangeEvent("rangeChanging", "pan"); // _this.render(); // _this._dispatchRangeEvent("rangeChanged", "pan"); //}, 0); } } else if (this.zoomEnabled) { this.resetOverlayedCanvas(); alpha = this.overlaidCanvasCtx.globalAlpha; this.overlaidCanvasCtx.globalAlpha = .7; this.overlaidCanvasCtx.fillStyle = "#A0ABB8"; var rect = { x1: zoomPX ? this.dragStartPoint.x : this.plotArea.x1, y1: zoomPY ? this.dragStartPoint.y : this.plotArea.y1, x2: zoomPX ? x - this.dragStartPoint.x : this.plotArea.x2 - this.plotArea.x1, y2: zoomPY ? y - this.dragStartPoint.y : this.plotArea.y2 - this.plotArea.y1 }; this.overlaidCanvasCtx.fillRect(rect.x1, rect.y1, rect.x2, rect.y2); this.overlaidCanvasCtx.globalAlpha = alpha; } } } else this._toolTip.mouseMoveHandler(x, y); } //#endregion Events //Sets the viewport range of Axis based on the given rect bounds (pixels). Also limits the zooming/panning based on axis bounds. Returns a boolean to indicate whether it was succesful or not based on the selected region. Chart.prototype._zoomPanToSelectedRegion = function (px1, py1, px2, py2, keepAxisIndependent) { keepAxisIndependent = keepAxisIndependent || false; var zoomPX = this.zoomType.indexOf("x") >= 0, //Whether to zoom horizontally zoomPY = this.zoomType.indexOf("y") >= 0, //Whether to zoom vertically validRegion = false; var axes = [], axesWithValidRange = []; if (this.axisX && zoomPX) axes.push(this.axisX); if (this.axisY && zoomPY) axes.push(this.axisY); if (this.axisY2 && zoomPY) axes.push(this.axisY2); var params = []; for (var i = 0; i < axes.length; i++) { var axis = axes[i]; //var range = Math.abs(axis.viewportMaximum - axis.viewportMinimum); var val1 = axis.convertPixelToValue({ x: px1, y: py1 }); var val2 = axis.convertPixelToValue({ x: px2, y: py2 }); if (val1 > val2) { var temp = val2; val2 = val1; val1 = temp; } if (isFinite(axis.dataInfo.minDiff)) { if (!(Math.abs(val2 - val1) < 3 * Math.abs(axis.dataInfo.minDiff) || (val1 < axis.minimum) || (val2 > axis.maximum))) { axesWithValidRange.push(axis); params.push({ val1: val1, val2: val2 }); validRegion = true; } else if (!keepAxisIndependent) { validRegion = false; break; } } } if (validRegion) { for (var i = 0; i < axesWithValidRange.length; i++) { var axis = axesWithValidRange[i]; var param = params[i]; axis.setViewPortRange(param.val1, param.val2); } } return validRegion; } Chart.prototype.preparePlotArea = function () { var plotArea = this.plotArea; var yAxis = this.axisY ? this.axisY : this.axisY2; if (!isCanvasSupported && (plotArea.x1 > 0 || plotArea.y1 > 0)) { plotArea.ctx.translate(plotArea.x1, plotArea.y1); } if (this.axisX && yAxis) { plotArea.x1 = this.axisX.lineCoordinates.x1 < this.axisX.lineCoordinates.x2 ? this.axisX.lineCoordinates.x1 : yAxis.lineCoordinates.x1; plotArea.y1 = (this.axisX.lineCoordinates.y1 < yAxis.lineCoordinates.y1 ? this.axisX.lineCoordinates.y1 : yAxis.lineCoordinates.y1); plotArea.x2 = (this.axisX.lineCoordinates.x2 > yAxis.lineCoordinates.x2 ? this.axisX.lineCoordinates.x2 : yAxis.lineCoordinates.x2); plotArea.y2 = this.axisX.lineCoordinates.y2 > this.axisX.lineCoordinates.y1 ? this.axisX.lineCoordinates.y2 : yAxis.lineCoordinates.y2; plotArea.width = plotArea.x2 - plotArea.x1; plotArea.height = plotArea.y2 - plotArea.y1; //plotArea = { x1: x1, y1: y1, x2: x2, y2: y2, width: x2 - x1, height: y2 - y1 }; } else { //ToDo: @sunil var freeSpace = this.layoutManager.getFreeSpace(); plotArea.x1 = freeSpace.x1; plotArea.x2 = freeSpace.x2; plotArea.y1 = freeSpace.y1; plotArea.y2 = freeSpace.y2; plotArea.width = freeSpace.width; plotArea.height = freeSpace.height; } if (!isCanvasSupported) { plotArea.canvas.width = plotArea.width; plotArea.canvas.height = plotArea.height; plotArea.canvas.style.left = plotArea.x1 + "px"; plotArea.canvas.style.top = plotArea.y1 + "px"; if (plotArea.x1 > 0 || plotArea.y1 > 0) { plotArea.ctx.translate(-plotArea.x1, -plotArea.y1); } } plotArea.layoutManager = new LayoutManager(plotArea.x1, plotArea.y1, plotArea.x2, plotArea.y2, 2); } Chart.prototype.getPixelCoordinatesOnPlotArea = function (x, y) { return { x: this.axisX.getPixelCoordinatesOnAxis(x).x, y: this.axisY.getPixelCoordinatesOnAxis(y).y } //return { x: 5, y: 10 }; } //#region Render Methods Chart.prototype.renderIndexLabels = function (targetCtx) { var ctx = targetCtx || this.plotArea.ctx; var plotArea = this.plotArea; var mid = 0; var yMinLimit = 0; var yMaxLimit = 0; var xMinLimit = 0; var xMaxLimit = 0; var marginX = 0, marginY = 0; // Margin between label and dataPoint / PlotArea var offSetX = 0, offSetY = 0; // Distance to offSet textBlock (top) from dataPoint inorder to position it var visibleWidth = 0; var visibleHeight = 0; for (var i = 0; i < this._indexLabels.length; i++) { var indexLabel = this._indexLabels[i]; var chartTypeLower = indexLabel.chartType.toLowerCase(); var x, y, angle; var fontColor = getProperty("indexLabelFontColor", indexLabel.dataPoint, indexLabel.dataSeries); var fontSize = getProperty("indexLabelFontSize", indexLabel.dataPoint, indexLabel.dataSeries); var fontFamily = getProperty("indexLabelFontFamily", indexLabel.dataPoint, indexLabel.dataSeries); var fontStyle = getProperty("indexLabelFontStyle", indexLabel.dataPoint, indexLabel.dataSeries); var fontWeight = getProperty("indexLabelFontWeight", indexLabel.dataPoint, indexLabel.dataSeries); var backgroundColor = getProperty("indexLabelBackgroundColor", indexLabel.dataPoint, indexLabel.dataSeries); var maxWidth = getProperty("indexLabelMaxWidth", indexLabel.dataPoint, indexLabel.dataSeries); var indexLabelWrap = getProperty("indexLabelWrap", indexLabel.dataPoint, indexLabel.dataSeries); var percentAndTotal = { percent: null, total: null }; var formatterParameter = null; if (indexLabel.dataSeries.type.indexOf("stacked") >= 0 || indexLabel.dataSeries.type === "pie" || indexLabel.dataSeries.type === "doughnut") percentAndTotal = this.getPercentAndTotal(indexLabel.dataSeries, indexLabel.dataPoint); if (indexLabel.dataSeries.indexLabelFormatter || indexLabel.dataPoint.indexLabelFormatter) formatterParameter = { chart: this._options, dataSeries: indexLabel.dataSeries, dataPoint: indexLabel.dataPoint, index: indexLabel.indexKeyword, total: percentAndTotal.total, percent: percentAndTotal.percent }; var indexLabelText = indexLabel.dataPoint.indexLabelFormatter ? indexLabel.dataPoint.indexLabelFormatter(formatterParameter) : indexLabel.dataPoint.indexLabel ? this.replaceKeywordsWithValue(indexLabel.dataPoint.indexLabel, indexLabel.dataPoint, indexLabel.dataSeries, null, indexLabel.indexKeyword) : indexLabel.dataSeries.indexLabelFormatter ? indexLabel.dataSeries.indexLabelFormatter(formatterParameter) : indexLabel.dataSeries.indexLabel ? this.replaceKeywordsWithValue(indexLabel.dataSeries.indexLabel, indexLabel.dataPoint, indexLabel.dataSeries, null, indexLabel.indexKeyword) : null; if (indexLabelText === null || indexLabelText === "") continue; var placement = getProperty("indexLabelPlacement", indexLabel.dataPoint, indexLabel.dataSeries); var orientation = getProperty("indexLabelOrientation", indexLabel.dataPoint, indexLabel.dataSeries); var angle = 0; var direction = indexLabel.direction; // +1 for above the point and -1 for below the point var axisX = indexLabel.dataSeries.axisX; var axisY = indexLabel.dataSeries.axisY; var textBlock = new TextBlock(ctx, { x: 0, y: 0, maxWidth: maxWidth ? maxWidth : this.width * .5, maxHeight: indexLabelWrap ? fontSize * 5 : fontSize * 1.5, angle: orientation === "horizontal" ? 0 : -90, text: indexLabelText, padding: 0, backgroundColor: backgroundColor, horizontalAlign: "left",//left, center, right fontSize: fontSize,//in pixels fontFamily: fontFamily, fontWeight: fontWeight, //normal, bold, bolder, lighter, fontColor: fontColor, fontStyle: fontStyle, // normal, italic, oblique textBaseline: "top" }); var textSize = textBlock.measureText(); //if (indexLabel.dataPoint.x < axisX.viewportMinimum || indexLabel.dataPoint.x > axisX.viewportMaximum || indexLabel.dataPoint.y < axisY.viewportMinimum || indexLabel.dataPoint.y > axisY.viewportMaximum) // continue; if (chartTypeLower.indexOf("line") >= 0 || chartTypeLower.indexOf("area") >= 0 || chartTypeLower.indexOf("bubble") >= 0 || chartTypeLower.indexOf("scatter") >= 0) { if (indexLabel.dataPoint.x < axisX.viewportMinimum || indexLabel.dataPoint.x > axisX.viewportMaximum || indexLabel.dataPoint.y < axisY.viewportMinimum || indexLabel.dataPoint.y > axisY.viewportMaximum) continue; } else { if (indexLabel.dataPoint.x < axisX.viewportMinimum || indexLabel.dataPoint.x > axisX.viewportMaximum) continue; } marginY = 2; marginX = 2; if (orientation === "horizontal") { visibleWidth = textBlock.width; visibleHeight = textBlock.height; } else { visibleHeight = textBlock.width; visibleWidth = textBlock.height; } if (this.plotInfo.axisPlacement === "normal") { if (chartTypeLower.indexOf("line") >= 0 || chartTypeLower.indexOf("area") >= 0) { placement = "auto"; marginY = 4; } else if (chartTypeLower.indexOf("stacked") >= 0) { if (placement === "auto") placement = "inside"; } else if (chartTypeLower === "bubble" || chartTypeLower === "scatter") { placement = "inside"; } x = indexLabel.point.x - visibleWidth / 2; if (placement !== "inside") { //outside or auto yMinLimit = plotArea.y1; yMaxLimit = plotArea.y2; if (direction > 0) { y = indexLabel.point.y - visibleHeight - marginY; if (y < yMinLimit) { if (placement === "auto") { y = Math.max(indexLabel.point.y, yMinLimit) + marginY; } else { y = yMinLimit + marginY; } } } else { y = indexLabel.point.y + marginY; if (y > yMaxLimit - visibleHeight - marginY) { if (placement === "auto") { y = Math.min(indexLabel.point.y, yMaxLimit) - visibleHeight - marginY; } else { y = yMaxLimit - visibleHeight - marginY; } } } } else { yMinLimit = Math.max(indexLabel.bounds.y1, plotArea.y1); yMaxLimit = Math.min(indexLabel.bounds.y2, plotArea.y2); if (chartTypeLower.indexOf("range") >= 0) { if (direction > 0) mid = Math.max(indexLabel.bounds.y1, plotArea.y1) + visibleHeight / 2 + marginY; else mid = Math.min(indexLabel.bounds.y2, plotArea.y2) - visibleHeight / 2 - marginY; } else mid = (Math.max(indexLabel.bounds.y1, plotArea.y1) + Math.min(indexLabel.bounds.y2, plotArea.y2)) / 2 if (direction > 0) { y = Math.max(indexLabel.point.y, mid) - visibleHeight / 2; if (y < yMinLimit && (chartTypeLower === "bubble" || chartTypeLower === "scatter")) { y = Math.max(indexLabel.point.y - visibleHeight - marginY, plotArea.y1 + marginY); } } else { y = Math.min(indexLabel.point.y, mid) - visibleHeight / 2; if (y > yMaxLimit - visibleHeight - marginY && (chartTypeLower === "bubble" || chartTypeLower === "scatter")) { y = Math.min(indexLabel.point.y + marginY, plotArea.y2 - visibleHeight - marginY); } } // Make Sure that it does not overlap the axis line y = Math.min(y, yMaxLimit - visibleHeight); } } else { if (chartTypeLower.indexOf("line") >= 0 || chartTypeLower.indexOf("area") >= 0 || chartTypeLower.indexOf("scatter") >= 0) { placement = "auto"; marginX = 4; } else if (chartTypeLower.indexOf("stacked") >= 0) { if (placement === "auto") placement = "inside"; } else if (chartTypeLower === "bubble") { placement = "inside"; } y = indexLabel.point.y - visibleHeight / 2; if (placement !== "inside") { //outside or auto xMinLimit = plotArea.x1; xMaxLimit = plotArea.x2; if (direction < 0) { x = indexLabel.point.x - visibleWidth - marginX; if (x < xMinLimit) { if (placement === "auto") { x = Math.max(indexLabel.point.x, xMinLimit) + marginX; } else { x = xMinLimit + marginX; } } } else { x = indexLabel.point.x + marginX; if (x > xMaxLimit - visibleWidth - marginX) { if (placement === "auto") { x = Math.min(indexLabel.point.x, xMaxLimit) - visibleWidth - marginX; } else { x = xMaxLimit - visibleWidth - marginX; } } } } else { xMinLimit = Math.max(indexLabel.bounds.x1, plotArea.x1); xMaxLimit = Math.min(indexLabel.bounds.x2, plotArea.x2); if (chartTypeLower.indexOf("range") >= 0) { if (direction < 0) mid = Math.max(indexLabel.bounds.x1, plotArea.x1) + visibleWidth / 2 + marginX; else mid = Math.min(indexLabel.bounds.x2, plotArea.x2) - visibleWidth / 2 - marginX; } else var mid = (Math.max(indexLabel.bounds.x1, plotArea.x1) + Math.min(indexLabel.bounds.x2, plotArea.x2)) / 2; if (direction < 0) { x = Math.max(indexLabel.point.x, mid) - visibleWidth / 2; //if (y < xMinLimit) { // y = Math.max(indexLabel.point.y - visibleHeight - marginY, plotArea.y1 + marginY); //} } else { x = Math.min(indexLabel.point.x, mid) - visibleWidth / 2; //if (y > xMaxLimit - visibleHeight - marginY) { // y = Math.min(indexLabel.point.y + marginY, plotArea.y2 - visibleHeight - marginY); //} } // Make Sure that it does not overlap the axis line x = Math.max(x, xMinLimit); } } if (orientation === "vertical") { y += visibleHeight; } textBlock.x = x; textBlock.y = y; //console.log(textBlock.text + ": " + textBlock.x + "; " + textBlock.y); textBlock.render(true); } //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.fadeInAnimation, easingFunction: AnimationHelper.easing.easeInQuad, animationBase: 0, startTimePercent: .7 }; return animationInfo; } Chart.prototype.renderLine = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var ghostCtx = this._eventManager.ghostCtx; //var ghostCtx = this.overlaidCanvasCtx; ctx.save(); var plotArea = this.plotArea; ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); var markers = []; for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; ctx.lineWidth = dataSeries.lineThickness; var dataPoints = dataSeries.dataPoints; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(dataSeries.lineDashType, dataSeries.lineThickness)); } var seriesId = dataSeries.id; this._eventManager.objectMap[seriesId] = { objectType: "dataSeries", dataSeriesIndex: dataSeriesIndex }; var hexColor = intToHexColorString(seriesId); ghostCtx.strokeStyle = hexColor; //ghostCtx.lineWidth = dataSeries.lineThickness; ghostCtx.lineWidth = dataSeries.lineThickness > 0 ? Math.max(dataSeries.lineThickness, 4) : 0; var colorSet = dataSeries._colorSet; var color = colorSet[0]; ctx.strokeStyle = color; var isFirstDataPointInPlotArea = true; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number back and forth. //if (!dataSeries._options.markerSize && dataSeries.dataPoints.length < 1000) // dataSeries.markerSize = 8; ctx.beginPath(); if (dataPoints.length > 0) { //var xy = this.getPixelCoordinatesOnPlotArea(dataPoints[0].x, dataPoints[0].y); //dataSeries.noDataPointsInPlotArea = 0 var prevDataNull = false; for (i = 0; i < dataPoints.length; i++) { dataPointX = dataPoints[i].x.getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) continue; //if (!isFinite(dataPoints[i].y)) // continue; if (typeof (dataPoints[i].y) !== "number") { if (i > 0) {// if first dataPoint is null then no need to call stroke method ctx.stroke(); if (isCanvasSupported) { ghostCtx.stroke(); } } prevDataNull = true; continue; } x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x, y1: y }; //dataSeries.noDataPointsInPlotArea++; if (isFirstDataPointInPlotArea || prevDataNull) { ctx.beginPath(); ctx.moveTo(x, y); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.moveTo(x, y); } isFirstDataPointInPlotArea = false; prevDataNull = false; } else { ctx.lineTo(x, y); if (isCanvasSupported) ghostCtx.lineTo(x, y); if (i % 500 == 0) { ctx.stroke(); ctx.beginPath(); ctx.moveTo(x, y); if (isCanvasSupported) { ghostCtx.stroke(); ghostCtx.beginPath(); ghostCtx.moveTo(x, y); } } } //Render Marker if (dataPoints[i].markerSize > 0 || dataSeries.markerSize > 0) { var markerProps = dataSeries.getMarkerProperties(i, x, y, ctx); markers.push(markerProps); //if (!dataSeries.maxWidthInX || markerProps.size > dataSeries.maxWidthInX) { // dataSeries.maxWidthInX = markerProps.size / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //} var markerColor = intToHexColorString(id); //window.console.log("index: " + i + "; id: " + id + "; hex: " + markerColor); if (isCanvasSupported) { markers.push({ x: x, y: y, ctx: ghostCtx, type: markerProps.type, size: markerProps.size, color: markerColor, borderColor: markerColor, borderThickness: markerProps.borderThickness }); } } if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "line", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: x, y: y }, direction: dataPoints[i].y >= 0 ? 1 : -1, color: color }); } } ctx.stroke(); if (isCanvasSupported) ghostCtx.stroke(); } } RenderHelper.drawMarkers(markers); ctx.restore(); ctx.beginPath(); if (isCanvasSupported) ghostCtx.beginPath(); //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xClipAnimation, easingFunction: AnimationHelper.easing.linear, animationBase: 0 }; return animationInfo; } Chart.prototype.renderStepLine = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var ghostCtx = this._eventManager.ghostCtx; //var ghostCtx = this.overlaidCanvasCtx; ctx.save(); var plotArea = this.plotArea; ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); var markers = []; for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; ctx.lineWidth = dataSeries.lineThickness; var dataPoints = dataSeries.dataPoints; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(dataSeries.lineDashType, dataSeries.lineThickness)); } var seriesId = dataSeries.id; this._eventManager.objectMap[seriesId] = { objectType: "dataSeries", dataSeriesIndex: dataSeriesIndex }; var hexColor = intToHexColorString(seriesId); ghostCtx.strokeStyle = hexColor; //ghostCtx.lineWidth = dataSeries.lineThickness; ghostCtx.lineWidth = dataSeries.lineThickness > 0 ? Math.max(dataSeries.lineThickness, 4) : 0; var colorSet = dataSeries._colorSet; var color = colorSet[0]; ctx.strokeStyle = color; var isFirstDataPointInPlotArea = true; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number back and forth. //if (!dataSeries._options.markerSize && dataSeries.dataPoints.length < 1000) // dataSeries.markerSize = 8; ctx.beginPath(); if (dataPoints.length > 0) { //var xy = this.getPixelCoordinatesOnPlotArea(dataPoints[0].x, dataPoints[0].y); //dataSeries.noDataPointsInPlotArea = 0 var prevDataNull = false; for (i = 0; i < dataPoints.length; i++) { dataPointX = dataPoints[i].getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) continue; //if (!isFinite(dataPoints[i].y)) // continue; if (typeof (dataPoints[i].y) !== "number") { if (i > 0) {// if first dataPoint is null then no need to call stroke method ctx.stroke(); if (isCanvasSupported) { ghostCtx.stroke(); } } prevDataNull = true; continue; } var prevY = y; x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x, y1: y }; //dataSeries.noDataPointsInPlotArea++; if (isFirstDataPointInPlotArea || prevDataNull) { ctx.beginPath(); ctx.moveTo(x, y); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.moveTo(x, y); } isFirstDataPointInPlotArea = false; prevDataNull = false; } else { ctx.lineTo(x, prevY); if (isCanvasSupported) ghostCtx.lineTo(x, prevY); ctx.lineTo(x, y); if (isCanvasSupported) ghostCtx.lineTo(x, y); if (i % 500 == 0) { ctx.stroke(); ctx.beginPath(); ctx.moveTo(x, y); if (isCanvasSupported) { ghostCtx.stroke(); ghostCtx.beginPath(); ghostCtx.moveTo(x, y); } } } //Render Marker if (dataPoints[i].markerSize > 0 || dataSeries.markerSize > 0) { var markerProps = dataSeries.getMarkerProperties(i, x, y, ctx); markers.push(markerProps); //if (!dataSeries.maxWidthInX || markerProps.size > dataSeries.maxWidthInX) { // dataSeries.maxWidthInX = markerProps.size / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //} var markerColor = intToHexColorString(id); //window.console.log("index: " + i + "; id: " + id + "; hex: " + markerColor); if (isCanvasSupported) { markers.push({ x: x, y: y, ctx: ghostCtx, type: markerProps.type, size: markerProps.size, color: markerColor, borderColor: markerColor, borderThickness: markerProps.borderThickness }); } } if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "stepLine", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: x, y: y }, direction: dataPoints[i].y >= 0 ? 1 : -1, color: color }); } } ctx.stroke(); if (isCanvasSupported) ghostCtx.stroke(); } } RenderHelper.drawMarkers(markers); ctx.restore(); ctx.beginPath(); if (isCanvasSupported) ghostCtx.beginPath(); //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xClipAnimation, easingFunction: AnimationHelper.easing.linear, animationBase: 0 }; return animationInfo; } function getBezierPoints(points, tension) { var bezierPoints = []; for (var i = 0; i < points.length; i++) { if (i == 0) { bezierPoints.push(points[0]); continue; } var i1, i2, pointIndex; pointIndex = i - 1; i1 = pointIndex === 0 ? 0 : pointIndex - 1; i2 = pointIndex === points.length - 1 ? pointIndex : pointIndex + 1; var drv1 = { x: (points[i2].x - points[i1].x) / tension, y: (points[i2].y - points[i1].y) / tension } var cp1 = { x: points[pointIndex].x + drv1.x / 3, y: points[pointIndex].y + drv1.y / 3 } bezierPoints[bezierPoints.length] = cp1; pointIndex = i; i1 = pointIndex === 0 ? 0 : pointIndex - 1; i2 = pointIndex === points.length - 1 ? pointIndex : pointIndex + 1; var drv2 = { x: (points[i2].x - points[i1].x) / tension, y: (points[i2].y - points[i1].y) / tension } var cp2 = { x: points[pointIndex].x - drv2.x / 3, y: points[pointIndex].y - drv2.y / 3 } bezierPoints[bezierPoints.length] = cp2; bezierPoints[bezierPoints.length] = points[i]; } return bezierPoints; } Chart.prototype.renderSpline = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var ghostCtx = this._eventManager.ghostCtx; ctx.save(); var plotArea = this.plotArea; ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); var markers = []; for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; ctx.lineWidth = dataSeries.lineThickness; var dataPoints = dataSeries.dataPoints; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(dataSeries.lineDashType, dataSeries.lineThickness)); } var seriesId = dataSeries.id; this._eventManager.objectMap[seriesId] = { objectType: "dataSeries", dataSeriesIndex: dataSeriesIndex }; var hexColor = intToHexColorString(seriesId); ghostCtx.strokeStyle = hexColor; //ghostCtx.lineWidth = dataSeries.lineThickness; ghostCtx.lineWidth = dataSeries.lineThickness > 0 ? Math.max(dataSeries.lineThickness, 4) : 0; var colorSet = dataSeries._colorSet; var color = colorSet[0]; ctx.strokeStyle = color; var isFirstDataPointInPlotArea = true; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number back and forth. //if (!dataSeries._options.markerSize && dataSeries.dataPoints.length < 1000) // dataSeries.markerSize = 8; var pixels = []; ctx.beginPath(); if (dataPoints.length > 0) { for (i = 0; i < dataPoints.length; i++) { dataPointX = dataPoints[i].getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) continue; //if (!isFinite(dataPoints[i].y)) // continue; if (typeof (dataPoints[i].y) !== "number") { if (i > 0) {// if first dataPoint is null then no need to call stroke method renderBezier(pixels); pixels = []; } continue; } x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x, y1: y }; pixels[pixels.length] = { x: x, y: y }; //Add Markers if (dataPoints[i].markerSize > 0 || dataSeries.markerSize > 0) { var markerProps = dataSeries.getMarkerProperties(i, x, y, ctx); markers.push(markerProps); //if (!dataSeries.maxWidthInX || markerProps.size > dataSeries.maxWidthInX) { // dataSeries.maxWidthInX = markerProps.size / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //} var markerColor = intToHexColorString(id); //window.console.log("index: " + i + "; id: " + id + "; hex: " + markerColor); if (isCanvasSupported) { markers.push({ x: x, y: y, ctx: ghostCtx, type: markerProps.type, size: markerProps.size, color: markerColor, borderColor: markerColor, borderThickness: markerProps.borderThickness }); } } //Add Labels if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "spline", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: x, y: y }, direction: dataPoints[i].y >= 0 ? 1 : -1, color: color }); } } } renderBezier(pixels); } RenderHelper.drawMarkers(markers); ctx.restore(); ctx.beginPath(); if (isCanvasSupported) ghostCtx.beginPath(); function renderBezier(pixels) { var bp = getBezierPoints(pixels, 2); if (bp.length > 0) { ctx.beginPath(); if (isCanvasSupported) ghostCtx.beginPath(); ctx.moveTo(bp[0].x, bp[0].y); if (isCanvasSupported) ghostCtx.moveTo(bp[0].x, bp[0].y); for (var i = 0; i < bp.length - 3; i += 3) { ctx.bezierCurveTo(bp[i + 1].x, bp[i + 1].y, bp[i + 2].x, bp[i + 2].y, bp[i + 3].x, bp[i + 3].y); if (isCanvasSupported) ghostCtx.bezierCurveTo(bp[i + 1].x, bp[i + 1].y, bp[i + 2].x, bp[i + 2].y, bp[i + 3].x, bp[i + 3].y); if (i > 0 && i % 3000 === 0) { ctx.stroke(); ctx.beginPath(); ctx.moveTo(bp[i + 3].x, bp[i + 3].y); if (isCanvasSupported) { ghostCtx.stroke(); ghostCtx.beginPath(); ghostCtx.moveTo(bp[i + 3].x, bp[i + 3].y); } } } ctx.stroke(); if (isCanvasSupported) ghostCtx.stroke(); } } //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xClipAnimation, easingFunction: AnimationHelper.easing.linear, animationBase: 0 }; return animationInfo; } var drawRect = function (ctx, x1, y1, x2, y2, color, borderThickness, borderColor, top, bottom, left, right, fillOpacity) { if (typeof (fillOpacity) === "undefined") fillOpacity = 1; borderThickness = borderThickness || 0; borderColor = borderColor || "black"; //alert("top"+ top + "bottom" + bottom + " lt" + left+ "rt" + right ) var a1 = x1, a2 = x2, b1 = y1, b2 = y2, edgeY, edgeX; if (x2 - x1 > 15 && y2 - y1 > 15) var bevelDepth = 8; else var bevelDepth = 0.35 * Math.min((x2 - x1), (y2 - y1)); //alert(a1 + "" + a2); var color2 = "rgba(255, 255, 255, .4)"; var color3 = "rgba(255, 255, 255, 0.1)"; //color1 = "rgba(" + r + "," + g + ", " + b + ",1)"; var color1 = color; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.save(); ctx.fillStyle = color1; ctx.globalAlpha = fillOpacity; ctx.fillRect(x1, y1, x2 - x1, y2 - y1); ctx.globalAlpha = 1; if (borderThickness > 0) { var offset = borderThickness % 2 === 0 ? 0 : .5; ctx.beginPath(); ctx.lineWidth = borderThickness; ctx.strokeStyle = borderColor; ctx.moveTo(x1, y1); ctx.rect(x1 - offset, y1 - offset, x2 - x1 + 2 * offset, y2 - y1 + 2 * offset); ctx.stroke(); } ctx.restore(); // ctx.beginPath(); if (top === true) { // alert(x1 + "" + x2 + " " + bevelDepth); ctx.save(); ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x1 + bevelDepth, y1 + bevelDepth); ctx.lineTo(x2 - bevelDepth, y1 + bevelDepth); ctx.lineTo(x2, y1); ctx.closePath(); var grd = ctx.createLinearGradient((x2 + x1) / 2, b1 + bevelDepth, (x2 + x1) / 2, b1); grd.addColorStop(0, color1); grd.addColorStop(1, color2); ctx.fillStyle = grd; ctx.fill(); // ctx.stroke(); ctx.restore(); } if (bottom === true) { ctx.save(); ctx.beginPath(); ctx.moveTo(x1, y2); ctx.lineTo(x1 + bevelDepth, y2 - bevelDepth); ctx.lineTo(x2 - bevelDepth, y2 - bevelDepth); ctx.lineTo(x2, y2); ctx.closePath(); var grd = ctx.createLinearGradient((x2 + x1) / 2, b2 - bevelDepth, (x2 + x1) / 2, b2); grd.addColorStop(0, color1); grd.addColorStop(1, color2); ctx.fillStyle = grd; // ctx.stroke(); ctx.fill(); ctx.restore(); } if (left === true) { // alert(x1) ctx.save(); ctx.beginPath(); ctx.moveTo(x1, y1) ctx.lineTo(x1 + bevelDepth, y1 + bevelDepth); ctx.lineTo(x1 + bevelDepth, y2 - bevelDepth); ctx.lineTo(x1, y2); ctx.closePath(); var grd = ctx.createLinearGradient(a1 + bevelDepth, (y2 + y1) / 2, a1, (y2 + y1) / 2); grd.addColorStop(0, color1); grd.addColorStop(1, color3); ctx.fillStyle = grd; ctx.fill(); // ctx.stroke(); ctx.restore(); } if (right === true) { ctx.save(); ctx.beginPath(); ctx.moveTo(x2, y1) ctx.lineTo(x2 - bevelDepth, y1 + bevelDepth); ctx.lineTo(x2 - bevelDepth, y2 - bevelDepth); ctx.lineTo(x2, y2); var grd = ctx.createLinearGradient(a2 - bevelDepth, (y2 + y1) / 2, a2, (y2 + y1) / 2); grd.addColorStop(0, color1); grd.addColorStop(1, color3); ctx.fillStyle = grd; grd.addColorStop(0, color1); grd.addColorStop(1, color3); ctx.fillStyle = grd; ctx.fill(); ctx.closePath(); // ctx.stroke(); ctx.restore(); } // } Chart.prototype.renderColumn = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var plotArea = this.plotArea; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number from dataTime everytime it is used. var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var maxBarWidth = this.dataPointMaxWidth ? this.dataPointMaxWidth : Math.min((this.width * .15), this.plotArea.width / plotUnit.plotType.totalDataSeries * .9) << 0; var xMinDiff = plotUnit.axisX.dataInfo.minDiff; if (!isFinite(xMinDiff)) { xMinDiff = Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum) * .3; } var barWidth = (((plotArea.width / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) / plotUnit.plotType.totalDataSeries * .9) << 0; if (barWidth > maxBarWidth) barWidth = maxBarWidth; else if (barWidth < 1) barWidth = 1; ctx.save(); if (isCanvasSupported) this._eventManager.ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { this._eventManager.ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); this._eventManager.ghostCtx.clip(); } //ctx.beginPath(); for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; // Reducing pixelPerUnit by 1 just to overcome any problems due to rounding off of pixels. //dataSeries.maxWidthInX = barWidth / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //var offsetX = barWidth * plotUnit.index << 0; if (dataPoints.length > 0) { //var xy = this.getPixelCoordinatesOnPlotArea(dataPoints[0].x, dataPoints[0].y); var bevelEnabled = (barWidth > 5) && dataSeries.bevelEnabled ? true : false; for (i = 0; i < dataPoints.length; i++) { dataPoints[i].getTime ? dataPointX = dataPoints[i].x.getTime() : dataPointX = dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoints[i].y) !== "number") continue; x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var x1 = x - (plotUnit.plotType.totalDataSeries * barWidth / 2) + ((plotUnit.previousDataSeriesCount + j) * barWidth) << 0; var x2 = x1 + barWidth << 0; var y1; var y2; if (dataPoints[i].y >= 0) { y1 = y; y2 = yZeroToPixel; if (y1 > y2) { var temp = y1; y1 = y2; y2 = y1; } } else { y2 = y; y1 = yZeroToPixel; if (y1 > y2) { var temp = y1; y1 = y2; y2 = y1; } } color = dataPoints[i].color ? dataPoints[i].color : dataSeries._colorSet[i % dataSeries._colorSet.length]; drawRect(ctx, x1, y1, x2, y2, color, 0, null, bevelEnabled && (dataPoints[i].y >= 0), (dataPoints[i].y < 0) && bevelEnabled, false, false, dataSeries.fillOpacity); //if (dataSeries.markerType && dataSeries.markerSize > 0) { // RenderHelper.drawMarker(x1 + (x2 - x1) / 2, y, ctx, dataSeries.markerType, dataSeries.markerSize, color, dataSeries.markerBorderColor, dataSeries.markerBorderThickness ? dataSeries.markerBorderThickness : 1); //} var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x1, y1: y1, x2: x2, y2: y2 }; color = intToHexColorString(id); if (isCanvasSupported) drawRect(this._eventManager.ghostCtx, x1, y1, x2, y2, color, 0, null, false, false, false, false); if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "column", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: x1 + (x2 - x1) / 2, y: dataPoints[i].y >= 0 ? y1 : y2 }, direction: dataPoints[i].y >= 0 ? 1 : -1, bounds: { x1: x1, y1: Math.min(y1, y2), x2: x2, y2: Math.max(y1, y2) }, color: color }); } } } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationBase = Math.min(yZeroToPixel, plotUnit.axisY.boundingRect.y2); var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.yScaleAnimation, easingFunction: AnimationHelper.easing.easeOutQuart, animationBase: animationBase }; return animationInfo; } Chart.prototype.renderStackedColumn = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var plotArea = this.plotArea; var offsetPositiveY = []; var offsetNegativeY = []; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number everytime it is accessed. //var yZeroToPixel = (axisYProps.y2 - axisYProps.height / rangeY * Math.abs(0 - plotUnit.axisY.viewportMinimum) + .5) << 0; var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var maxBarWidth = this.dataPointMaxWidth ? this.dataPointMaxWidth : this.width * .15 << 0; var xMinDiff = plotUnit.axisX.dataInfo.minDiff; if (!isFinite(xMinDiff)) { xMinDiff = Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum) * .3; } var barWidth = (((plotArea.width / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) / plotUnit.plotType.plotUnits.length * .9) << 0; if (barWidth > maxBarWidth) barWidth = maxBarWidth; else if (barWidth < 1) barWidth = 1; ctx.save(); if (isCanvasSupported) this._eventManager.ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { this._eventManager.ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); this._eventManager.ghostCtx.clip(); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; // Reducing pixelPerUnit by 1 just to overcome any problems due to rounding off of pixels. //dataSeries.maxWidthInX = barWidth / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); if (dataPoints.length > 0) { //var xy = this.getPixelCoordinatesOnPlotArea(dataPoints[0].x, dataPoints[0].y); var bevelEnabled = (barWidth > 5) && dataSeries.bevelEnabled ? true : false; ctx.strokeStyle = "#4572A7 "; for (i = 0; i < dataPoints.length; i++) { dataPointX = dataPoints[i].x.getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoints[i].y) !== "number") continue; x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum)); var x1 = x - (plotUnit.plotType.plotUnits.length * barWidth / 2) + (plotUnit.index * barWidth) << 0; var x2 = x1 + barWidth << 0; var y1; var y2; if (dataPoints[i].y >= 0) { var offset = offsetPositiveY[dataPointX] ? offsetPositiveY[dataPointX] : 0; y1 = y - offset; y2 = yZeroToPixel - offset; offsetPositiveY[dataPointX] = offset + (y2 - y1); } else { var offset = offsetNegativeY[dataPointX] ? offsetNegativeY[dataPointX] : 0; y2 = y + offset; y1 = yZeroToPixel + offset; offsetNegativeY[dataPointX] = offset + (y2 - y1); } color = dataPoints[i].color ? dataPoints[i].color : dataSeries._colorSet[i % dataSeries._colorSet.length]; drawRect(ctx, x1, y1, x2, y2, color, 0, null, bevelEnabled && (dataPoints[i].y >= 0), (dataPoints[i].y < 0) && bevelEnabled, false, false, dataSeries.fillOpacity); //if (dataSeries.markerType && dataSeries.markerSize > 0) { // RenderHelper.drawMarker(x1 + (x2 - x1)/2, y1, ctx, dataSeries.markerType, dataSeries.markerSize, color, dataSeries.markerBorderColor, dataSeries.markerBorderThickness ? dataSeries.markerBorderThickness : 1); //} var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x1, y1: y1, x2: x2, y2: y2 }; color = intToHexColorString(id); if (isCanvasSupported) drawRect(this._eventManager.ghostCtx, x1, y1, x2, y2, color, 0, null, false, false, false, false); if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "stackedColumn", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: x, y: dataPoints[i].y >= 0 ? y1 : y2 }, direction: dataPoints[i].y >= 0 ? 1 : -1, bounds: { x1: x1, y1: Math.min(y1, y2), x2: x2, y2: Math.max(y1, y2) }, color: color }); } } } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationBase = Math.min(yZeroToPixel, plotUnit.axisY.boundingRect.y2); var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.yScaleAnimation, easingFunction: AnimationHelper.easing.easeOutQuart, animationBase: animationBase }; return animationInfo; } Chart.prototype.renderStackedColumn100 = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var plotArea = this.plotArea; var offsetPositiveY = []; var offsetNegativeY = []; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number everytime it is accessed. //var yZeroToPixel = (axisYProps.y2 - axisYProps.height / rangeY * Math.abs(0 - plotUnit.axisY.viewportMinimum) + .5) << 0; var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var maxBarWidth = this.dataPointMaxWidth ? this.dataPointMaxWidth : this.width * .15 << 0; var xMinDiff = plotUnit.axisX.dataInfo.minDiff; if (!isFinite(xMinDiff)) { xMinDiff = Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum) * .3; } var barWidth = (((plotArea.width / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) / plotUnit.plotType.plotUnits.length * .9) << 0; if (barWidth > maxBarWidth) barWidth = maxBarWidth; else if (barWidth < 1) barWidth = 1; ctx.save(); if (isCanvasSupported) this._eventManager.ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { this._eventManager.ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); this._eventManager.ghostCtx.clip(); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; //dataSeries.maxWidthInX = barWidth / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); if (dataPoints.length > 0) { //var xy = this.getPixelCoordinatesOnPlotArea(dataPoints[0].x, dataPoints[0].y); var bevelEnabled = (barWidth > 5) && dataSeries.bevelEnabled ? true : false; //ctx.strokeStyle = "#4572A7 "; for (i = 0; i < dataPoints.length; i++) { dataPointX = dataPoints[i].x.getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoints[i].y) !== "number") continue; x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; var yPercent; if (plotUnit.dataPointYSums[dataPointX] !== 0) yPercent = dataPoints[i].y / plotUnit.dataPointYSums[dataPointX] * 100; else yPercent = 0; //y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (yPercent - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (yPercent - plotUnit.axisY.conversionParameters.minimum)); var x1 = x - (plotUnit.plotType.plotUnits.length * barWidth / 2) + (plotUnit.index * barWidth) << 0; var x2 = x1 + barWidth << 0; var y1; var y2; if (dataPoints[i].y >= 0) { var offset = offsetPositiveY[dataPointX] ? offsetPositiveY[dataPointX] : 0; y1 = y - offset; y2 = yZeroToPixel - offset; offsetPositiveY[dataPointX] = offset + (y2 - y1); } else { var offset = offsetNegativeY[dataPointX] ? offsetNegativeY[dataPointX] : 0; y2 = y + offset; y1 = yZeroToPixel + offset; offsetNegativeY[dataPointX] = offset + (y2 - y1); } color = dataPoints[i].color ? dataPoints[i].color : dataSeries._colorSet[i % dataSeries._colorSet.length]; drawRect(ctx, x1, y1, x2, y2, color, 0, null, bevelEnabled && (dataPoints[i].y >= 0), (dataPoints[i].y < 0) && bevelEnabled, false, false, dataSeries.fillOpacity); var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x1, y1: y1, x2: x2, y2: y2 }; color = intToHexColorString(id); if (isCanvasSupported) drawRect(this._eventManager.ghostCtx, x1, y1, x2, y2, color, 0, null, false, false, false, false); if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "stackedColumn100", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: x, y: dataPoints[i].y >= 0 ? y1 : y2 }, direction: dataPoints[i].y >= 0 ? 1 : -1, bounds: { x1: x1, y1: Math.min(y1, y2), x2: x2, y2: Math.max(y1, y2) }, color: color }); } } } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationBase = Math.min(yZeroToPixel, plotUnit.axisY.boundingRect.y2); var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.yScaleAnimation, easingFunction: AnimationHelper.easing.easeOutQuart, animationBase: animationBase }; return animationInfo; } Chart.prototype.renderBar = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var plotArea = this.plotArea; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number from dataTime everytime it is used. //In case of Bar Chart, yZeroToPixel is x co-ordinate! var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var maxBarWidth = this.dataPointMaxWidth ? this.dataPointMaxWidth : Math.min((this.height * .15), this.plotArea.height / plotUnit.plotType.totalDataSeries * .9) << 0; var xMinDiff = plotUnit.axisX.dataInfo.minDiff; if (!isFinite(xMinDiff)) { xMinDiff = Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum) * .3; } //var barWidth = (((plotArea.height / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) / totalDataSeries * .9) << 0; var barWidth = (((plotArea.height / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) / plotUnit.plotType.totalDataSeries * .9) << 0; if (barWidth > maxBarWidth) barWidth = maxBarWidth; else if (barWidth < 1) barWidth = 1; ctx.save(); if (isCanvasSupported) this._eventManager.ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { this._eventManager.ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); this._eventManager.ghostCtx.clip(); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; //dataSeries.maxWidthInX = barWidth / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); if (dataPoints.length > 0) { //var xy = this.getPixelCoordinatesOnPlotArea(dataPoints[0].x, dataPoints[0].y); var bevelEnabled = (barWidth > 5) && dataSeries.bevelEnabled ? true : false; ctx.strokeStyle = "#4572A7 "; for (i = 0; i < dataPoints.length; i++) { dataPoints[i].getTime ? dataPointX = dataPoints[i].x.getTime() : dataPointX = dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoints[i].y) !== "number") continue; //x and y are pixel co-ordinates of point and should not be confused with X and Y values y = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; x = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var y1 = (y - (plotUnit.plotType.totalDataSeries * barWidth / 2) + ((plotUnit.previousDataSeriesCount + j) * barWidth)) << 0; var y2 = y1 + barWidth << 0; var x1; var x2; if (dataPoints[i].y >= 0) { x1 = yZeroToPixel; x2 = x; } else { x1 = x; x2 = yZeroToPixel; } //drawRect(ctx, x1, y1, plotArea.x2, y2, "#EEEEEE", 0, null, false, false, false, false); //drawRect(ctx, x1, y1, plotArea.x2, y2, "#BDCED3", 0, null, false, false, false, false); color = dataPoints[i].color ? dataPoints[i].color : dataSeries._colorSet[i % dataSeries._colorSet.length]; //color = "#1B4962"; drawRect(ctx, x1, y1, x2, y2, color, 0, null, bevelEnabled, false, false, false, dataSeries.fillOpacity); var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x1, y1: y1, x2: x2, y2: y2 }; color = intToHexColorString(id); if (isCanvasSupported) drawRect(this._eventManager.ghostCtx, x1, y1, x2, y2, color, 0, null, false, false, false, false); if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) this._indexLabels.push({ chartType: "bar", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: dataPoints[i].y >= 0 ? x2 : x1, y: y1 + (y2 - y1) / 2 }, direction: dataPoints[i].y >= 0 ? 1 : -1, bounds: { x1: Math.min(x1, x2), y1: y1, x2: Math.max(x1, x2), y2: y2 }, color: color }); } } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationBase = Math.max(yZeroToPixel, plotUnit.axisX.boundingRect.x2); var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xScaleAnimation, easingFunction: AnimationHelper.easing.easeOutQuart, animationBase: animationBase }; return animationInfo; } Chart.prototype.renderStackedBar = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var plotArea = this.plotArea; var offsetPositiveY = []; var offsetNegativeY = []; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number everytime it is accessed. //var yZeroToPixel = (axisYProps.y2 - axisYProps.height / rangeY * Math.abs(0 - plotUnit.axisY.viewportMinimum) + .5) << 0; var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var maxBarWidth = this.dataPointMaxWidth ? this.dataPointMaxWidth : this.height * .15 << 0; var xMinDiff = plotUnit.axisX.dataInfo.minDiff; if (!isFinite(xMinDiff)) { xMinDiff = Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum) * .3; } var barWidth = (((plotArea.height / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) / plotUnit.plotType.plotUnits.length * .9) << 0; if (barWidth > maxBarWidth) barWidth = maxBarWidth; else if (barWidth < 1) barWidth = 1; ctx.save(); if (isCanvasSupported) this._eventManager.ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { this._eventManager.ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); this._eventManager.ghostCtx.clip(); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; //dataSeries.maxWidthInX = barWidth / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); if (dataPoints.length > 0) { //var xy = this.getPixelCoordinatesOnPlotArea(dataPoints[0].x, dataPoints[0].y); var bevelEnabled = (barWidth > 5) && dataSeries.bevelEnabled ? true : false; ctx.strokeStyle = "#4572A7 "; for (i = 0; i < dataPoints.length; i++) { dataPointX = dataPoints[i].x.getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoints[i].y) !== "number") continue; y = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; //x = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; x = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum)); //var x1 = x - (plotUnit.plotType.plotUnits.length * barWidth / 2) + (plotUnit.index * barWidth) << 0; var y1 = y - (plotUnit.plotType.plotUnits.length * barWidth / 2) + (plotUnit.index * barWidth) << 0; var y2 = y1 + barWidth << 0; var x1; var x2; if (dataPoints[i].y >= 0) { var offset = offsetPositiveY[dataPointX] ? offsetPositiveY[dataPointX] : 0; x1 = yZeroToPixel + offset; x2 = x + offset; offsetPositiveY[dataPointX] = offset + (x2 - x1); } else { var offset = offsetNegativeY[dataPointX] ? offsetNegativeY[dataPointX] : 0; x1 = x - offset; x2 = yZeroToPixel - offset; offsetNegativeY[dataPointX] = offset + (x2 - x1); } color = dataPoints[i].color ? dataPoints[i].color : dataSeries._colorSet[i % dataSeries._colorSet.length]; drawRect(ctx, x1, y1, x2, y2, color, 0, null, bevelEnabled, false, false, false, dataSeries.fillOpacity); var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x1, y1: y1, x2: x2, y2: y2 }; color = intToHexColorString(id); if (isCanvasSupported) drawRect(this._eventManager.ghostCtx, x1, y1, x2, y2, color, 0, null, false, false, false, false); if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) this._indexLabels.push({ chartType: "stackedBar", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: dataPoints[i].y >= 0 ? x2 : x1, y: y }, direction: dataPoints[i].y >= 0 ? 1 : -1, bounds: { x1: Math.min(x1, x2), y1: y1, x2: Math.max(x1, x2), y2: y2 }, color: color }); } } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationBase = Math.max(yZeroToPixel, plotUnit.axisX.boundingRect.x2); var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xScaleAnimation, easingFunction: AnimationHelper.easing.easeOutQuart, animationBase: animationBase }; return animationInfo; } Chart.prototype.renderStackedBar100 = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var plotArea = this.plotArea; var offsetPositiveY = []; var offsetNegativeY = []; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number everytime it is accessed. //var yZeroToPixel = (axisYProps.y2 - axisYProps.height / rangeY * Math.abs(0 - plotUnit.axisY.viewportMinimum) + .5) << 0; var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var maxBarWidth = this.dataPointMaxWidth ? this.dataPointMaxWidth : this.height * .15 << 0; var xMinDiff = plotUnit.axisX.dataInfo.minDiff; if (!isFinite(xMinDiff)) { xMinDiff = Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum) * .3; } var barWidth = (((plotArea.height / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) / plotUnit.plotType.plotUnits.length * .9) << 0; if (barWidth > maxBarWidth) barWidth = maxBarWidth; else if (barWidth < 1) barWidth = 1; ctx.save(); if (isCanvasSupported) this._eventManager.ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { this._eventManager.ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); this._eventManager.ghostCtx.clip(); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; //dataSeries.maxWidthInX = barWidth / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); if (dataPoints.length > 0) { //var xy = this.getPixelCoordinatesOnPlotArea(dataPoints[0].x, dataPoints[0].y); var bevelEnabled = (barWidth > 5) && dataSeries.bevelEnabled ? true : false; ctx.strokeStyle = "#4572A7 "; for (i = 0; i < dataPoints.length; i++) { dataPointX = dataPoints[i].x.getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoints[i].y) !== "number") continue; y = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; var yPercent; if (plotUnit.dataPointYSums[dataPointX] !== 0) yPercent = dataPoints[i].y / plotUnit.dataPointYSums[dataPointX] * 100; else yPercent = 0; //x = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (yPercent - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; x = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (yPercent - plotUnit.axisY.conversionParameters.minimum)); var y1 = y - (plotUnit.plotType.plotUnits.length * barWidth / 2) + (plotUnit.index * barWidth) << 0; var y2 = y1 + barWidth << 0; var x1; var x2; if (dataPoints[i].y >= 0) { var offset = offsetPositiveY[dataPointX] ? offsetPositiveY[dataPointX] : 0; x1 = yZeroToPixel + offset; x2 = x + offset; offsetPositiveY[dataPointX] = offset + (x2 - x1); } else { var offset = offsetNegativeY[dataPointX] ? offsetNegativeY[dataPointX] : 0; x1 = x - offset; x2 = yZeroToPixel - offset; offsetNegativeY[dataPointX] = offset + (x2 - x1); } color = dataPoints[i].color ? dataPoints[i].color : dataSeries._colorSet[i % dataSeries._colorSet.length]; drawRect(ctx, x1, y1, x2, y2, color, 0, null, bevelEnabled, false, false, false, dataSeries.fillOpacity); var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x1, y1: y1, x2: x2, y2: y2 }; color = intToHexColorString(id); if (isCanvasSupported) drawRect(this._eventManager.ghostCtx, x1, y1, x2, y2, color, 0, null, false, false, false, false); if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) this._indexLabels.push({ chartType: "stackedBar100", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: dataPoints[i].y >= 0 ? x2 : x1, y: y }, direction: dataPoints[i].y >= 0 ? 1 : -1, bounds: { x1: Math.min(x1, x2), y1: y1, x2: Math.max(x1, x2), y2: y2 }, color: color }); } } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationBase = Math.max(yZeroToPixel, plotUnit.axisX.boundingRect.x2); var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xScaleAnimation, easingFunction: AnimationHelper.easing.easeOutQuart, animationBase: animationBase }; return animationInfo; } Chart.prototype.renderArea = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var ghostCtx = this._eventManager.ghostCtx; var axisXProps = plotUnit.axisX.lineCoordinates; var axisYProps = plotUnit.axisY.lineCoordinates; var markers = []; var plotArea = this.plotArea; ctx.save(); if (isCanvasSupported) ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ghostCtx.clip(); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var seriesId = dataSeries.id; this._eventManager.objectMap[seriesId] = { objectType: "dataSeries", dataSeriesIndex: dataSeriesIndex }; var hexColor = intToHexColorString(seriesId); ghostCtx.fillStyle = hexColor; //ghostCtx.lineWidth = dataSeries.lineThickness; //ghostCtx.lineWidth = 20; markers = []; var isFirstDataPointInPlotArea = true; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number back and forth. var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var baseY; var startPoint = null; if (dataPoints.length > 0) { //ctx.strokeStyle = "#4572A7 "; var color = dataSeries._colorSet[i % dataSeries._colorSet.length]; //ctx.strokeStyle = "red"; ctx.fillStyle = color; ctx.strokeStyle = color; ctx.lineWidth = dataSeries.lineThickness; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(dataSeries.lineDashType, dataSeries.lineThickness)); } var prevDataNull = true; for (; i < dataPoints.length; i++) { dataPointX = dataPoints[i].x.getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoints[i].y) !== "number") { closeArea(); prevDataNull = true; continue; } x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; if (isFirstDataPointInPlotArea || prevDataNull) { ctx.beginPath(); ctx.moveTo(x, y); startPoint = { x: x, y: y }; if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.moveTo(x, y); } isFirstDataPointInPlotArea = false; prevDataNull = false; } else { ctx.lineTo(x, y); if (isCanvasSupported) ghostCtx.lineTo(x, y); if (i % 250 == 0) { closeArea(); } } var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x, y1: y }; //Render Marker if (dataPoints[i].markerSize !== 0) { if (dataPoints[i].markerSize > 0 || dataSeries.markerSize > 0) { var markerProps = dataSeries.getMarkerProperties(i, x, y, ctx); markers.push(markerProps); //if (!dataSeries.maxWidthInX || markerProps.size > dataSeries.maxWidthInX) { // dataSeries.maxWidthInX = markerProps.size / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //} var markerColor = intToHexColorString(id); if (isCanvasSupported) { markers.push({ x: x, y: y, ctx: ghostCtx, type: markerProps.type, size: markerProps.size, color: markerColor, borderColor: markerColor, borderThickness: markerProps.borderThickness }); } } } if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "area", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: x, y: y }, direction: dataPoints[i].y >= 0 ? 1 : -1, color: color }); } } closeArea(); //startPoint = { x: x, y: y }; RenderHelper.drawMarkers(markers); } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); function closeArea() { if (!startPoint) return; if (dataSeries.lineThickness > 0) ctx.stroke(); if (plotUnit.axisY.viewportMinimum <= 0 && plotUnit.axisY.viewportMaximum >= 0) { baseY = yZeroToPixel; } else if (plotUnit.axisY.viewportMaximum < 0) baseY = axisYProps.y1; else if (plotUnit.axisY.viewportMinimum > 0) baseY = axisXProps.y2; ctx.lineTo(x, baseY); ctx.lineTo(startPoint.x, baseY); ctx.closePath(); ctx.globalAlpha = dataSeries.fillOpacity; ctx.fill(); ctx.globalAlpha = 1; if (isCanvasSupported) { ghostCtx.lineTo(x, baseY); ghostCtx.lineTo(startPoint.x, baseY); ghostCtx.closePath(); ghostCtx.fill(); } ctx.beginPath(); ctx.moveTo(x, y); ghostCtx.beginPath(); ghostCtx.moveTo(x, y); startPoint = { x: x, y: y }; } //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xClipAnimation, easingFunction: AnimationHelper.easing.linear, animationBase: 0 }; return animationInfo; } Chart.prototype.renderSplineArea = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var ghostCtx = this._eventManager.ghostCtx; var axisXProps = plotUnit.axisX.lineCoordinates; var axisYProps = plotUnit.axisY.lineCoordinates; var markers = []; var plotArea = this.plotArea; ctx.save(); if (isCanvasSupported) ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ghostCtx.clip(); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var seriesId = dataSeries.id; this._eventManager.objectMap[seriesId] = { objectType: "dataSeries", dataSeriesIndex: dataSeriesIndex }; var hexColor = intToHexColorString(seriesId); ghostCtx.fillStyle = hexColor; //ghostCtx.lineWidth = dataSeries.lineThickness; //ghostCtx.lineWidth = 20; markers = []; var isFirstDataPointInPlotArea = true; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number back and forth. var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var baseY; var startPoint = null; var pixels = []; if (dataPoints.length > 0) { //ctx.strokeStyle = "#4572A7 "; color = dataSeries._colorSet[i % dataSeries._colorSet.length]; ctx.fillStyle = color; ctx.strokeStyle = color; ctx.lineWidth = dataSeries.lineThickness; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(dataSeries.lineDashType, dataSeries.lineThickness)); } for (; i < dataPoints.length; i++) { dataPointX = dataPoints[i].x.getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoints[i].y) !== "number") { if (i > 0) { renderBezierArea(); pixels = []; } continue; } x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x, y1: y }; pixels[pixels.length] = { x: x, y: y }; //Render Marker if (dataPoints[i].markerSize !== 0) { if (dataPoints[i].markerSize > 0 || dataSeries.markerSize > 0) { var markerProps = dataSeries.getMarkerProperties(i, x, y, ctx); markers.push(markerProps); //if (!dataSeries.maxWidthInX || markerProps.size > dataSeries.maxWidthInX) { // dataSeries.maxWidthInX = markerProps.size / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //} var markerColor = intToHexColorString(id); if (isCanvasSupported) { markers.push({ x: x, y: y, ctx: ghostCtx, type: markerProps.type, size: markerProps.size, color: markerColor, borderColor: markerColor, borderThickness: markerProps.borderThickness }); } } } //Render Index Labels if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "splineArea", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: x, y: y }, direction: dataPoints[i].y >= 0 ? 1 : -1, color: color }); } } renderBezierArea(); RenderHelper.drawMarkers(markers); } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); function renderBezierArea() { var bp = getBezierPoints(pixels, 2); if (bp.length > 0) { ctx.beginPath(); ctx.moveTo(bp[0].x, bp[0].y); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.moveTo(bp[0].x, bp[0].y); } for (var i = 0; i < bp.length - 3; i += 3) { ctx.bezierCurveTo(bp[i + 1].x, bp[i + 1].y, bp[i + 2].x, bp[i + 2].y, bp[i + 3].x, bp[i + 3].y); if (isCanvasSupported) ghostCtx.bezierCurveTo(bp[i + 1].x, bp[i + 1].y, bp[i + 2].x, bp[i + 2].y, bp[i + 3].x, bp[i + 3].y); } if (dataSeries.lineThickness > 0) ctx.stroke(); if (plotUnit.axisY.viewportMinimum <= 0 && plotUnit.axisY.viewportMaximum >= 0) { baseY = yZeroToPixel; } else if (plotUnit.axisY.viewportMaximum < 0) baseY = axisYProps.y1; else if (plotUnit.axisY.viewportMinimum > 0) baseY = axisXProps.y2; startPoint = { x: bp[0].x, y: bp[0].y }; ctx.lineTo(bp[bp.length - 1].x, baseY); ctx.lineTo(startPoint.x, baseY); ctx.closePath(); ctx.globalAlpha = dataSeries.fillOpacity; ctx.fill(); ctx.globalAlpha = 1; if (isCanvasSupported) { ghostCtx.lineTo(bp[bp.length - 1].x, baseY); ghostCtx.lineTo(startPoint.x, baseY); ghostCtx.closePath(); ghostCtx.fill(); } } } //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xClipAnimation, easingFunction: AnimationHelper.easing.linear, animationBase: 0 }; return animationInfo; } Chart.prototype.renderStepArea = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var ghostCtx = this._eventManager.ghostCtx; var axisXProps = plotUnit.axisX.lineCoordinates; var axisYProps = plotUnit.axisY.lineCoordinates; var markers = []; var plotArea = this.plotArea; ctx.save(); if (isCanvasSupported) ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ghostCtx.clip(); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var seriesId = dataSeries.id; this._eventManager.objectMap[seriesId] = { objectType: "dataSeries", dataSeriesIndex: dataSeriesIndex }; var hexColor = intToHexColorString(seriesId); ghostCtx.fillStyle = hexColor; //ghostCtx.lineWidth = dataSeries.lineThickness; //ghostCtx.lineWidth = 20; markers = []; var isFirstDataPointInPlotArea = true; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number back and forth. var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var baseY; var startPoint = null; var prevDataNull = false; if (dataPoints.length > 0) { //ctx.strokeStyle = "#4572A7 "; var color = dataSeries._colorSet[i % dataSeries._colorSet.length]; //ctx.strokeStyle = "red"; ctx.fillStyle = color; ctx.strokeStyle = color; ctx.lineWidth = dataSeries.lineThickness; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(dataSeries.lineDashType, dataSeries.lineThickness)); } for (; i < dataPoints.length; i++) { dataPointX = dataPoints[i].x.getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } var prevY = y; if (typeof (dataPoints[i].y) !== "number") { closeArea(); prevDataNull = true; continue; } x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; if (isFirstDataPointInPlotArea || prevDataNull) { ctx.beginPath(); ctx.moveTo(x, y); startPoint = { x: x, y: y }; if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.moveTo(x, y); } isFirstDataPointInPlotArea = false; prevDataNull = false; } else { ctx.lineTo(x, prevY); if (isCanvasSupported) ghostCtx.lineTo(x, prevY); ctx.lineTo(x, y); if (isCanvasSupported) ghostCtx.lineTo(x, y); if (i % 250 == 0) { closeArea(); } } var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x, y1: y }; //Render Marker if (dataPoints[i].markerSize !== 0) { if (dataPoints[i].markerSize > 0 || dataSeries.markerSize > 0) { var markerProps = dataSeries.getMarkerProperties(i, x, y, ctx); markers.push(markerProps); //if (!dataSeries.maxWidthInX || markerProps.size > dataSeries.maxWidthInX) { // dataSeries.maxWidthInX = markerProps.size / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //} var markerColor = intToHexColorString(id); if (isCanvasSupported) { markers.push({ x: x, y: y, ctx: ghostCtx, type: markerProps.type, size: markerProps.size, color: markerColor, borderColor: markerColor, borderThickness: markerProps.borderThickness }); } } } if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "stepArea", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: x, y: y }, direction: dataPoints[i].y >= 0 ? 1 : -1, color: color }); } } closeArea(); RenderHelper.drawMarkers(markers); } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); function closeArea() { if (!startPoint) return; if (dataSeries.lineThickness > 0) ctx.stroke(); if (plotUnit.axisY.viewportMinimum <= 0 && plotUnit.axisY.viewportMaximum >= 0) { baseY = yZeroToPixel; } else if (plotUnit.axisY.viewportMaximum < 0) baseY = axisYProps.y1; else if (plotUnit.axisY.viewportMinimum > 0) baseY = axisXProps.y2; ctx.lineTo(x, baseY); ctx.lineTo(startPoint.x, baseY); ctx.closePath(); ctx.globalAlpha = dataSeries.fillOpacity; ctx.fill(); ctx.globalAlpha = 1; if (isCanvasSupported) { ghostCtx.lineTo(x, baseY); ghostCtx.lineTo(startPoint.x, baseY); ghostCtx.closePath(); ghostCtx.fill(); } ctx.beginPath(); ctx.moveTo(x, y); ghostCtx.beginPath(); ghostCtx.moveTo(x, y); startPoint = { x: x, y: y }; } //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xClipAnimation, easingFunction: AnimationHelper.easing.linear, animationBase: 0 }; return animationInfo; } Chart.prototype.renderStackedArea = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var markers = []; var plotArea = this.plotArea; var offsetY = []; var allXValues = []; //var offsetNegativeY = []; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number everytime it is accessed. //var yZeroToPixel = (axisYProps.y2 - axisYProps.height / rangeY * Math.abs(0 - plotUnit.axisY.viewportMinimum) + .5) << 0; var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var xMinDiff = plotUnit.axisX.dataInfo.minDiff; var ghostCtx = this._eventManager.ghostCtx; if (isCanvasSupported) ghostCtx.beginPath(); ctx.save(); if (isCanvasSupported) ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ghostCtx.clip(); } xValuePresent = []; for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var xValue; dataSeries.dataPointIndexes = []; for (i = 0; i < dataPoints.length; i++) { xValue = dataPoints[i].x.getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; dataSeries.dataPointIndexes[xValue] = i; if (!xValuePresent[xValue]) { allXValues.push(xValue); xValuePresent[xValue] = true; } } allXValues.sort(compareNumbers); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; var currentBaseValues = []; var seriesId = dataSeries.id; this._eventManager.objectMap[seriesId] = { objectType: "dataSeries", dataSeriesIndex: dataSeriesIndex }; var hexColor = intToHexColorString(seriesId); ghostCtx.fillStyle = hexColor; if (allXValues.length > 0) { color = dataSeries._colorSet[0]; //ctx.strokeStyle = "red"; ctx.fillStyle = color; ctx.strokeStyle = color; ctx.lineWidth = dataSeries.lineThickness; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(dataSeries.lineDashType, dataSeries.lineThickness)); } for (i = 0; i < allXValues.length; i++) { dataPointX = allXValues[i]; var dataPoint = null; if (dataSeries.dataPointIndexes[dataPointX] >= 0) dataPoint = dataPoints[dataSeries.dataPointIndexes[dataPointX]]; else dataPoint = { x: dataPointX, y: 0 }; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoint.y) !== "number") continue; var x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; //var y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoint.y - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoint.y - plotUnit.axisY.conversionParameters.minimum)); var offset = offsetY[dataPointX] ? offsetY[dataPointX] : 0; y = y - offset; currentBaseValues.push({ x: x, y: yZeroToPixel - offset }); offsetY[dataPointX] = yZeroToPixel - y; if (isFirstDataPointInPlotArea) { ctx.beginPath(); ctx.moveTo(x, y); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.moveTo(x, y); } isFirstDataPointInPlotArea = false; } else { ctx.lineTo(x, y); if (isCanvasSupported) ghostCtx.lineTo(x, y); if (i % 250 == 0) { if (dataSeries.lineThickness > 0) ctx.stroke(); while (currentBaseValues.length > 0) { var point = currentBaseValues.pop(); ctx.lineTo(point.x, point.y); if (isCanvasSupported) ghostCtx.lineTo(point.x, point.y); } ctx.closePath(); ctx.globalAlpha = dataSeries.fillOpacity; ctx.fill(); ctx.globalAlpha = 1; ctx.beginPath(); ctx.moveTo(x, y); if (isCanvasSupported) { ghostCtx.closePath(); ghostCtx.fill(); ghostCtx.beginPath(); ghostCtx.moveTo(x, y); } currentBaseValues.push({ x: x, y: yZeroToPixel - offset }); } } if (dataSeries.dataPointIndexes[dataPointX] >= 0) { var id = dataSeries.dataPointIds[dataSeries.dataPointIndexes[dataPointX]]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: dataSeries.dataPointIndexes[dataPointX], x1: x, y1: y }; } //Render Marker if (dataSeries.dataPointIndexes[dataPointX] >= 0 && dataPoint.markerSize !== 0) { if (dataPoint.markerSize > 0 || dataSeries.markerSize > 0) { var markerProps = dataSeries.getMarkerProperties(i, x, y, ctx); markers.push(markerProps); //if (!dataSeries.maxWidthInX || markerProps.size > dataSeries.maxWidthInX) { // dataSeries.maxWidthInX = markerProps.size / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //} markerColor = intToHexColorString(id); if (isCanvasSupported) { markers.push({ x: x, y: y, ctx: ghostCtx, type: markerProps.type, size: markerProps.size, color: markerColor, borderColor: markerColor, borderThickness: markerProps.borderThickness }); } } } if (dataPoint.indexLabel || dataSeries.indexLabel || dataPoint.indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "stackedArea", dataPoint: dataPoint, dataSeries: dataSeries, point: { x: x, y: y }, direction: dataPoints[i].y >= 0 ? 1 : -1, color: color }); } } if (dataSeries.lineThickness > 0) ctx.stroke(); while (currentBaseValues.length > 0) { var point = currentBaseValues.pop(); ctx.lineTo(point.x, point.y); if (isCanvasSupported) ghostCtx.lineTo(point.x, point.y); } ctx.closePath(); ctx.globalAlpha = dataSeries.fillOpacity; ctx.fill(); ctx.globalAlpha = 1; ctx.beginPath(); ctx.moveTo(x, y); if (isCanvasSupported) { ghostCtx.closePath(); ghostCtx.fill(); ghostCtx.beginPath(); ghostCtx.moveTo(x, y); } } delete (dataSeries.dataPointIndexes); } RenderHelper.drawMarkers(markers); ctx.restore(); if (isCanvasSupported) ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xClipAnimation, easingFunction: AnimationHelper.easing.linear, animationBase: 0 }; return animationInfo; } Chart.prototype.renderStackedArea100 = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var plotArea = this.plotArea; var markers = []; var offsetY = []; var allXValues = []; //var offsetNegativeY = []; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number everytime it is accessed. //var yZeroToPixel = (axisYProps.y2 - axisYProps.height / rangeY * Math.abs(0 - plotUnit.axisY.viewportMinimum) + .5) << 0; var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var maxBarWidth = this.dataPointMaxWidth ? this.dataPointMaxWidth : this.width * .15 << 0; var xMinDiff = plotUnit.axisX.dataInfo.minDiff; var barWidth = (((plotArea.width / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) * .9) << 0; var ghostCtx = this._eventManager.ghostCtx; ctx.save(); if (isCanvasSupported) ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ghostCtx.clip(); } xValuePresent = []; for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var xValue; dataSeries.dataPointIndexes = []; for (i = 0; i < dataPoints.length; i++) { xValue = dataPoints[i].x.getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; dataSeries.dataPointIndexes[xValue] = i; if (!xValuePresent[xValue]) { allXValues.push(xValue); xValuePresent[xValue] = true; } } allXValues.sort(compareNumbers); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; var seriesId = dataSeries.id; this._eventManager.objectMap[seriesId] = { objectType: "dataSeries", dataSeriesIndex: dataSeriesIndex }; var hexColor = intToHexColorString(seriesId); ghostCtx.fillStyle = hexColor; if (dataPoints.length == 1) barWidth = maxBarWidth; if (barWidth < 1) barWidth = 1; else if (barWidth > maxBarWidth) barWidth = maxBarWidth; var currentBaseValues = []; if (allXValues.length > 0) { color = dataSeries._colorSet[i % dataSeries._colorSet.length]; //ctx.strokeStyle = "red"; ctx.fillStyle = color; ctx.strokeStyle = color; ctx.lineWidth = dataSeries.lineThickness; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(dataSeries.lineDashType, dataSeries.lineThickness)); } var bevelEnabled = (barWidth > 5) ? false : false; //ctx.strokeStyle = "#4572A7 "; for (i = 0; i < allXValues.length; i++) { dataPointX = allXValues[i]; var dataPoint = null; if (dataSeries.dataPointIndexes[dataPointX] >= 0) dataPoint = dataPoints[dataSeries.dataPointIndexes[dataPointX]]; else dataPoint = { x: dataPointX, y: 0 }; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoint.y) !== "number") continue; var yPercent; if (plotUnit.dataPointYSums[dataPointX] !== 0) yPercent = dataPoint.y / plotUnit.dataPointYSums[dataPointX] * 100; else yPercent = 0; var x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; var y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (yPercent - plotUnit.axisY.conversionParameters.minimum)); var offset = offsetY[dataPointX] ? offsetY[dataPointX] : 0; y = y - offset; currentBaseValues.push({ x: x, y: yZeroToPixel - offset }); offsetY[dataPointX] = yZeroToPixel - y; if (isFirstDataPointInPlotArea) { ctx.beginPath(); ctx.moveTo(x, y); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.moveTo(x, y); } isFirstDataPointInPlotArea = false; } else { ctx.lineTo(x, y); if (isCanvasSupported) ghostCtx.lineTo(x, y); if (i % 250 == 0) { if (dataSeries.lineThickness > 0) ctx.stroke(); while (currentBaseValues.length > 0) { var point = currentBaseValues.pop(); ctx.lineTo(point.x, point.y); if (isCanvasSupported) ghostCtx.lineTo(point.x, point.y); } ctx.closePath(); ctx.globalAlpha = dataSeries.fillOpacity; ctx.fill(); ctx.globalAlpha = 1; ctx.beginPath(); ctx.moveTo(x, y); if (isCanvasSupported) { ghostCtx.closePath(); ghostCtx.fill(); ghostCtx.beginPath(); ghostCtx.moveTo(x, y); } currentBaseValues.push({ x: x, y: yZeroToPixel - offset }); } } if (dataSeries.dataPointIndexes[dataPointX] >= 0) { var id = dataSeries.dataPointIds[dataSeries.dataPointIndexes[dataPointX]]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: dataSeries.dataPointIndexes[dataPointX], x1: x, y1: y }; } //Render Marker if (dataSeries.dataPointIndexes[dataPointX] >= 0 && dataPoint.markerSize !== 0) { if (dataPoint.markerSize > 0 || dataSeries.markerSize > 0) { var markerProps = dataSeries.getMarkerProperties(i, x, y, ctx); markers.push(markerProps); //if (!dataSeries.maxWidthInX || markerProps.size > dataSeries.maxWidthInX) { // dataSeries.maxWidthInX = markerProps.size / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //} markerColor = intToHexColorString(id); if (isCanvasSupported) { markers.push({ x: x, y: y, ctx: ghostCtx, type: markerProps.type, size: markerProps.size, color: markerColor, borderColor: markerColor, borderThickness: markerProps.borderThickness }); } } } if (dataPoint.indexLabel || dataSeries.indexLabel || dataPoint.indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "stackedArea100", dataPoint: dataPoint, dataSeries: dataSeries, point: { x: x, y: y }, direction: dataPoints[i].y >= 0 ? 1 : -1, color: color }); } } if (dataSeries.lineThickness > 0) ctx.stroke(); while (currentBaseValues.length > 0) { var point = currentBaseValues.pop(); ctx.lineTo(point.x, point.y); if (isCanvasSupported) ghostCtx.lineTo(point.x, point.y); } ctx.closePath(); ctx.globalAlpha = dataSeries.fillOpacity; ctx.fill(); ctx.globalAlpha = 1; ctx.beginPath(); ctx.moveTo(x, y); if (isCanvasSupported) { ghostCtx.closePath(); ghostCtx.fill(); ghostCtx.beginPath(); ghostCtx.moveTo(x, y); } } delete (dataSeries.dataPointIndexes); } RenderHelper.drawMarkers(markers); ctx.restore(); if (isCanvasSupported) ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xClipAnimation, easingFunction: AnimationHelper.easing.linear, animationBase: 0 }; return animationInfo; } Chart.prototype.renderBubble = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var plotArea = this.plotArea; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number from dataTime everytime it is used. var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var maxBarWidth = this.dataPointMaxWidth ? this.dataPointMaxWidth : this.width * .15 << 0; var xMinDiff = plotUnit.axisX.dataInfo.minDiff; var barWidth = (((plotArea.width / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) / totalDataSeries * .9) << 0; ctx.save(); if (isCanvasSupported) this._eventManager.ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { this._eventManager.ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); this._eventManager.ghostCtx.clip(); } var maxZ = -Infinity; var minZ = Infinity; for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var z = 0; for (var i = 0; i < dataPoints.length; i++) { dataPointX = dataPoints[i].getTime ? dataPointX = dataPoints[i].x.getTime() : dataPointX = dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoints[i].z) !== "undefined") { z = dataPoints[i].z; if (z > maxZ) maxZ = z; if (z < minZ) minZ = z; } } } var minArea = Math.PI * 5 * 5; var maxArea = Math.max(Math.pow(Math.min(plotArea.height, plotArea.width) * .25 / 2, 2) * Math.PI, minArea); for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; if (dataPoints.length == 1) barWidth = maxBarWidth; if (barWidth < 1) barWidth = 1; else if (barWidth > maxBarWidth) barWidth = maxBarWidth; if (dataPoints.length > 0) { //var xy = this.getPixelCoordinatesOnPlotArea(dataPoints[0].x, dataPoints[0].y); //var bevelEnabled = (barWidth > 5) ? false : false; ctx.strokeStyle = "#4572A7 "; for (var i = 0; i < dataPoints.length; i++) { dataPointX = dataPoints[i].getTime ? dataPointX = dataPoints[i].x.getTime() : dataPointX = dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoints[i].y) !== "number") continue; x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var z = dataPoints[i].z; var area = (maxZ === minZ) ? maxArea / 2 : minArea + (maxArea - minArea) / (maxZ - minZ) * (z - minZ); var radius = Math.max(Math.sqrt(area / Math.PI) << 0, 1); var markerSize = radius * 2; var markerProps = dataSeries.getMarkerProperties(i, ctx); markerProps.size = markerSize; ctx.globalAlpha = dataSeries.fillOpacity; RenderHelper.drawMarker(x, y, ctx, markerProps.type, markerProps.size, markerProps.color, markerProps.borderColor, markerProps.borderThickness); ctx.globalAlpha = 1; var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x, y1: y, size: markerSize }; var markerColor = intToHexColorString(id); //RenderHelper.drawMarker(x, y, this._eventManager.ghostCtx, markerType, markerSize, markerColor, markerColor, dataSeries.markerBorderThickness); if (isCanvasSupported) RenderHelper.drawMarker(x, y, this._eventManager.ghostCtx, markerProps.type, markerProps.size, markerColor, markerColor, markerProps.borderThickness); if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "bubble", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: x, y: y }, direction: 1, bounds: { x1: x - markerProps.size / 2, y1: y - markerProps.size / 2, x2: x + markerProps.size / 2, y2: y + markerProps.size / 2 }, color: color }); } } } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.fadeInAnimation, easingFunction: AnimationHelper.easing.easeInQuad, animationBase: 0 }; return animationInfo; } Chart.prototype.renderScatter = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var plotArea = this.plotArea; var i = 0, x, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number from dataTime everytime it is used. var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var maxBarWidth = this.dataPointMaxWidth ? this.dataPointMaxWidth : this.width * .15 << 0; var xMinDiff = plotUnit.axisX.dataInfo.minDiff; var barWidth = (((plotArea.width / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) / totalDataSeries * .9) << 0; ctx.save(); if (isCanvasSupported) this._eventManager.ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { this._eventManager.ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); this._eventManager.ghostCtx.clip(); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; if (dataPoints.length == 1) barWidth = maxBarWidth; if (barWidth < 1) barWidth = 1; else if (barWidth > maxBarWidth) barWidth = maxBarWidth; if (dataPoints.length > 0) { //var bevelEnabled = (barWidth > 5) ? false : false; ctx.strokeStyle = "#4572A7 "; var maxArea = Math.pow(Math.min(plotArea.height, plotArea.width) * .3 / 2, 2) * Math.PI; var prevDataPointX = 0; var prevDataPointY = 0; for (var i = 0; i < dataPoints.length; i++) { dataPointX = dataPoints[i].getTime ? dataPointX = dataPoints[i].x.getTime() : dataPointX = dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (typeof (dataPoints[i].y) !== "number") continue; x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var markerProps = dataSeries.getMarkerProperties(i, x, y, ctx); ctx.globalAlpha = dataSeries.fillOpacity; RenderHelper.drawMarker(markerProps.x, markerProps.y, markerProps.ctx, markerProps.type, markerProps.size, markerProps.color, markerProps.borderColor, markerProps.borderThickness); ctx.globalAlpha = 1; //if (Math.abs(prevDataPointX - x) < markerProps.size / 2 && Math.abs(prevDataPointY - y) < markerProps.size / 2) { // continue; //} //if (!dataSeries.maxWidthInX || markerProps.size > dataSeries.maxWidthInX) { // dataSeries.maxWidthInX = markerProps.size / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //} if ((Math.sqrt((prevDataPointX - x) * (prevDataPointX - x) + (prevDataPointY - y) * (prevDataPointY - y)) < Math.min(markerProps.size, 5)) && dataPoints.length > (Math.min(this.plotArea.width, this.plotArea.height))) { continue; } //Render ID on Ghost Canvas - for event handling var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x, y1: y }; var markerColor = intToHexColorString(id); if (isCanvasSupported) { RenderHelper.drawMarker( markerProps.x, markerProps.y, this._eventManager.ghostCtx, markerProps.type, markerProps.size, markerColor, markerColor, markerProps.borderThickness ); } //markers.push(); if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "scatter", dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: x, y: y }, direction: 1, bounds: { x1: x - markerProps.size / 2, y1: y - markerProps.size / 2, x2: x + markerProps.size / 2, y2: y + markerProps.size / 2 }, color: color }); } prevDataPointX = x; prevDataPointY = y; } } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.fadeInAnimation, easingFunction: AnimationHelper.easing.easeInQuad, animationBase: 0 }; return animationInfo; } Chart.prototype.renderCandlestick = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var ghostCtx = this._eventManager.ghostCtx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var plotArea = this.plotArea; var i = 0, x, y1, y2, y3, y4; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number from dataTime everytime it is used. var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var maxBarWidth = this.dataPointMaxWidth ? this.dataPointMaxWidth : (this.width * .015); var xMinDiff = plotUnit.axisX.dataInfo.minDiff; if (!isFinite(xMinDiff)) { xMinDiff = Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum) * .3; } var barWidth = (((plotArea.width / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) * .7) << 0; if (barWidth > maxBarWidth) barWidth = maxBarWidth; else if (barWidth < 1) barWidth = 1; ctx.save(); if (isCanvasSupported) ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ghostCtx.clip(); } //ctx.beginPath(); for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; // Reducing pixelPerUnit by 1 just to overcome any problems due to rounding off of pixels. //dataSeries.maxWidthInX = barWidth / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //var offsetX = barWidth * plotUnit.index << 0; if (dataPoints.length > 0) { //var xy = this.getPixelCoordinatesOnPlotArea(dataPoints[0].x, dataPoints[0].y); var bevelEnabled = (barWidth > 5) && dataSeries.bevelEnabled ? true : false; for (i = 0; i < dataPoints.length; i++) { dataPoints[i].getTime ? dataPointX = dataPoints[i].x.getTime() : dataPointX = dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (dataPoints[i].y === null || !dataPoints[i].y.length || typeof (dataPoints[i].y[0]) !== "number" || typeof (dataPoints[i].y[1]) !== "number" || typeof (dataPoints[i].y[2]) !== "number" || typeof (dataPoints[i].y[3]) !== "number") continue; x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y1 = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y[0] - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; y2 = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y[1] - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; y3 = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y[2] - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; y4 = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y[3] - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var x1 = (x - barWidth / 2) << 0; var x2 = (x1 + barWidth) << 0; color = dataPoints[i].color ? dataPoints[i].color : dataSeries._colorSet[0]; //var borderThickness = Math.max(2, ((barWidth * .1) / 2 << 0) * 2); // Set only even numbers for border var borderThickness = Math.round(Math.max(1, (barWidth * .15))); //borderThickness = (borderThickness / 2 << 0) * 2; //borderThickness = 2; var offset = borderThickness % 2 === 0 ? 0 : .5; var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x1, y1: y1, x2: x2, y2: y2, x3: x, y3: y3, x4: x, y4: y4, borderThickness: borderThickness, color: color }; ctx.strokeStyle = color; ctx.beginPath(); ctx.lineWidth = borderThickness; ghostCtx.lineWidth = Math.max(borderThickness, 4); if (dataSeries.type === "candlestick") { ctx.moveTo(x - offset, y2); ctx.lineTo(x - offset, Math.min(y1, y4)); ctx.stroke(); ctx.moveTo(x - offset, Math.max(y1, y4)); ctx.lineTo(x - offset, y3); ctx.stroke(); drawRect(ctx, x1, Math.min(y1, y4), x2, Math.max(y1, y4), dataPoints[i].y[0] <= dataPoints[i].y[3] ? dataSeries.risingColor : color, borderThickness, color, bevelEnabled, bevelEnabled, false, false, dataSeries.fillOpacity); if (isCanvasSupported) { color = intToHexColorString(id); ghostCtx.strokeStyle = color; ghostCtx.moveTo(x - offset, y2); ghostCtx.lineTo(x - offset, Math.min(y1, y4)); ghostCtx.stroke(); ghostCtx.moveTo(x - offset, Math.max(y1, y4)); ghostCtx.lineTo(x - offset, y3); ghostCtx.stroke(); drawRect(ghostCtx, x1, Math.min(y1, y4), x2, Math.max(y1, y4), color, 0, null, false, false, false, false); } } else if (dataSeries.type === "ohlc") { ctx.moveTo(x - offset, y2); ctx.lineTo(x - offset, y3); ctx.stroke(); ctx.beginPath(); ctx.moveTo(x, y1); ctx.lineTo(x1, y1); ctx.stroke(); ctx.beginPath(); ctx.moveTo(x, y4); ctx.lineTo(x2, y4); ctx.stroke(); if (isCanvasSupported) { color = intToHexColorString(id); ghostCtx.strokeStyle = color; ghostCtx.moveTo(x - offset, y2); ghostCtx.lineTo(x - offset, y3); ghostCtx.stroke(); ghostCtx.beginPath(); ghostCtx.moveTo(x, y1); ghostCtx.lineTo(x1, y1); ghostCtx.stroke(); ghostCtx.beginPath(); ghostCtx.moveTo(x, y4); ghostCtx.lineTo(x2, y4); ghostCtx.stroke(); } } if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: dataSeries.type, dataPoint: dataPoints[i], dataSeries: dataSeries, point: { x: x1 + (x2 - x1) / 2, y: y2 }, direction: 1, bounds: { x1: x1, y1: Math.min(y2, y3), x2: x2, y2: Math.max(y2, y3) }, color: color }); } } } } ctx.restore(); if (isCanvasSupported) ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.fadeInAnimation, easingFunction: AnimationHelper.easing.easeInQuad, animationBase: 0 }; return animationInfo; } Chart.prototype.renderRangeColumn = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var plotArea = this.plotArea; var i = 0, x, y1, y2; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number from dataTime everytime it is used. var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var maxBarWidth = this.dataPointMaxWidth ? this.dataPointMaxWidth : (this.width * .03); //var maxBarWidth = (this.width * .015); var xMinDiff = plotUnit.axisX.dataInfo.minDiff; if (!isFinite(xMinDiff)) { xMinDiff = Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum) * .3; } //var barWidth = (((plotArea.width / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) * .9) << 0; var barWidth = (((plotArea.width / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) / plotUnit.plotType.totalDataSeries * .9) << 0; if (barWidth > maxBarWidth) barWidth = maxBarWidth; else if (barWidth < 1) barWidth = 1; ctx.save(); if (isCanvasSupported) this._eventManager.ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { this._eventManager.ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); this._eventManager.ghostCtx.clip(); } //ctx.beginPath(); for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; // Reducing pixelPerUnit by 1 just to overcome any problems due to rounding off of pixels. //dataSeries.maxWidthInX = barWidth / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //var offsetX = barWidth * plotUnit.index << 0; if (dataPoints.length > 0) { //var xy = this.getPixelCoordinatesOnPlotArea(dataPoints[0].x, dataPoints[0].y); var bevelEnabled = (barWidth > 5) && dataSeries.bevelEnabled ? true : false; for (i = 0; i < dataPoints.length; i++) { dataPoints[i].getTime ? dataPointX = dataPoints[i].x.getTime() : dataPointX = dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (dataPoints[i].y === null || !dataPoints[i].y.length || typeof (dataPoints[i].y[0]) !== "number" || typeof (dataPoints[i].y[1]) !== "number") continue; x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y1 = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y[0] - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; y2 = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y[1] - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; //var x1 = x - barWidth / 2 << 0; var x1 = x - (plotUnit.plotType.totalDataSeries * barWidth / 2) + ((plotUnit.previousDataSeriesCount + j) * barWidth) << 0; var x2 = x1 + barWidth << 0; var y1; var y2; color = dataPoints[i].color ? dataPoints[i].color : dataSeries._colorSet[i % dataSeries._colorSet.length]; if (y1 > y2) { var temp = y1; y1 = y2; y2 = temp; } var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x1, y1: y1, x2: x2, y2: y2 }; //var borderThickness = Math.max(1, (barWidth * .1 << 0)); var borderThickness = 0; drawRect(ctx, x1, y1, x2, y2, color, borderThickness, color, bevelEnabled, bevelEnabled, false, false, dataSeries.fillOpacity); color = intToHexColorString(id); if (isCanvasSupported) drawRect(this._eventManager.ghostCtx, x1, y1, x2, y2, color, 0, null, false, false, false, false); if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "rangeColumn", dataPoint: dataPoints[i], dataSeries: dataSeries, indexKeyword: 0, point: { x: x1 + (x2 - x1) / 2, y: dataPoints[i].y[1] >= dataPoints[i].y[0] ? y2 : y1 }, direction: dataPoints[i].y[1] >= dataPoints[i].y[0] ? -1 : 1, bounds: { x1: x1, y1: Math.min(y1, y2), x2: x2, y2: Math.max(y1, y2) }, color: color }); this._indexLabels.push({ chartType: "rangeColumn", dataPoint: dataPoints[i], dataSeries: dataSeries, indexKeyword: 1, point: { x: x1 + (x2 - x1) / 2, y: dataPoints[i].y[1] >= dataPoints[i].y[0] ? y1 : y2 }, direction: dataPoints[i].y[1] >= dataPoints[i].y[0] ? 1 : -1, bounds: { x1: x1, y1: Math.min(y1, y2), x2: x2, y2: Math.max(y1, y2) }, color: color }); } } } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.fadeInAnimation, easingFunction: AnimationHelper.easing.easeInQuad, animationBase: 0 }; return animationInfo; } Chart.prototype.renderRangeBar = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var color = null; var plotArea = this.plotArea; var i = 0, x1, x2, y; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number from dataTime everytime it is used. //In case of Bar Chart, yZeroToPixel is x co-ordinate! var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum)) << 0; var maxBarWidth = this.dataPointMaxWidth ? this.dataPointMaxWidth : Math.min((this.height * .15), this.plotArea.height / plotUnit.plotType.totalDataSeries * .9) << 0; var xMinDiff = plotUnit.axisX.dataInfo.minDiff; if (!isFinite(xMinDiff)) { xMinDiff = Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum) * .3; } //var barWidth = (((plotArea.height / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) / totalDataSeries * .9) << 0; var barWidth = (((plotArea.height / Math.abs(plotUnit.axisX.viewportMaximum - plotUnit.axisX.viewportMinimum)) * Math.abs(xMinDiff)) / plotUnit.plotType.totalDataSeries * .9) << 0; if (barWidth > maxBarWidth) barWidth = maxBarWidth; else if (barWidth < 1) barWidth = 1; ctx.save(); if (isCanvasSupported) this._eventManager.ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { this._eventManager.ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); this._eventManager.ghostCtx.clip(); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var isFirstDataPointInPlotArea = true; //dataSeries.maxWidthInX = barWidth / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); if (dataPoints.length > 0) { //var xy = this.getPixelCoordinatesOnPlotArea(dataPoints[0].x, dataPoints[0].y); var bevelEnabled = (barWidth > 5) && dataSeries.bevelEnabled ? true : false; ctx.strokeStyle = "#4572A7 "; for (i = 0; i < dataPoints.length; i++) { dataPoints[i].getTime ? dataPointX = dataPoints[i].x.getTime() : dataPointX = dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (dataPoints[i].y === null || !dataPoints[i].y.length || typeof (dataPoints[i].y[0]) !== "number" || typeof (dataPoints[i].y[1]) !== "number") continue; //x and y are pixel co-ordinates of point and should not be confused with X and Y values x1 = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y[0] - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; x2 = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y[1] - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; y = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; var y1 = (y - (plotUnit.plotType.totalDataSeries * barWidth / 2) + ((plotUnit.previousDataSeriesCount + j) * barWidth)) << 0; var y2 = y1 + barWidth << 0; if (x1 > x2) { var temp = x1; x1 = x2; x2 = temp; } //drawRect(ctx, x1, y1, plotArea.x2, y2, "#EEEEEE", 0, null, false, false, false, false); //drawRect(ctx, x1, y1, plotArea.x2, y2, "#BDCED3", 0, null, false, false, false, false); color = dataPoints[i].color ? dataPoints[i].color : dataSeries._colorSet[i % dataSeries._colorSet.length]; //color = "#1B4962"; drawRect(ctx, x1, y1, x2, y2, color, 0, null, bevelEnabled, false, false, false, dataSeries.fillOpacity); var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x1, y1: y1, x2: x2, y2: y2 }; color = intToHexColorString(id); if (isCanvasSupported) drawRect(this._eventManager.ghostCtx, x1, y1, x2, y2, color, 0, null, false, false, false, false); if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "rangeBar", dataPoint: dataPoints[i], dataSeries: dataSeries, indexKeyword: 0, point: { x: dataPoints[i].y[1] >= dataPoints[i].y[0] ? x1 : x2, y: y1 + (y2 - y1) / 2 }, direction: dataPoints[i].y[1] >= dataPoints[i].y[0] ? -1 : 1, bounds: { x1: Math.min(x1, x2), y1: y1, x2: Math.max(x1, x2), y2: y2 }, color: color }); this._indexLabels.push({ chartType: "rangeBar", dataPoint: dataPoints[i], dataSeries: dataSeries, indexKeyword: 1, point: { x: dataPoints[i].y[1] >= dataPoints[i].y[0] ? x2 : x1, y: y1 + (y2 - y1) / 2 }, direction: dataPoints[i].y[1] >= dataPoints[i].y[0] ? 1 : -1, bounds: { x1: Math.min(x1, x2), y1: y1, x2: Math.max(x1, x2), y2: y2 }, color: color }); } } } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.fadeInAnimation, easingFunction: AnimationHelper.easing.easeInQuad, animationBase: 0 }; return animationInfo; } Chart.prototype.renderRangeArea = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var ghostCtx = this._eventManager.ghostCtx; var axisXProps = plotUnit.axisX.lineCoordinates; var axisYProps = plotUnit.axisY.lineCoordinates; var markers = []; var plotArea = this.plotArea; ctx.save(); if (isCanvasSupported) ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ghostCtx.clip(); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var closingPath = []; var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var seriesId = dataSeries.id; this._eventManager.objectMap[seriesId] = { objectType: "dataSeries", dataSeriesIndex: dataSeriesIndex }; var hexColor = intToHexColorString(seriesId); ghostCtx.fillStyle = hexColor; //ghostCtx.lineWidth = dataSeries.lineThickness; //ghostCtx.lineWidth = 20; markers = []; var isFirstDataPointInPlotArea = true; var i = 0, x, y1, y2; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number back and forth. var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var baseY; var startPoint = null; if (dataPoints.length > 0) { //ctx.strokeStyle = "#4572A7 "; var color = dataSeries._colorSet[i % dataSeries._colorSet.length]; //ctx.strokeStyle = "red"; ctx.fillStyle = color; ctx.strokeStyle = color; ctx.lineWidth = dataSeries.lineThickness; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(dataSeries.lineDashType, dataSeries.lineThickness)); } var prevDataNull = true; for (; i < dataPoints.length; i++) { dataPointX = dataPoints[i].x.getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (dataPoints[i].y === null || !dataPoints[i].y.length || typeof (dataPoints[i].y[0]) !== "number" || typeof (dataPoints[i].y[1]) !== "number") { closeArea(); prevDataNull = true; continue; } x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y1 = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y[0] - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; y2 = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y[1] - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; if (isFirstDataPointInPlotArea || prevDataNull) { ctx.beginPath(); ctx.moveTo(x, y1); startPoint = { x: x, y: y1 }; closingPath = []; closingPath.push({ x: x, y: y2 }); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.moveTo(x, y1); } isFirstDataPointInPlotArea = false; prevDataNull = false; } else { ctx.lineTo(x, y1); closingPath.push({ x: x, y: y2 }); if (isCanvasSupported) ghostCtx.lineTo(x, y1); if (i % 250 == 0) { closeArea(); } } var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x, y1: y1, y2: y2 }; //Render Marker if (dataPoints[i].markerSize !== 0) { if (dataPoints[i].markerSize > 0 || dataSeries.markerSize > 0) { var markerProps = dataSeries.getMarkerProperties(i, x, y2, ctx); markers.push(markerProps); //if (!dataSeries.maxWidthInX || markerProps.size > dataSeries.maxWidthInX) { // dataSeries.maxWidthInX = markerProps.size / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //} var markerColor = intToHexColorString(id); if (isCanvasSupported) { markers.push({ x: x, y: y2, ctx: ghostCtx, type: markerProps.type, size: markerProps.size, color: markerColor, borderColor: markerColor, borderThickness: markerProps.borderThickness }); } markerProps = dataSeries.getMarkerProperties(i, x, y1, ctx); markers.push(markerProps); var markerColor = intToHexColorString(id); if (isCanvasSupported) { markers.push({ x: x, y: y1, ctx: ghostCtx, type: markerProps.type, size: markerProps.size, color: markerColor, borderColor: markerColor, borderThickness: markerProps.borderThickness }); } } } if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "rangeArea", dataPoint: dataPoints[i], dataSeries: dataSeries, indexKeyword: 0, point: { x: x, y: y1 }, direction: dataPoints[i].y[0] <= dataPoints[i].y[1] ? -1 : 1, color: color }); this._indexLabels.push({ chartType: "rangeArea", dataPoint: dataPoints[i], dataSeries: dataSeries, indexKeyword: 1, point: { x: x, y: y2 }, direction: dataPoints[i].y[0] <= dataPoints[i].y[1] ? 1 : -1, color: color }); } //alert("hi"); } closeArea(); //startPoint = { x: x, y: y }; RenderHelper.drawMarkers(markers); } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); function closeArea() { if (!startPoint) return; var point = null; if (dataSeries.lineThickness > 0) ctx.stroke(); for (var i = closingPath.length - 1; i >= 0; i--) { point = closingPath[i]; ctx.lineTo(point.x, point.y); ghostCtx.lineTo(point.x, point.y); } ctx.closePath(); //ctx.lineTo(startPoint.x, startPoint.y); ctx.globalAlpha = dataSeries.fillOpacity; ctx.fill(); ctx.globalAlpha = 1; ghostCtx.fill(); //if (isCanvasSupported) { // ghostCtx.lineTo(x, baseY); // ghostCtx.lineTo(startPoint.x, baseY); // ghostCtx.closePath(); // ghostCtx.fill(); //} if (dataSeries.lineThickness > 0) { ctx.beginPath(); ctx.moveTo(point.x, point.y); for (var i = 0; i < closingPath.length; i++) { point = closingPath[i]; ctx.lineTo(point.x, point.y); } ctx.stroke(); } ctx.beginPath(); ctx.moveTo(x, y1); ghostCtx.beginPath(); ghostCtx.moveTo(x, y1); startPoint = { x: x, y: y1 }; closingPath = []; closingPath.push({ x: x, y: y2 }); } //ctx.beginPath(); //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xClipAnimation, easingFunction: AnimationHelper.easing.linear, animationBase: 0 }; return animationInfo; } Chart.prototype.renderRangeSplineArea = function (plotUnit) { var ctx = plotUnit.targetCanvasCtx || this.plotArea.ctx; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var ghostCtx = this._eventManager.ghostCtx; var axisXProps = plotUnit.axisX.lineCoordinates; var axisYProps = plotUnit.axisY.lineCoordinates; var markers = []; var plotArea = this.plotArea; ctx.save(); if (isCanvasSupported) ghostCtx.save(); ctx.beginPath(); ctx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.clip(); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.rect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ghostCtx.clip(); } for (var j = 0; j < plotUnit.dataSeriesIndexes.length; j++) { var dataSeriesIndex = plotUnit.dataSeriesIndexes[j]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var seriesId = dataSeries.id; this._eventManager.objectMap[seriesId] = { objectType: "dataSeries", dataSeriesIndex: dataSeriesIndex }; var hexColor = intToHexColorString(seriesId); ghostCtx.fillStyle = hexColor; //ghostCtx.lineWidth = dataSeries.lineThickness; //ghostCtx.lineWidth = 20; markers = []; var isFirstDataPointInPlotArea = true; var i = 0, x, y1, y2; var dataPointX; //Used so that when dataPoint.x is a DateTime value, it doesn't get converted to number back and forth. var yZeroToPixel = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (0 - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var baseY; var startPoint = null; var pixelsY1 = []; var pixelsY2 = []; if (dataPoints.length > 0) { //ctx.strokeStyle = "#4572A7 "; color = dataSeries._colorSet[i % dataSeries._colorSet.length]; //ctx.strokeStyle = "red"; ctx.fillStyle = color; ctx.strokeStyle = color; ctx.lineWidth = dataSeries.lineThickness; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(dataSeries.lineDashType, dataSeries.lineThickness)); } for (; i < dataPoints.length; i++) { dataPointX = dataPoints[i].x.getTime ? dataPoints[i].x.getTime() : dataPoints[i].x; if (dataPointX < plotUnit.axisX.dataInfo.viewPortMin || dataPointX > plotUnit.axisX.dataInfo.viewPortMax) { continue; } if (dataPoints[i].y === null || !dataPoints[i].y.length || typeof (dataPoints[i].y[0]) !== "number" || typeof (dataPoints[i].y[1]) !== "number") { if (i > 0) { renderBezierArea(); pixelsY1 = []; pixelsY2 = []; } continue; } x = (plotUnit.axisX.conversionParameters.reference + plotUnit.axisX.conversionParameters.pixelPerUnit * (dataPointX - plotUnit.axisX.conversionParameters.minimum) + .5) << 0; y1 = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y[0] - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; y2 = (plotUnit.axisY.conversionParameters.reference + plotUnit.axisY.conversionParameters.pixelPerUnit * (dataPoints[i].y[1] - plotUnit.axisY.conversionParameters.minimum) + .5) << 0; var id = dataSeries.dataPointIds[i]; this._eventManager.objectMap[id] = { id: id, objectType: "dataPoint", dataSeriesIndex: dataSeriesIndex, dataPointIndex: i, x1: x, y1: y1, y2: y2 }; pixelsY1[pixelsY1.length] = { x: x, y: y1 }; pixelsY2[pixelsY2.length] = { x: x, y: y2 }; //Render Marker if (dataPoints[i].markerSize !== 0) { if (dataPoints[i].markerSize > 0 || dataSeries.markerSize > 0) { var markerProps = dataSeries.getMarkerProperties(i, x, y1, ctx); markers.push(markerProps); //if (!dataSeries.maxWidthInX || markerProps.size > dataSeries.maxWidthInX) { // dataSeries.maxWidthInX = markerProps.size / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //} var markerColor = intToHexColorString(id); if (isCanvasSupported) { markers.push({ x: x, y: y1, ctx: ghostCtx, type: markerProps.type, size: markerProps.size, color: markerColor, borderColor: markerColor, borderThickness: markerProps.borderThickness }); } var markerProps = dataSeries.getMarkerProperties(i, x, y2, ctx); markers.push(markerProps); //if (!dataSeries.maxWidthInX || markerProps.size > dataSeries.maxWidthInX) { // dataSeries.maxWidthInX = markerProps.size / (plotUnit.axisX.conversionParameters.pixelPerUnit > 1 ? plotUnit.axisX.conversionParameters.pixelPerUnit - 1 : plotUnit.axisX.conversionParameters.pixelPerUnit); //} var markerColor = intToHexColorString(id); if (isCanvasSupported) { markers.push({ x: x, y: y2, ctx: ghostCtx, type: markerProps.type, size: markerProps.size, color: markerColor, borderColor: markerColor, borderThickness: markerProps.borderThickness }); } } } //Render Index Labels if (dataPoints[i].indexLabel || dataSeries.indexLabel || dataPoints[i].indexLabelFormatter || dataSeries.indexLabelFormatter) { this._indexLabels.push({ chartType: "splineArea", dataPoint: dataPoints[i], dataSeries: dataSeries, indexKeyword: 0, point: { x: x, y: y1 }, direction: dataPoints[i].y[0] <= dataPoints[i].y[1] ? -1 : 1, color: color }); this._indexLabels.push({ chartType: "splineArea", dataPoint: dataPoints[i], dataSeries: dataSeries, indexKeyword: 1, point: { x: x, y: y2 }, direction: dataPoints[i].y[0] <= dataPoints[i].y[1] ? 1 : -1, color: color }); } } renderBezierArea(); RenderHelper.drawMarkers(markers); } } ctx.restore(); if (isCanvasSupported) this._eventManager.ghostCtx.restore(); function renderBezierArea() { var bp = getBezierPoints(pixelsY1, 2); if (bp.length > 0) { ctx.beginPath(); ctx.moveTo(bp[0].x, bp[0].y); if (isCanvasSupported) { ghostCtx.beginPath(); ghostCtx.moveTo(bp[0].x, bp[0].y); } for (var i = 0; i < bp.length - 3; i += 3) { ctx.bezierCurveTo(bp[i + 1].x, bp[i + 1].y, bp[i + 2].x, bp[i + 2].y, bp[i + 3].x, bp[i + 3].y); if (isCanvasSupported) ghostCtx.bezierCurveTo(bp[i + 1].x, bp[i + 1].y, bp[i + 2].x, bp[i + 2].y, bp[i + 3].x, bp[i + 3].y); } if (dataSeries.lineThickness > 0) ctx.stroke(); bp = getBezierPoints(pixelsY2, 2); ctx.lineTo(pixelsY2[pixelsY2.length - 1].x, pixelsY2[pixelsY2.length - 1].y); for (var i = bp.length - 1; i > 2; i -= 3) { ctx.bezierCurveTo(bp[i - 1].x, bp[i - 1].y, bp[i - 2].x, bp[i - 2].y, bp[i - 3].x, bp[i - 3].y); if (isCanvasSupported) ghostCtx.bezierCurveTo(bp[i - 1].x, bp[i - 1].y, bp[i - 2].x, bp[i - 2].y, bp[i - 3].x, bp[i - 3].y); } ctx.closePath(); ctx.globalAlpha = dataSeries.fillOpacity; ctx.fill(); ctx.globalAlpha = 1; if (dataSeries.lineThickness > 0) { ctx.beginPath(); ctx.moveTo(pixelsY2[pixelsY2.length - 1].x, pixelsY2[pixelsY2.length - 1].y); for (var i = bp.length - 1; i > 2; i -= 3) { ctx.bezierCurveTo(bp[i - 1].x, bp[i - 1].y, bp[i - 2].x, bp[i - 2].y, bp[i - 3].x, bp[i - 3].y); if (isCanvasSupported) ghostCtx.bezierCurveTo(bp[i - 1].x, bp[i - 1].y, bp[i - 2].x, bp[i - 2].y, bp[i - 3].x, bp[i - 3].y); } ctx.stroke(); } ctx.beginPath(); if (isCanvasSupported) { ghostCtx.closePath(); ghostCtx.fill(); } } } //source and dest would be same when animation is not enabled var animationInfo = { source: ctx, dest: this.plotArea.ctx, animationCallback: AnimationHelper.xClipAnimation, easingFunction: AnimationHelper.easing.linear, animationBase: 0 }; return animationInfo; } //#region pieChart var drawSegment = function (ctx, center, radius, color, type, theta1, theta2, fillOpacity, percentInnerRadius) { if (typeof (fillOpacity) === "undefined") fillOpacity = 1; //IE8- FIX: In IE8- segment doesn't get draw if theta2 is equal to theta1 + 2*PI. if (!isCanvasSupported) { var theta2Mod = Number((theta2 % (2 * Math.PI)).toFixed(8)); var theta1Mod = Number((theta1 % (2 * Math.PI)).toFixed(8)); if (theta1Mod === theta2Mod) theta2 -= .0001; } ctx.save(); ctx.globalAlpha = fillOpacity; if (type === "pie") { ctx.beginPath(); ctx.moveTo(center.x, center.y); ctx.arc(center.x, center.y, radius, theta1, theta2, false); ctx.fillStyle = color; ctx.strokeStyle = "white"; ctx.lineWidth = 2; // ctx.shadowOffsetX = 2; // ctx.shadowOffsetY = 1; // ctx.shadowBlur = 2; // ctx.shadowColor = '#BFBFBF'; ctx.closePath(); //ctx.stroke(); ctx.fill(); } else if (type === "doughnut") { ctx.beginPath(); ctx.arc(center.x, center.y, radius, theta1, theta2, false); ctx.arc(center.x, center.y, percentInnerRadius * radius, theta2, theta1, true); ctx.closePath(); ctx.fillStyle = color; ctx.strokeStyle = "white"; ctx.lineWidth = 2; // shadow properties // ctx.shadowOffsetX = 1; // ctx.shadowOffsetY = 1; // ctx.shadowBlur = 1; // ctx.shadowColor = '#BFBFBF'; //grey shadow //ctx.stroke(); ctx.fill(); } ctx.globalAlpha = 1; ctx.restore(); }; function convertPercentToValue(input, referenceValue) { //input can be a number or string if (input === null || typeof (input) === "undefined") return referenceValue; var result = parseFloat(input.toString()) * (input.toString().indexOf("%") >= 0 ? referenceValue / 100 : 1); // limit to plot area if (!isNaN(result) && result <= referenceValue && result >= 0) return result; return referenceValue; } Chart.prototype.renderPie = function (plotUnit) { var _this = this; var totalDataSeries = plotUnit.dataSeriesIndexes.length; if (totalDataSeries <= 0) return; var dataSeriesIndex = plotUnit.dataSeriesIndexes[0]; var dataSeries = this.data[dataSeriesIndex]; var dataPoints = dataSeries.dataPoints; var indexLabelLineEdgeLength = 10; var explodeDuration = 500; var plotArea = this.plotArea; //var maxFrame = isCanvasSupported ? 300 : 4; //var totalRecursions = 0; var dataPointEOs = []; //dataPoint Extension Objects Behaves like a storage place for all additional data relating to dataPoints. Requred because actual dataPoints should not be modified. var minDistanceBetweenLabels = 2; var indexLabelRadiusToRadiusRatio = 1.3; var poleAnglularDistance = (20 / 180) * Math.PI; //Anglular Distance from 90 & 270 to be considered pole var precision = 6; var center = { x: (plotArea.x2 + plotArea.x1) / 2, y: (plotArea.y2 + plotArea.y1) / 2 }; var sum = 0; var isIndexLabelPresent = false; for (var j = 0; j < dataPoints.length; j++) { sum += Math.abs(dataPoints[j].y); if (!isIndexLabelPresent && typeof (dataPoints[j].indexLabel) !== "undefined" && dataPoints[j].indexLabel !== null && dataPoints[j].indexLabel.toString().length > 0) isIndexLabelPresent = true; if (!isIndexLabelPresent && typeof (dataPoints[j].label) !== "undefined" && dataPoints[j].label !== null && dataPoints[j].label.toString().length > 0) isIndexLabelPresent = true; } if (sum === 0) return; isIndexLabelPresent = isIndexLabelPresent || (typeof (dataSeries.indexLabel) !== "undefined" && dataSeries.indexLabel !== null && dataSeries.indexLabel.toString().length > 0); var outerRadius = dataSeries.indexLabelPlacement !== "inside" && isIndexLabelPresent ? (Math.min(plotArea.width, plotArea.height) * 0.75) / 2 : (Math.min(plotArea.width, plotArea.height) * .92) / 2; if (dataSeries.radius) outerRadius = convertPercentToValue(dataSeries.radius, outerRadius); var innerRadius = (typeof dataSeries.innerRadius !== 'undefined' && dataSeries.innerRadius !== null) ? convertPercentToValue(dataSeries.innerRadius, outerRadius) : 0.7 * outerRadius; var percentInnerRadius = Math.min(innerRadius / outerRadius, (outerRadius - 1) / outerRadius); function initLabels() { if (!dataSeries || !dataPoints) return; var noDPNearSouthPole = 0; var noDPNearNorthPole = 0; var firstDPCloseToSouth = 0; var firstDPCloseToNorth = 0; for (j = 0; j < dataPoints.length; j++) { var dataPoint = dataPoints[j]; var id = dataSeries.dataPointIds[j]; var dataPointEO = { id: id, objectType: "dataPoint", dataPointIndex: j, dataSeriesIndex: 0 }; dataPointEOs.push(dataPointEO); var percentAndTotal = { percent: null, total: null }; var formatterParameter = null; percentAndTotal = _this.getPercentAndTotal(dataSeries, dataPoint); if (dataSeries.indexLabelFormatter || dataPoint.indexLabelFormatter) formatterParameter = { chart: _this._options, dataSeries: dataSeries, dataPoint: dataPoint, total: percentAndTotal.total, percent: percentAndTotal.percent }; var indexLabelText = dataPoint.indexLabelFormatter ? dataPoint.indexLabelFormatter(formatterParameter) : dataPoint.indexLabel ? _this.replaceKeywordsWithValue(dataPoint.indexLabel, dataPoint, dataSeries, j) : dataSeries.indexLabelFormatter ? dataSeries.indexLabelFormatter(formatterParameter) : dataSeries.indexLabel ? _this.replaceKeywordsWithValue(dataSeries.indexLabel, dataPoint, dataSeries, j) : dataPoint.label ? dataPoint.label : ''; _this._eventManager.objectMap[id] = dataPointEO; //dataPointEO.indexLabelText = j.toString() + " " + "kingfisher: " + dataPoint.y.toString();; dataPointEO.center = { x: center.x, y: center.y }; dataPointEO.y = dataPoint.y; dataPointEO.radius = outerRadius; dataPointEO.percentInnerRadius = percentInnerRadius; dataPointEO.indexLabelText = indexLabelText; dataPointEO.indexLabelPlacement = dataSeries.indexLabelPlacement; dataPointEO.indexLabelLineColor = dataPoint.indexLabelLineColor ? dataPoint.indexLabelLineColor : dataSeries.indexLabelLineColor ? dataSeries.indexLabelLineColor : dataPoint.color ? dataPoint.color : dataSeries._colorSet[j % dataSeries._colorSet.length]; dataPointEO.indexLabelLineThickness = dataPoint.indexLabelLineThickness ? dataPoint.indexLabelLineThickness : dataSeries.indexLabelLineThickness; dataPointEO.indexLabelLineDashType = dataPoint.indexLabelLineDashType ? dataPoint.indexLabelLineDashType : dataSeries.indexLabelLineDashType; dataPointEO.indexLabelFontColor = dataPoint.indexLabelFontColor ? dataPoint.indexLabelFontColor : dataSeries.indexLabelFontColor; dataPointEO.indexLabelFontStyle = dataPoint.indexLabelFontStyle ? dataPoint.indexLabelFontStyle : dataSeries.indexLabelFontStyle; dataPointEO.indexLabelFontWeight = dataPoint.indexLabelFontWeight ? dataPoint.indexLabelFontWeight : dataSeries.indexLabelFontWeight; dataPointEO.indexLabelFontSize = dataPoint.indexLabelFontSize ? dataPoint.indexLabelFontSize : dataSeries.indexLabelFontSize; dataPointEO.indexLabelFontFamily = dataPoint.indexLabelFontFamily ? dataPoint.indexLabelFontFamily : dataSeries.indexLabelFontFamily; dataPointEO.indexLabelBackgroundColor = dataPoint.indexLabelBackgroundColor ? dataPoint.indexLabelBackgroundColor : dataSeries.indexLabelBackgroundColor ? dataSeries.indexLabelBackgroundColor : null; dataPointEO.indexLabelMaxWidth = dataPoint.indexLabelMaxWidth ? dataPoint.indexLabelMaxWidth : dataSeries.indexLabelMaxWidth ? dataSeries.indexLabelMaxWidth : plotArea.width * .33; dataPointEO.indexLabelWrap = typeof (dataPoint.indexLabelWrap) !== "undefined" ? dataPoint.indexLabelWrap : dataSeries.indexLabelWrap; dataPointEO.startAngle = j === 0 ? dataSeries.startAngle ? (dataSeries.startAngle / 180) * Math.PI : 0 : dataPointEOs[j - 1].endAngle; dataPointEO.startAngle = (dataPointEO.startAngle + (2 * Math.PI)) % (2 * Math.PI); dataPointEO.endAngle = dataPointEO.startAngle + ((2 * Math.PI / sum) * Math.abs(dataPoint.y)); //var midAngle = dataPointEO.startAngle + Math.abs(dataPointEO.endAngle - dataPointEO.startAngle) / 2; var midAngle = (dataPointEO.endAngle + dataPointEO.startAngle) / 2; //var midAngle = (180 / Math.PI * midAngle); midAngle = (midAngle + (2 * Math.PI)) % (2 * Math.PI); dataPointEO.midAngle = midAngle; if (dataPointEO.midAngle > (Math.PI / 2) - poleAnglularDistance && dataPointEO.midAngle < (Math.PI / 2) + poleAnglularDistance) { if (noDPNearSouthPole === 0 || dataPointEOs[firstDPCloseToSouth].midAngle > dataPointEO.midAngle) firstDPCloseToSouth = j; noDPNearSouthPole++; } else if (dataPointEO.midAngle > (3 * Math.PI / 2) - poleAnglularDistance && dataPointEO.midAngle < (3 * Math.PI / 2) + poleAnglularDistance) { if (noDPNearNorthPole === 0 || dataPointEOs[firstDPCloseToNorth].midAngle > dataPointEO.midAngle) firstDPCloseToNorth = j; noDPNearNorthPole++; } if (midAngle > (Math.PI / 2) && midAngle <= (3 * Math.PI / 2)) dataPointEO.hemisphere = "left"; else dataPointEO.hemisphere = "right"; //dataPointEO.indexLabelText = j.toString() + "; " + dataPoint.y.toString() + "; " + midAngle.toString() + "; junk"; dataPointEO.indexLabelTextBlock = new TextBlock(_this.plotArea.ctx, { fontSize: dataPointEO.indexLabelFontSize, fontFamily: dataPointEO.indexLabelFontFamily, fontColor: dataPointEO.indexLabelFontColor, fontStyle: dataPointEO.indexLabelFontStyle, fontWeight: dataPointEO.indexLabelFontWeight, horizontalAlign: "left", backgroundColor: dataPointEO.indexLabelBackgroundColor, maxWidth: dataPointEO.indexLabelMaxWidth, maxHeight: dataPointEO.indexLabelWrap ? dataPointEO.indexLabelFontSize * 5 : dataPointEO.indexLabelFontSize * 1.5, text: dataPointEO.indexLabelText, padding: 0, //textBaseline: dataPointEO.indexLabelBackgroundColor ? "middle" : "top" textBaseline: "top" }); dataPointEO.indexLabelTextBlock.measureText(); //dataPoint.labelWidth = ctx.measureText(j.toString() + "; " + dataPoint.label).width; //console.log(dataPoint.label); } var noOfDPToRightOfSouthPole = 0; var noOfDPToLeftOfNorthPole = 0; var keepSameDirection = false; // once a dataPoint's hemisphere is changed, others should follow the same so that there are no labes near pole pointing in opposite direction. for (j = 0; j < dataPoints.length; j++) { var dataPointEO = dataPointEOs[(firstDPCloseToSouth + j) % dataPoints.length]; if (noDPNearSouthPole > 1 && dataPointEO.midAngle > (Math.PI / 2) - poleAnglularDistance && dataPointEO.midAngle < (Math.PI / 2) + poleAnglularDistance) { if (noOfDPToRightOfSouthPole <= noDPNearSouthPole / 2 && !keepSameDirection) { dataPointEO.hemisphere = "right"; noOfDPToRightOfSouthPole++; } else { dataPointEO.hemisphere = "left"; keepSameDirection = true; } } } keepSameDirection = false; for (j = 0; j < dataPoints.length; j++) { var dataPointEO = dataPointEOs[(firstDPCloseToNorth + j) % dataPoints.length]; //if (dataPoint.hemisphere = "right") // break; if (noDPNearNorthPole > 1 && dataPointEO.midAngle > (3 * Math.PI / 2) - poleAnglularDistance && dataPointEO.midAngle < (3 * Math.PI / 2) + poleAnglularDistance) { if (noOfDPToLeftOfNorthPole <= noDPNearNorthPole / 2 && !keepSameDirection) { dataPointEO.hemisphere = "left"; noOfDPToLeftOfNorthPole++; } else { dataPointEO.hemisphere = "right"; keepSameDirection = true; } } } }//End of initLabels() function renderLabels() { var ctx = _this.plotArea.ctx; ctx.fillStyle = "black"; ctx.strokeStyle = "grey"; var fontSize = 16; //ctx.font = fontSize + "px Arial"; ctx.textBaseline = "middle"; ctx.lineJoin = "round"; var i = 0, j = 0; for (i = 0; i < dataPoints.length; i++) { var dataPointEO = dataPointEOs[i]; if (!dataPointEO.indexLabelText) continue; dataPointEO.indexLabelTextBlock.y -= dataPointEO.indexLabelTextBlock.height / 2; var xOffset = 0; if (dataPointEO.hemisphere === "left") { var xOffset = dataSeries.indexLabelPlacement !== "inside" ? -(dataPointEO.indexLabelTextBlock.width + indexLabelLineEdgeLength) : -dataPointEO.indexLabelTextBlock.width / 2; } else { var xOffset = dataSeries.indexLabelPlacement !== "inside" ? indexLabelLineEdgeLength : -dataPointEO.indexLabelTextBlock.width / 2; } dataPointEO.indexLabelTextBlock.x += xOffset; dataPointEO.indexLabelTextBlock.render(true); dataPointEO.indexLabelTextBlock.x -= xOffset; //if (i < 4) // customPrompt(i + "; " + center.y + "; " + dataPointEO.indexLabelTextBlock.y.toFixed(2)); dataPointEO.indexLabelTextBlock.y += dataPointEO.indexLabelTextBlock.height / 2; if (dataPointEO.indexLabelPlacement !== "inside") { var indexLabelLineStartX = dataPointEO.center.x + outerRadius * Math.cos(dataPointEO.midAngle); var indexLabelLineStartY = dataPointEO.center.y + outerRadius * Math.sin(dataPointEO.midAngle); //ctx.strokeStyle = dataPoints[i].color ? dataPoints[i].color : dataSeries._colorSet[i % dataSeries._colorSet.length]; ctx.strokeStyle = dataPointEO.indexLabelLineColor; ctx.lineWidth = dataPointEO.indexLabelLineThickness; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(dataPointEO.indexLabelLineDashType, dataPointEO.indexLabelLineThickness)); } //ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(indexLabelLineStartX, indexLabelLineStartY); ctx.lineTo(dataPointEO.indexLabelTextBlock.x, dataPointEO.indexLabelTextBlock.y); ctx.lineTo(dataPointEO.indexLabelTextBlock.x + (dataPointEO.hemisphere === "left" ? -indexLabelLineEdgeLength : indexLabelLineEdgeLength), dataPointEO.indexLabelTextBlock.y); ctx.stroke(); //ctx.closePath(); //window.alert("contine??"); //animate(); } ctx.lineJoin = "miter"; } } function animate(fractionComplete) { var ctx = _this.plotArea.ctx; ctx.clearRect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.fillStyle = _this.backgroundColor; ctx.fillRect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); var maxAngle = dataPointEOs[0].startAngle + (2 * Math.PI * fractionComplete); for (var i = 0; i < dataPoints.length; i++) { var startAngle = i === 0 ? dataPointEOs[i].startAngle : endAngle; var endAngle = startAngle + (dataPointEOs[i].endAngle - dataPointEOs[i].startAngle); var shouldBreak = false; if (endAngle > maxAngle) { endAngle = maxAngle; shouldBreak = true; } var color = dataPoints[i].color ? dataPoints[i].color : dataSeries._colorSet[i % dataSeries._colorSet.length]; if (endAngle > startAngle) drawSegment(_this.plotArea.ctx, dataPointEOs[i].center, dataPointEOs[i].radius, color, dataSeries.type, startAngle, endAngle, dataSeries.fillOpacity, dataPointEOs[i].percentInnerRadius); if (shouldBreak) break; } } function explodeToggle(fractionComplete) { var ctx = _this.plotArea.ctx; ctx.clearRect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); ctx.fillStyle = _this.backgroundColor; ctx.fillRect(plotArea.x1, plotArea.y1, plotArea.width, plotArea.height); for (var i = 0; i < dataPoints.length; i++) { var startAngle = dataPointEOs[i].startAngle; var endAngle = dataPointEOs[i].endAngle; if (endAngle > startAngle) { var offsetX = (outerRadius * .07 * Math.cos(dataPointEOs[i].midAngle)); var offsetY = (outerRadius * .07 * Math.sin(dataPointEOs[i].midAngle)); var isInTransition = false; if (dataPoints[i].exploded) { if (Math.abs(dataPointEOs[i].center.x - (center.x + offsetX)) > 0.000000001 || Math.abs(dataPointEOs[i].center.y - (center.y + offsetY)) > 0.000000001) { dataPointEOs[i].center.x = center.x + offsetX * fractionComplete; dataPointEOs[i].center.y = center.y + offsetY * fractionComplete; isInTransition = true; } } else if (Math.abs(dataPointEOs[i].center.x - center.x) > 0 || Math.abs(dataPointEOs[i].center.y - center.y) > 0) { dataPointEOs[i].center.x = center.x + offsetX * (1 - fractionComplete); dataPointEOs[i].center.y = center.y + offsetY * (1 - fractionComplete); isInTransition = true; } if (isInTransition) { var entry = { }; entry.dataSeries = dataSeries; entry.dataPoint = dataSeries.dataPoints[i]; entry.index = i; _this._toolTip.highlightObjects([entry]); } var color = dataPoints[i].color ? dataPoints[i].color : dataSeries._colorSet[i % dataSeries._colorSet.length]; drawSegment(_this.plotArea.ctx, dataPointEOs[i].center, dataPointEOs[i].radius, color, dataSeries.type, startAngle, endAngle, dataSeries.fillOpacity, dataPointEOs[i].percentInnerRadius); } } //window.alert("next??"); renderLabels(); } function areDataPointsTooClose(first, second) { var label1 = { x1: first.indexLabelTextBlock.x, y1: first.indexLabelTextBlock.y - first.indexLabelTextBlock.height / 2, x2: first.indexLabelTextBlock.x + first.indexLabelTextBlock.width, y2: first.indexLabelTextBlock.y + first.indexLabelTextBlock.height / 2 }; var label2 = { x1: second.indexLabelTextBlock.x, y1: second.indexLabelTextBlock.y - second.indexLabelTextBlock.height / 2, x2: second.indexLabelTextBlock.x + second.indexLabelTextBlock.width, y2: second.indexLabelTextBlock.y + second.indexLabelTextBlock.height / 2 }; if (label1.x2 < label2.x1 - indexLabelLineEdgeLength || label1.x1 > label2.x2 + indexLabelLineEdgeLength || label1.y1 > label2.y2 + indexLabelLineEdgeLength || label1.y2 < label2.y1 - indexLabelLineEdgeLength) return false; return true; } function getVerticalDistanceBetweenLabels(first, second) { var distance = 0; var label1 = { y: first.indexLabelTextBlock.y, y1: first.indexLabelTextBlock.y - first.indexLabelTextBlock.height / 2, y2: first.indexLabelTextBlock.y + first.indexLabelTextBlock.height / 2 }; var label2 = { y: second.indexLabelTextBlock.y, y1: second.indexLabelTextBlock.y - second.indexLabelTextBlock.height / 2, y2: second.indexLabelTextBlock.y + second.indexLabelTextBlock.height / 2 }; if (label2.y > label1.y) { distance = label2.y1 - label1.y2; } else { distance = label1.y1 - label2.y2; } return distance; } function getNextLabelIndex(currentLabelIndex) { var nextLabelIndex = null; for (var i = 1; i < dataPoints.length; i++) { nextLabelIndex = (currentLabelIndex + i + dataPointEOs.length) % dataPointEOs.length; if (dataPointEOs[nextLabelIndex].hemisphere !== dataPointEOs[currentLabelIndex].hemisphere) { nextLabelIndex = null; break; } else if ((dataPointEOs[nextLabelIndex].indexLabelText) && (nextLabelIndex !== currentLabelIndex) && ((getVerticalDistanceBetweenLabels(dataPointEOs[nextLabelIndex], dataPointEOs[currentLabelIndex]) < 0) || (dataPointEOs[currentLabelIndex].hemisphere === "right" ? dataPointEOs[nextLabelIndex].indexLabelTextBlock.y >= dataPointEOs[currentLabelIndex].indexLabelTextBlock.y : dataPointEOs[nextLabelIndex].indexLabelTextBlock.y <= dataPointEOs[currentLabelIndex].indexLabelTextBlock.y))) break; else { nextLabelIndex = null; } } return nextLabelIndex; } function getPreviousLabelIndex(currentLabelIndex) { var prevLabelIndex = null; for (var i = 1; i < dataPoints.length; i++) { prevLabelIndex = (currentLabelIndex - i + dataPointEOs.length) % dataPointEOs.length; if (dataPointEOs[prevLabelIndex].hemisphere !== dataPointEOs[currentLabelIndex].hemisphere) { prevLabelIndex = null; break; } else if ((dataPointEOs[prevLabelIndex].indexLabelText) && (dataPointEOs[prevLabelIndex].hemisphere === dataPointEOs[currentLabelIndex].hemisphere) && (prevLabelIndex !== currentLabelIndex) && ((getVerticalDistanceBetweenLabels(dataPointEOs[prevLabelIndex], dataPointEOs[currentLabelIndex]) < 0) || (dataPointEOs[currentLabelIndex].hemisphere === "right" ? dataPointEOs[prevLabelIndex].indexLabelTextBlock.y <= dataPointEOs[currentLabelIndex].indexLabelTextBlock.y : dataPointEOs[prevLabelIndex].indexLabelTextBlock.y >= dataPointEOs[currentLabelIndex].indexLabelTextBlock.y))) break; else { prevLabelIndex = null; } } return prevLabelIndex; } function rePositionLabels(dataPointIndex, offset, recursionCount) { recursionCount = (recursionCount || 0) + 1; if (recursionCount > 1000) return 0; offset = offset || 0; var actualOffset = 0; //var labelYMin = 2; //var labelYMax = ctx.canvas.height - 2; //var labelYMin = _this.plotArea.ctx.canvas.height / 2 - indexLabelRadius * 1; //var labelYMax = _this.plotArea.ctx.canvas.height / 2 + indexLabelRadius * 1; var labelYMin = center.y - indexLabelRadius * 1; var labelYMax = center.y + indexLabelRadius * 1; //console.log(totalRecursions); if (dataPointIndex >= 0 && dataPointIndex < dataPoints.length) { var dataPointEO = dataPointEOs[dataPointIndex]; //if (dataPointIndex === 0) // customPrompt(labelYMin.toFixed(2) + "; " + labelYMax.toFixed(2) + "; " + dataPointEO.indexLabelTextBlock.y.toFixed(2)); // If label is already outside the bounds, return if ((offset < 0 && dataPointEO.indexLabelTextBlock.y < labelYMin) || (offset > 0 && dataPointEO.indexLabelTextBlock.y > labelYMax)) return 0; var validOffset = offset; //Check if the offset falls within the bounds (labelYMin, labelYMax, tangential bounds) without considering overlap. Else use the closest offset that is possible - validOffset. { var distFromIndexLineStart = 0; var indexLabelLineStartX = 0; var indexLabelLineStartY = 0; var indexLabelAngle = 0; var indexLabelAngleWhenTangent = 0; if (validOffset < 0) { if (dataPointEO.indexLabelTextBlock.y - dataPointEO.indexLabelTextBlock.height / 2 > labelYMin && dataPointEO.indexLabelTextBlock.y - dataPointEO.indexLabelTextBlock.height / 2 + validOffset < labelYMin) validOffset = -(labelYMin - (dataPointEO.indexLabelTextBlock.y - dataPointEO.indexLabelTextBlock.height / 2 + validOffset)); } else { if (dataPointEO.indexLabelTextBlock.y + dataPointEO.indexLabelTextBlock.height / 2 < labelYMin && dataPointEO.indexLabelTextBlock.y + dataPointEO.indexLabelTextBlock.height / 2 + validOffset > labelYMax) validOffset = (dataPointEO.indexLabelTextBlock.y + dataPointEO.indexLabelTextBlock.height / 2 + validOffset) - labelYMax; } var newlabelY = dataPointEO.indexLabelTextBlock.y + validOffset; var newlabelX = 0; if (dataPointEO.hemisphere === "right") { newlabelX = center.x + Math.sqrt(Math.pow(indexLabelRadius, 2) - Math.pow(newlabelY - center.y, 2)); } else newlabelX = center.x - Math.sqrt(Math.pow(indexLabelRadius, 2) - Math.pow(newlabelY - center.y, 2)); indexLabelLineStartX = center.x + outerRadius * Math.cos(dataPointEO.midAngle); indexLabelLineStartY = center.y + outerRadius * Math.sin(dataPointEO.midAngle); distFromIndexLineStart = Math.sqrt(Math.pow(newlabelX - indexLabelLineStartX, 2) + Math.pow(newlabelY - indexLabelLineStartY, 2)); indexLabelAngleWhenTangent = Math.acos(outerRadius / indexLabelRadius); //indexLabelAngle = Math.acos((outerRadius * outerRadius + distFromIndexLineStart * distFromIndexLineStart - indexLabelRadius * indexLabelRadius) / (2 * outerRadius * distFromIndexLineStart)); indexLabelAngle = Math.acos((indexLabelRadius * indexLabelRadius + outerRadius * outerRadius - distFromIndexLineStart * distFromIndexLineStart) / (2 * outerRadius * indexLabelRadius)); if (indexLabelAngle < indexLabelAngleWhenTangent) { validOffset = newlabelY - dataPointEO.indexLabelTextBlock.y; //dataPointEO.indexLabelTextBlock.x = newlabelX; } else { validOffset = 0; //dataPointEO.indexLabelTextBlock.x = newlabelX; //Index Line is overlapping the pie. So lets find out the point where indexline becomes a tangent. //distFromIndexLineStart = Math.sqrt(indexLabelRadius * indexLabelRadius - outerRadius * outerRadius); ////distFromIndexLineStart *= offset < 0 ? -1 : 1; ////indexLabelAngle = Math.acos((indexLabelRadius * indexLabelRadius + outerRadius * outerRadius - distFromIndexLineStart * distFromIndexLineStart) / (2 * outerRadius * indexLabelRadius)); //indexLabelAngle = Math.atan2(distFromIndexLineStart, outerRadius); //newlabelX = center.x + indexLabelRadius * Math.cos(indexLabelAngle); //newlabelY = center.y + indexLabelRadius * Math.sin(indexLabelAngle); //actualOffset = newlabelY - dataPointEO.indexLabelTextBlock.y; //dataPointEO.indexLabelTextBlock.y = newlabelY; //dataPointEO.indexLabelTextBlock.x = newlabelX; } } //var tempIndex = (dataPointIndex + dataPointEOs.length - 1) % dataPointEOs.length; //var prevDataPointIndex = dataPointEOs[tempIndex].hemisphere === dataPointEO.hemisphere ? tempIndex : null; var prevDataPointIndex = getPreviousLabelIndex(dataPointIndex); //tempIndex = (dataPointIndex + dataPointEOs.length + 1) % dataPointEOs.length; //var nextDataPointIndex = dataPointEOs[tempIndex].hemisphere === dataPointEO.hemisphere ? tempIndex : null; var nextDataPointIndex = getNextLabelIndex(dataPointIndex); var otherdataPointEO, otherDataPointIndex, distanceFromOtherLabel; var otherDataPointOffset = 0; var otherDataPointActualOffset = 0; if (validOffset < 0) { otherDataPointIndex = dataPointEO.hemisphere === "right" ? prevDataPointIndex : nextDataPointIndex; actualOffset = validOffset; if (otherDataPointIndex !== null) { //if (dataPointIndex < 4) // customPrompt("valid: " + validOffset); var tempOffset = -validOffset; var distanceFromOtherLabel = (dataPointEO.indexLabelTextBlock.y - dataPointEO.indexLabelTextBlock.height / 2) - (dataPointEOs[otherDataPointIndex].indexLabelTextBlock.y + dataPointEOs[otherDataPointIndex].indexLabelTextBlock.height / 2); if (distanceFromOtherLabel - tempOffset < minDistanceBetweenLabels) { otherDataPointOffset = -tempOffset; //totalRecursions++; otherDataPointActualOffset = rePositionLabels(otherDataPointIndex, otherDataPointOffset, recursionCount + 1); //if (dataPointIndex < 4) // customPrompt(dataPointIndex + "; " + "offset: " + otherDataPointOffset); if (+otherDataPointActualOffset.toFixed(precision) > +otherDataPointOffset.toFixed(precision)) { if (distanceFromOtherLabel > minDistanceBetweenLabels) actualOffset = -(distanceFromOtherLabel - minDistanceBetweenLabels); //else // actualOffset = 0; else actualOffset = -(tempOffset - (otherDataPointActualOffset - otherDataPointOffset)); } //if (dataPointIndex < 4) // customPrompt("actual: " + actualOffset); } } } else if (validOffset > 0) { otherDataPointIndex = dataPointEO.hemisphere === "right" ? nextDataPointIndex : prevDataPointIndex; actualOffset = validOffset; if (otherDataPointIndex !== null) { var tempOffset = validOffset; var distanceFromOtherLabel = (dataPointEOs[otherDataPointIndex].indexLabelTextBlock.y - dataPointEOs[otherDataPointIndex].indexLabelTextBlock.height / 2) - (dataPointEO.indexLabelTextBlock.y + dataPointEO.indexLabelTextBlock.height / 2); if (distanceFromOtherLabel - tempOffset < minDistanceBetweenLabels) { otherDataPointOffset = tempOffset; //totalRecursions++; otherDataPointActualOffset = rePositionLabels(otherDataPointIndex, otherDataPointOffset, recursionCount + 1); if (+otherDataPointActualOffset.toFixed(precision) < +otherDataPointOffset.toFixed(precision)) { if (distanceFromOtherLabel > minDistanceBetweenLabels) actualOffset = distanceFromOtherLabel - minDistanceBetweenLabels; //else // actualOffset = 0; else actualOffset = tempOffset - (otherDataPointOffset - otherDataPointActualOffset); } } } //if (!(dataPointEO.indexLabelTextBlock.y + dataPointEO.indexLabelTextBlock.height / 2 + actualOffset < labelYMax)) { // if (dataPointEO.indexLabelTextBlock.y + dataPointEO.indexLabelTextBlock.height / 2 < labelYMax) { // actualOffset = labelYMax - (dataPointEO.indexLabelTextBlock.y + dataPointEO.indexLabelTextBlock.height / 2); // } // else { // actualOffset = 0; // } //} } if (actualOffset) { var newLabelY = dataPointEO.indexLabelTextBlock.y + actualOffset; var newLabelX = 0; if (dataPointEO.hemisphere === "right") { newLabelX = center.x + Math.sqrt(Math.pow(indexLabelRadius, 2) - Math.pow(newLabelY - center.y, 2)); } else newLabelX = center.x - Math.sqrt(Math.pow(indexLabelRadius, 2) - Math.pow(newLabelY - center.y, 2)); if (dataPointEO.midAngle > (Math.PI / 2) - poleAnglularDistance && dataPointEO.midAngle < (Math.PI / 2) + poleAnglularDistance) { var prevDPIndex = (dataPointIndex - 1 + dataPointEOs.length) % dataPointEOs.length; var prevDP = dataPointEOs[prevDPIndex]; var nextDP = dataPointEOs[(dataPointIndex + 1 + dataPointEOs.length) % dataPointEOs.length]; if (dataPointEO.hemisphere === "left" && prevDP.hemisphere === "right" && newLabelX > prevDP.indexLabelTextBlock.x) { newLabelX = prevDP.indexLabelTextBlock.x - 15; } else if (dataPointEO.hemisphere === "right" && nextDP.hemisphere === "left" && newLabelX < nextDP.indexLabelTextBlock.x) { newLabelX = nextDP.indexLabelTextBlock.x + 15; } } else if (dataPointEO.midAngle > (3 * Math.PI / 2) - poleAnglularDistance && dataPointEO.midAngle < (3 * Math.PI / 2) + poleAnglularDistance) { var prevDPIndex = (dataPointIndex - 1 + dataPointEOs.length) % dataPointEOs.length; var prevDP = dataPointEOs[prevDPIndex]; var nextDP = dataPointEOs[(dataPointIndex + 1 + dataPointEOs.length) % dataPointEOs.length]; if (dataPointEO.hemisphere === "right" && prevDP.hemisphere === "left" && newLabelX < prevDP.indexLabelTextBlock.x) { newLabelX = prevDP.indexLabelTextBlock.x + 15; } else if (dataPointEO.hemisphere === "left" && nextDP.hemisphere === "right" && newLabelX > nextDP.indexLabelTextBlock.x) { newLabelX = nextDP.indexLabelTextBlock.x - 15; } } //if (actualOffset < 0 && dataPointIndex < 4) // customPrompt(actualOffset.toFixed(2) + "; " + dataPointEO.indexLabelTextBlock.y.toFixed(2) + "; " + newLabelY.toFixed(2)); dataPointEO.indexLabelTextBlock.y = newLabelY; dataPointEO.indexLabelTextBlock.x = newLabelX; dataPointEO.indexLabelAngle = Math.atan2((dataPointEO.indexLabelTextBlock.y - center.y), (dataPointEO.indexLabelTextBlock.x - center.x)); } } return actualOffset; } function positionLabels() { var ctx = _this.plotArea.ctx; ctx.fillStyle = "grey"; ctx.strokeStyle = "grey"; var fontSize = 16; ctx.font = fontSize + "px Arial"; ctx.textBaseline = "middle"; var i = 0, j = 0; var deltaR = 0; var resizeFlag = true; for (j = 0; j < 10 && (j < 1 || deltaR > 0) ; j++) { if (dataSeries.radius || (!dataSeries.radius && typeof dataSeries.innerRadius !== 'undefined' && dataSeries.innerRadius !== null && outerRadius - deltaR <= innerRadius)) resizeFlag = false; if (resizeFlag) outerRadius -= deltaR; deltaR = 0; if (dataSeries.indexLabelPlacement !== "inside") { indexLabelRadius = outerRadius * indexLabelRadiusToRadiusRatio; for (i = 0; i < dataPoints.length; i++) { var dataPointEO = dataPointEOs[i]; dataPointEO.indexLabelTextBlock.x = center.x + indexLabelRadius * Math.cos(dataPointEO.midAngle); dataPointEO.indexLabelTextBlock.y = center.y + indexLabelRadius * Math.sin(dataPointEO.midAngle); dataPointEO.indexLabelAngle = dataPointEO.midAngle; dataPointEO.radius = outerRadius; dataPointEO.percentInnerRadius = percentInnerRadius; //dataPointEO.indexLabelFontSize = dataPoint.indexLabelFontSize ? dataPoint.indexLabelFontSize : dataSeries.indexLabelFontSize; } var currentDataPoint, nextDataPoint; for (i = 0; i < dataPoints.length; i++) { var dataPointEO = dataPointEOs[i]; //dataPointEO.lab //resetAnimationFrame(); //animate(); //renderLabels(); //var prevDataPointIndex = (i - 1 + dataPointEOs.length) % dataPointEOs.length; //var nextDataPointIndex = (i + 1 + dataPointEOs.length) % dataPointEOs.length; //nextDataPointIndex = dataPointEOs[nextDataPointIndex].hemisphere === dataPointEO.hemisphere && nextDataPointIndex !== i ? nextDataPointIndex : null; var nextDataPointIndex = getNextLabelIndex(i); if (nextDataPointIndex === null) continue; currentDataPoint = dataPointEOs[i]; nextDataPoint = dataPointEOs[nextDataPointIndex]; var distanceFromNextLabel = 0; //if (dataPointEO.hemisphere === "right") // distanceFromNextLabel = (nextDataPoint.indexLabelTextBlock.y - nextDataPoint.indexLabelTextBlock.height / 2) - (currentDataPoint.indexLabelTextBlock.y + currentDataPoint.indexLabelTextBlock.height / 2) - minDistanceBetweenLabels; //else // distanceFromNextLabel = (currentDataPoint.indexLabelTextBlock.y - currentDataPoint.indexLabelTextBlock.height / 2) - (nextDataPoint.indexLabelTextBlock.y + nextDataPoint.indexLabelTextBlock.height / 2) - minDistanceBetweenLabels; distanceFromNextLabel = getVerticalDistanceBetweenLabels(currentDataPoint, nextDataPoint) - minDistanceBetweenLabels; if (distanceFromNextLabel < 0) { var dataPointsAbove = 0; var dataPointsBelow = 0; //var indexLabelAngleWhenTangent = Math.acos(outerRadius / indexLabelRadius) / Math.PI * 180; for (var k = 0; k < dataPoints.length; k++) { if (k === i) continue; //if (dataPointEOs[k].hemisphere !== dataPointEO.hemisphere || Math.abs(dataPointEOs[k].midAngle - dataPointEO.midAngle) > 30) // continue; //if (dataPointEOs[k].hemisphere !== dataPointEO.hemisphere || Math.abs(dataPointEOs[k].labelAngle - dataPointEO.indexLabelAngle) > 30) // continue; //if (dataPointEOs[k].hemisphere !== dataPointEO.hemisphere || Math.abs(dataPointEOs[k].midAngle - dataPointEO.midAngle) > indexLabelAngleWhenTangent) // continue; if (dataPointEOs[k].hemisphere !== dataPointEO.hemisphere) continue; if (dataPointEOs[k].indexLabelTextBlock.y < dataPointEO.indexLabelTextBlock.y) dataPointsAbove++; else dataPointsBelow++; } //var upWardsOffset = (distanceFromNextLabel) / dataPoints.length * (dataPointsBelow); var upWardsOffset = (distanceFromNextLabel) / (dataPointsAbove + dataPointsBelow || 1) * (dataPointsBelow); var downWardsOffset = -1 * (distanceFromNextLabel - upWardsOffset); var actualUpwardOffset = 0; var actualDownwardOffset = 0; if (dataPointEO.hemisphere === "right") { actualUpwardOffset = rePositionLabels(i, upWardsOffset); //if (i < 4 && actualDownwardOffset !== upWardsOffset) // customPrompt(i + "; " + upWardsOffset.toFixed(2) + "; " + actualUpwardOffset.toFixed(2)); downWardsOffset = -1 * (distanceFromNextLabel - actualUpwardOffset); actualDownwardOffset = rePositionLabels(nextDataPointIndex, downWardsOffset); //window.alert(typeof +downWardsOffset.toFixed(precision)); //Setting precision to make sure that they don't become not equal become of minor differences - like a difference of .000001 if (+actualDownwardOffset.toFixed(precision) < +downWardsOffset.toFixed(precision) && +actualUpwardOffset.toFixed(precision) <= +upWardsOffset.toFixed(precision)) rePositionLabels(i, -(downWardsOffset - actualDownwardOffset)); } else { actualUpwardOffset = rePositionLabels(nextDataPointIndex, upWardsOffset); downWardsOffset = -1 * (distanceFromNextLabel - actualUpwardOffset); actualDownwardOffset = rePositionLabels(i, downWardsOffset); //Setting precision to make sure that they don't become not equal become of minor differences - like a difference of .000001 if (+actualDownwardOffset.toFixed(precision) < +downWardsOffset.toFixed(precision) && +actualUpwardOffset.toFixed(precision) <= +upWardsOffset.toFixed(precision)) rePositionLabels(nextDataPointIndex, -(downWardsOffset - actualDownwardOffset)); } } //resetAnimationFrame(); //animate(); //renderLabels(); //window.alert("next??"); } } else { for (i = 0; i < dataPoints.length; i++) { var dataPointEO = dataPointEOs[i]; indexLabelRadius = dataSeries.type === "pie" ? outerRadius * .7 : outerRadius * .8; var dx = center.x + indexLabelRadius * (Math.cos((dataPointEO.midAngle))); var dy = center.y + indexLabelRadius * (Math.sin((dataPointEO.midAngle))); dataPointEO.indexLabelTextBlock.x = dx; dataPointEO.indexLabelTextBlock.y = dy; } } // Resize Pie based on the label length. for (i = 0; i < dataPoints.length; i++) { dataPointEO = dataPointEOs[i]; var size = dataPointEO.indexLabelTextBlock.measureText(); // To make sure that null text or empty strings don't affect the radius. Required when user is not showing any labels if (size.height === 0 || size.width === 0) continue; var xOverflow = 0; var xdr = 0; if (dataPointEO.hemisphere === "right") { xOverflow = plotArea.x2 - (dataPointEO.indexLabelTextBlock.x + dataPointEO.indexLabelTextBlock.width + indexLabelLineEdgeLength); xOverflow *= -1; } else { xOverflow = plotArea.x1 - (dataPointEO.indexLabelTextBlock.x - dataPointEO.indexLabelTextBlock.width - indexLabelLineEdgeLength); } if (xOverflow > 0) { if (!resizeFlag && dataPointEO.indexLabelText) { var newIndexLabelMaxWidth = dataPointEO.hemisphere === "right" ? plotArea.x2 - dataPointEO.indexLabelTextBlock.x : dataPointEO.indexLabelTextBlock.x - plotArea.x1; dataPointEO.indexLabelTextBlock.maxWidth * .3 > newIndexLabelMaxWidth ? dataPointEO.indexLabelText = "" : dataPointEO.indexLabelTextBlock.maxWidth = newIndexLabelMaxWidth * .85; if (dataPointEO.indexLabelTextBlock.maxWidth * .3 < newIndexLabelMaxWidth) dataPointEO.indexLabelTextBlock.x -= dataPointEO.hemisphere === "right" ? 2 : -2; } if (Math.abs(dataPointEO.indexLabelTextBlock.y - dataPointEO.indexLabelTextBlock.height / 2 - center.y) < outerRadius || Math.abs(dataPointEO.indexLabelTextBlock.y + dataPointEO.indexLabelTextBlock.height / 2 - center.y) < outerRadius) { xdr = xOverflow / Math.abs(Math.cos(dataPointEO.indexLabelAngle)); if (xdr > 9) xdr = xdr * .3; if (xdr > deltaR) deltaR = xdr; } } var yOverflow = 0; var ydr = 0; if (dataPointEO.indexLabelAngle > 0 && dataPointEO.indexLabelAngle < Math.PI) { yOverflow = plotArea.y2 - (dataPointEO.indexLabelTextBlock.y + dataPointEO.indexLabelTextBlock.height / 2 + 5); yOverflow *= -1; } else { yOverflow = plotArea.y1 - (dataPointEO.indexLabelTextBlock.y - dataPointEO.indexLabelTextBlock.height / 2 - 5); } if (yOverflow > 0) { if (!resizeFlag && dataPointEO.indexLabelText) { var positionMultiplier = dataPointEO.indexLabelAngle > 0 && dataPointEO.indexLabelAngle < Math.PI ? -1 : 1; if (rePositionLabels(i, yOverflow * positionMultiplier) === 0) rePositionLabels(i, 2 * positionMultiplier); } if (Math.abs(dataPointEO.indexLabelTextBlock.x - center.x) < outerRadius) { ydr = yOverflow / Math.abs(Math.sin(dataPointEO.indexLabelAngle)); if (ydr > 9) ydr = ydr * .3; if (ydr > deltaR) deltaR = ydr; } } } function removeLabelsForSmallSegments(totalOverlap, startIndex, endIndex) { var dpEOs = []; var totalRemovedLabelHeight = 0; for (var i = startIndex; true; i = (i + 1 + dataPoints.length) % dataPoints.length) { dpEOs.push(dataPointEOs[i]); if (i === endIndex) break; } dpEOs.sort(function (entry1, entry2) { return entry1.y - entry2.y; }); for (i = 0; i < dpEOs.length; i++) { var dpEO = dpEOs[i]; if (totalRemovedLabelHeight < totalOverlap * .7) { totalRemovedLabelHeight += dpEO.indexLabelTextBlock.height; dpEO.indexLabelTextBlock.text = ""; dpEO.indexLabelText = ""; dpEO.indexLabelTextBlock.measureText(); } else break; } } //resetAnimationFrame(1); //animate(); //window.alert("next??"); function skipLabels() { var overlapStartIndex = -1; var overlapEndIndex = -1; var totalOverlap = 0; var removeLabels = false; for (var k = 0; k < dataPoints.length; k++) { removeLabels = false; currentDataPoint = dataPointEOs[k]; if (!currentDataPoint.indexLabelText) continue; var nextLabelIndex = getNextLabelIndex(k); if (nextLabelIndex === null) continue; var nextDataPoint = dataPointEOs[nextLabelIndex]; distanceFromNextLabel = 0; //if (nextDataPoint.indexLabelTextBlock.y > currentDataPoint.indexLabelTextBlock.y) // distanceFromNextLabel = (nextDataPoint.indexLabelTextBlock.y - (nextDataPoint.indexLabelTextBlock.height / 2)) - (currentDataPoint.indexLabelTextBlock.y + (currentDataPoint.indexLabelTextBlock.height / 2)); //else // distanceFromNextLabel = (currentDataPoint.indexLabelTextBlock.y - (currentDataPoint.indexLabelTextBlock.height / 2)) - (nextDataPoint.indexLabelTextBlock.y + (nextDataPoint.indexLabelTextBlock.height / 2)); distanceFromNextLabel = getVerticalDistanceBetweenLabels(currentDataPoint, nextDataPoint); if (distanceFromNextLabel < 0 && areDataPointsTooClose(currentDataPoint, nextDataPoint)) { //if (distanceFromNextLabel < 0 && areDataPointsTooClose(currentDataPoint, nextDataPoint) ) { if (overlapStartIndex < 0) overlapStartIndex = k; if (nextLabelIndex !== overlapStartIndex) { overlapEndIndex = nextLabelIndex; totalOverlap += -distanceFromNextLabel; } if (k % Math.max(dataPoints.length / 10, 3) === 0) removeLabels = true; //nextDataPoint.indexLabelText = ""; //nextDataPoint.indexLabelTextBlock.text = ""; //nextDataPoint.indexLabelTextBlock.measureText(); } else { removeLabels = true; } if (removeLabels) { if (totalOverlap > 0 && overlapStartIndex >= 0 && overlapEndIndex >= 0) { removeLabelsForSmallSegments(totalOverlap, overlapStartIndex, overlapEndIndex); overlapStartIndex = -1; overlapEndIndex = -1; totalOverlap = 0; } } } if (totalOverlap > 0) removeLabelsForSmallSegments(totalOverlap, overlapStartIndex, overlapEndIndex); } skipLabels(); } //window.alert("next??"); //resetAnimationFrame(_this.animationEnabled && _this.renderCount === 0 ? isCanvasSupported ? 60 : 30 : 1); //animate(); //console.log("totalRecursions: " + totalRecursions); } this.pieDoughnutClickHandler = function (e) { if (_this.isAnimating) { return; } var i = e.dataPointIndex; var dataPoint = e.dataPoint; var dataSeries = this; var id = dataSeries.dataPointIds[i]; //dataPointEO = _this._eventManager.objectMap[id]; if (dataPoint.exploded) dataPoint.exploded = false; else dataPoint.exploded = true; // So that it doesn't try to explode when there is only one segment if (dataSeries.dataPoints.length > 1) { _this._animator.animate(0, explodeDuration, function (fractionComplete) { explodeToggle(fractionComplete); renderChartElementsInPlotArea(); //console.log("Explode Start"); }); } return; } initLabels(); positionLabels(); positionLabels(); positionLabels(); positionLabels(); this.disableToolTip = true; this._animator.animate(0, this.animatedRender ? this.animationDuration : 0, function (fractionComplete) { animate(fractionComplete); renderChartElementsInPlotArea(); }, function () { _this.disableToolTip = false; _this._animator.animate(0, _this.animatedRender ? explodeDuration : 0, function (fractionComplete) { explodeToggle(fractionComplete); renderChartElementsInPlotArea(); }); //console.log("Animation Complete"); }); function renderChartElementsInPlotArea() { _this.plotArea.layoutManager.reset(); if (_this._title) { if (_this._title.dockInsidePlotArea || (_this._title.horizontalAlign === "center" && _this._title.verticalAlign === "center")) _this._title.render(); } if (_this.subtitles) for (var i = 0; i < _this.subtitles.length; i++) { var subtitle = _this.subtitles[i]; if (subtitle.dockInsidePlotArea || (subtitle.horizontalAlign === "center" && subtitle.verticalAlign === "center")) subtitle.render(); } if (_this.legend) { if (_this.legend.dockInsidePlotArea || (_this.legend.horizontalAlign === "center" && _this.legend.verticalAlign === "center")) _this.legend.render(); } } //this.ctx.strokeRect(plotArea.x1 + 1, plotArea.y1, plotArea.width - 2, plotArea.height); } //#endregion pieChart //#endregion Render Methods Chart.prototype.animationRequestId = null; Chart.prototype.requestAnimFrame = (function () { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; })(); Chart.prototype.cancelRequestAnimFrame = (function () { return window.cancelAnimationFrame || window.webkitCancelRequestAnimationFrame || window.mozCancelRequestAnimationFrame || window.oCancelRequestAnimationFrame || window.msCancelRequestAnimationFrame || clearTimeout })(); //#endregion Class Chart //#region Class LayoutManager function LayoutManager(x1, y1, x2, y2, padding) { if (typeof (padding) === "undefined") padding = 0; this._padding = padding; this._x1 = x1; this._y1 = y1; this._x2 = x2; this._y2 = y2; this._topOccupied = this._padding; this._bottomOccupied = this._padding; this._leftOccupied = this._padding; this._rightOccupied = this._padding; } LayoutManager.prototype.registerSpace = function (position, size) { if (position === "top") { this._topOccupied += size.height; } else if (position === "bottom") { this._bottomOccupied += size.height; } else if (position === "left") { this._leftOccupied += size.width; // this is width when seen upright/vertically } else if (position === "right") { this._rightOccupied += size.width;// this is width when seen upright/vertically } } LayoutManager.prototype.unRegisterSpace = function (position, size) { if (position === "top") { this._topOccupied -= size.height; } else if (position === "bottom") { this._bottomOccupied -= size.height; } else if (position === "left") { this._leftOccupied -= size.width;// this is width when seen upright/vertically } else if (position === "right") { this._rightOccupied -= size.width;// this is width when seen upright/vertically } } LayoutManager.prototype.getFreeSpace = function () { /// ///Returns available free space {x1:number, y1:number, x2:number, y2:number} /// return { x1: this._x1 + this._leftOccupied, y1: this._y1 + this._topOccupied, x2: this._x2 - this._rightOccupied, y2: this._y2 - this._bottomOccupied, width: (this._x2 - this._x1) - this._rightOccupied - this._leftOccupied, height: (this._y2 - this._y1) - this._bottomOccupied - this._topOccupied }; } LayoutManager.prototype.reset = function () { //so that there is enough padding. this._topOccupied = this._padding; this._bottomOccupied = this._padding; this._leftOccupied = this._padding; this._rightOccupied = this._padding; } //#endregion Class LayoutManager //#region Class TextBlock function TextBlock(ctx, options) { TextBlock.base.constructor.call(this, "TextBlock", options); this.ctx = ctx; this._isDirty = true; this._wrappedText = null; this._lineHeight = getFontHeightInPixels(this.fontFamily, this.fontSize, this.fontWeight); } extend(TextBlock, CanvasJSObject); TextBlock.prototype.render = function (preserveContext) { if (preserveContext) this.ctx.save(); var font = this.ctx.font; this.ctx.textBaseline = this.textBaseline; var offsetY = 0; if (this._isDirty) this.measureText(this.ctx); this.ctx.translate(this.x, this.y + offsetY); if (this.textBaseline === "middle") { offsetY = -this._lineHeight / 2; } this.ctx.font = this._getFontString(); this.ctx.rotate(Math.PI / 180 * this.angle); var textLeft = 0; var textTop = this.padding; //var textTop = this.padding; var line = null; if ((this.borderThickness > 0 && this.borderColor) || this.backgroundColor) { this.ctx.roundRect(0, offsetY, this.width, this.height, this.cornerRadius, this.borderThickness, this.backgroundColor, this.borderColor); //if (this.textBaseline === "middle") { // //textTop += this.fontSize / 2; // textTop += this._lineHeight / 2; //} } this.ctx.fillStyle = this.fontColor; for (var i = 0; i < this._wrappedText.lines.length; i++) { line = this._wrappedText.lines[i]; if (this.horizontalAlign === "right") textLeft = this.width - line.width - this.padding; else if (this.horizontalAlign === "left") textLeft = this.padding; else if (this.horizontalAlign === "center") textLeft = (this.width - this.padding * 2) / 2 - line.width / 2 + this.padding; this.ctx.fillText(line.text, textLeft, textTop); textTop += line.height; } this.ctx.font = font; if (preserveContext) this.ctx.restore(); } TextBlock.prototype.setText = function (text) { this.text = text; this._isDirty = true; this._wrappedText = null; } TextBlock.prototype.measureText = function () { if (this.maxWidth === null) { throw ("Please set maxWidth and height for TextBlock"); } this._wrapText(this.ctx); this._isDirty = false; return { width: this.width, height: this.height } } TextBlock.prototype._getLineWithWidth = function (text, width, clipWord) { text = String(text); clipWord = clipWord || false; if (!text) return { text: "", width: 0 }; var textWidth = 0, min = 0, max = text.length - 1, mid = Infinity; this.ctx.font = this._getFontString(); while (min <= max) { mid = Math.floor((min + max) / 2); var tempText = text.substr(0, mid + 1); textWidth = this.ctx.measureText(tempText).width; if (textWidth < width) { min = mid + 1; } else if (textWidth > width) { max = mid - 1; } else { break; } } //edge cases if (textWidth > width && tempText.length > 1) { tempText = tempText.substr(0, tempText.length - 1); textWidth = this.ctx.measureText(tempText).width; } var isClipped = true; if (tempText.length === text.length || text[tempText.length] === " ") isClipped = false; if (isClipped) { var resultWords = tempText.split(" "); if (resultWords.length > 1) resultWords.pop(); tempText = resultWords.join(" "); textWidth = this.ctx.measureText(tempText).width; } return { text: tempText, width: textWidth }; } TextBlock.prototype._wrapText = function wrapText() { //this.ctx.save(); var text = new String(trimString(String(this.text))); var lines = []; var font = this.ctx.font; // Save the current Font var height = 0; var width = 0; this.ctx.font = this._getFontString(); while (text.length > 0) { var maxWidth = this.maxWidth - this.padding * 2; var maxHeight = this.maxHeight - this.padding * 2; var line = this._getLineWithWidth(text, maxWidth, false); line.height = this._lineHeight; lines.push(line); width = Math.max(width, line.width); height += line.height; text = trimString(text.slice(line.text.length, text.length)); if (maxHeight && height > maxHeight) { var line = lines.pop(); height -= line.height; } } this._wrappedText = { lines: lines, width: width, height: height }; this.width = width + this.padding * 2; this.height = height + this.padding * 2; this.ctx.font = font; // Restore the font } TextBlock.prototype._getFontString = function () { //return this.fontStyle + " " + this.fontWeight + " " + this.fontSize + "px " + this.fontFamily return getFontString("", this, null); } //#endregion Class TextBlock //#region Class Title function Title(chart, options) { Title.base.constructor.call(this, "Title", options, chart.theme); this.chart = chart; this.canvas = chart.canvas; this.ctx = this.chart.ctx; if (typeof (this._options.fontSize) === "undefined") { this.fontSize = this.chart.getAutoFontSize(this.fontSize); //window.console.log("Chart Title fontSize: " + this.fontSize); } this.width = null,//read only this.height = null//read only this.bounds = { x1: null, y1: null, x2: null, y2: null }; } extend(Title, CanvasJSObject); Title.prototype.render = function () { if (!this.text) return; var container = (!this.dockInsidePlotArea ? this.chart : this.chart.plotArea); var freespace = container.layoutManager.getFreeSpace(); var left = freespace.x1; var top = freespace.y1; var angle = 0; var maxHeight = 0; var containerMargin = 2; //Margin towards the container var rightOffset = this.chart._menuButton && this.chart.exportEnabled && this.verticalAlign === "top" ? 22 : 0; //So that Title doesn't overlap menu button. var textBlockHorizontalAlign; var position; if (this.verticalAlign === "top" || this.verticalAlign === "bottom") { if (this.maxWidth === null) this.maxWidth = freespace.width - containerMargin * 2 - rightOffset * (this.horizontalAlign === "center" ? 2 : 1); maxHeight = freespace.height * .5 - this.margin - containerMargin; angle = 0; } else if (this.verticalAlign === "center") { if (this.horizontalAlign === "left" || this.horizontalAlign === "right") { if (this.maxWidth === null) this.maxWidth = freespace.height - containerMargin * 2; maxHeight = freespace.width * .5 - this.margin - containerMargin; } else if (this.horizontalAlign === "center") { if (this.maxWidth === null) this.maxWidth = freespace.width - containerMargin * 2; maxHeight = freespace.height * .5 - containerMargin * 2; } } if (!this.wrap) maxHeight = Math.min(maxHeight, Math.max(this.fontSize * 1.5, this.fontSize + this.padding * 2.5)); //console.log(this.maxWidth); var textBlock = new TextBlock(this.ctx, { fontSize: this.fontSize, fontFamily: this.fontFamily, fontColor: this.fontColor, fontStyle: this.fontStyle, fontWeight: this.fontWeight, horizontalAlign: this.horizontalAlign, verticalAlign: this.verticalAlign, borderColor: this.borderColor, borderThickness: this.borderThickness, backgroundColor: this.backgroundColor, maxWidth: this.maxWidth, maxHeight: maxHeight, cornerRadius: this.cornerRadius, text: this.text, padding: this.padding, textBaseline: "top" }); var textBlockSize = textBlock.measureText(); if (this.verticalAlign === "top" || this.verticalAlign === "bottom") { if (this.verticalAlign === "top") { top = freespace.y1 + containerMargin; position = "top"; } else if (this.verticalAlign === "bottom") { top = freespace.y2 - containerMargin - textBlockSize.height; position = "bottom"; } if (this.horizontalAlign === "left") { left = freespace.x1 + containerMargin; } else if (this.horizontalAlign === "center") { left = freespace.x1 + freespace.width / 2 - textBlockSize.width / 2; } else if (this.horizontalAlign === "right") { left = freespace.x2 - containerMargin - textBlockSize.width - rightOffset; } textBlockHorizontalAlign = this.horizontalAlign; this.width = textBlockSize.width; this.height = textBlockSize.height; } else if (this.verticalAlign === "center") { if (this.horizontalAlign === "left") { left = freespace.x1 + containerMargin; top = freespace.y2 - containerMargin - (this.maxWidth / 2 - textBlockSize.width / 2); angle = -90; position = "left"; this.width = textBlockSize.height; this.height = textBlockSize.width; } else if (this.horizontalAlign === "right") { left = freespace.x2 - containerMargin; top = freespace.y1 + containerMargin + (this.maxWidth / 2 - textBlockSize.width / 2); angle = 90; position = "right"; this.width = textBlockSize.height; this.height = textBlockSize.width; } else if (this.horizontalAlign === "center") { top = container.y1 + (container.height / 2 - textBlockSize.height / 2); left = container.x1 + (container.width / 2 - textBlockSize.width / 2); position = "center"; this.width = textBlockSize.width; this.height = textBlockSize.height; } textBlockHorizontalAlign = "center"; } textBlock.x = left; textBlock.y = top; textBlock.angle = angle; textBlock.horizontalAlign = textBlockHorizontalAlign; textBlock.render(true); container.layoutManager.registerSpace(position, { width: this.width + (position === "left" || position === "right" ? this.margin + containerMargin : 0), height: this.height + (position === "top" || position === "bottom" ? this.margin + containerMargin : 0) }); this.bounds = { x1: left, y1: top, x2: left + this.width, y2: top + this.height }; this.ctx.textBaseline = "top"; } //#endregion Class Title //#region Class SubTitle function Subtitle(chart, options) { Subtitle.base.constructor.call(this, "Subtitle", options, chart.theme); this.chart = chart; this.canvas = chart.canvas; this.ctx = this.chart.ctx; if (typeof (this._options.fontSize) === "undefined") { this.fontSize = this.chart.getAutoFontSize(this.fontSize); //window.console.log("Chart Title fontSize: " + this.fontSize); } this.width = null,//read only this.height = null//read only this.bounds = { x1: null, y1: null, x2: null, y2: null }; } extend(Subtitle, CanvasJSObject); Subtitle.prototype.render = Title.prototype.render; //#endregion Class SubTitle //#region Legend //TBI: Implement Markes for Legend function Legend(chart, options, theme) { Legend.base.constructor.call(this, "Legend", options, theme); this.chart = chart; this.canvas = chart.canvas; this.ctx = this.chart.ctx; this.ghostCtx = this.chart._eventManager.ghostCtx; this.items = []; this.width = 0, //this.fontSize = 12, this.height = 0, this.orientation = null, this.dataSeries = []; this.bounds = { x1: null, y1: null, x2: null, y2: null }; if (typeof (this._options.fontSize) === "undefined") { this.fontSize = this.chart.getAutoFontSize(this.fontSize); //window.console.log("fontSize: " + this.fontSize); } this.lineHeight = getFontHeightInPixels(this.fontFamily, this.fontSize, this.fontWeight); this.horizontalSpacing = this.fontSize; } extend(Legend, CanvasJSObject); Legend.prototype.render = function () { var container = (!this.dockInsidePlotArea ? this.chart : this.chart.plotArea); var freeSpace = container.layoutManager.getFreeSpace(); var position = null; var top = 0; var left = 0; var maxWidth = 0; var maxHeight = 0; var itemMargin = 5; var items = []; var rows = []; //this.ctx.font = getFontString("", this, null); //this.ctx.fontColor = this.fontColor; if (this.verticalAlign === "top" || this.verticalAlign === "bottom") { this.orientation = "horizontal"; position = this.verticalAlign; maxWidth = this.maxWidth !== null ? this.maxWidth : freeSpace.width * .7; maxHeight = this.maxHeight !== null ? this.maxHeight : freeSpace.height * .5; } else if (this.verticalAlign === "center") { this.orientation = "vertical"; position = this.horizontalAlign; maxWidth = this.maxWidth !== null ? this.maxWidth : freeSpace.width * .5; maxHeight = this.maxHeight !== null ? this.maxHeight : freeSpace.height * .7; } for (var i = 0; i < this.dataSeries.length; i++) { var dataSeries = this.dataSeries[i]; if (dataSeries.type !== "pie" && dataSeries.type !== "doughnut" && dataSeries.type !== "funnel") { var markerType = dataSeries.legendMarkerType ? dataSeries.legendMarkerType : (dataSeries.type === "line" || dataSeries.type === "stepLine" || dataSeries.type === "spline" || dataSeries.type === "scatter" || dataSeries.type === "bubble") && dataSeries.markerType ? dataSeries.markerType : DataSeries.getDefaultLegendMarker(dataSeries.type); var legendText = dataSeries.legendText ? dataSeries.legendText : this.itemTextFormatter ? this.itemTextFormatter({ chart: this.chart, legend: this._options, dataSeries: dataSeries, dataPoint: null }) : dataSeries.name; var markerColor = dataSeries.legendMarkerColor ? dataSeries.legendMarkerColor : dataSeries.markerColor ? dataSeries.markerColor : dataSeries._colorSet[0]; var markerSize = (!dataSeries.markerSize && (dataSeries.type === "line" || dataSeries.type === "stepLine" || dataSeries.type === "spline")) ? 0 : this.lineHeight * .6; var markerBorderColor = dataSeries.legendMarkerBorderColor ? dataSeries.legendMarkerBorderColor : dataSeries.markerBorderColor; var markerBorderThickness = dataSeries.legendMarkerBorderThickness ? dataSeries.legendMarkerBorderThickness : dataSeries.markerBorderThickness ? Math.max(1, Math.round(markerSize * .2)) : 0; var lineColor = dataSeries._colorSet[0]; legendText = this.chart.replaceKeywordsWithValue(legendText, dataSeries.dataPoints[0], dataSeries, i); var item = { markerType: markerType, markerColor: markerColor, text: legendText, textBlock: null, chartType: dataSeries.type, markerSize: markerSize, lineColor: dataSeries._colorSet[0], dataSeriesIndex: dataSeries.index, dataPointIndex: null, markerBorderColor: markerBorderColor, markerBorderThickness: markerBorderThickness }; items.push(item); } else { for (var dataPointIndex = 0; dataPointIndex < dataSeries.dataPoints.length; dataPointIndex++) { var dataPoint = dataSeries.dataPoints[dataPointIndex]; var markerType = dataPoint.legendMarkerType ? dataPoint.legendMarkerType : dataSeries.legendMarkerType ? dataSeries.legendMarkerType : DataSeries.getDefaultLegendMarker(dataSeries.type); var legendText = dataPoint.legendText ? dataPoint.legendText : dataSeries.legendText ? dataSeries.legendText : this.itemTextFormatter ? this.itemTextFormatter({ chart: this.chart, legend: this._options, dataSeries: dataSeries, dataPoint: dataPoint }) : dataPoint.name ? dataPoint.name : "DataPoint: " + (dataPointIndex + 1); var markerColor = dataPoint.legendMarkerColor ? dataPoint.legendMarkerColor : dataSeries.legendMarkerColor ? dataSeries.legendMarkerColor : dataPoint.color ? dataPoint.color : dataSeries.color ? dataSeries.color : dataSeries._colorSet[dataPointIndex % dataSeries._colorSet.length]; var markerSize = this.lineHeight * .6; var markerBorderColor = dataPoint.legendMarkerBorderColor ? dataPoint.legendMarkerBorderColor : dataSeries.legendMarkerBorderColor ? dataSeries.legendMarkerBorderColor : dataPoint.markerBorderColor ? dataPoint.markerBorderColor : dataSeries.markerBorderColor; var markerBorderThickness = dataPoint.legendMarkerBorderThickness ? dataPoint.legendMarkerBorderThickness : dataSeries.legendMarkerBorderThickness ? dataSeries.legendMarkerBorderThickness : dataPoint.markerBorderThickness || dataSeries.markerBorderThickness ? Math.max(1, Math.round(markerSize * .2)) : 0; legendText = this.chart.replaceKeywordsWithValue(legendText, dataPoint, dataSeries, dataPointIndex); var item = { markerType: markerType, markerColor: markerColor, text: legendText, textBlock: null, chartType: dataSeries.type, markerSize: markerSize, dataSeriesIndex: i, dataPointIndex: dataPointIndex, markerBorderColor: markerBorderColor, markerBorderThickness: markerBorderThickness }; if (dataPoint.showInLegend || (dataSeries.showInLegend && dataPoint.showInLegend !== false)) { items.push(item); } } } item = null; } if (this.reversed === true) { items.reverse(); } // Find out the required width and height of Legend and position the items relative to the container if (items.length > 0) { var row = null; var rowIndex = 0; // required for vertical orientation var textMaxWidth = 0; var columnHeight = 0; if (this.itemWidth !== null) { if (this.itemMaxWidth !== null) { textMaxWidth = Math.min(this.itemWidth, this.itemMaxWidth, maxWidth); } else { textMaxWidth = Math.min(this.itemWidth, maxWidth); } } else { if (this.itemMaxWidth !== null) { textMaxWidth = Math.min(this.itemMaxWidth, maxWidth); } else { textMaxWidth = maxWidth; } } markerSize = (markerSize === 0 ? this.lineHeight * .6 : markerSize); textMaxWidth = textMaxWidth - (markerSize + this.horizontalSpacing * .1); for (var i = 0; i < items.length; i++) { var item = items[i]; if (item.chartType === "line" || item.chartType === "spline" || item.chartType === "stepLine") { textMaxWidth = textMaxWidth - 2 * (this.lineHeight * .1); } if (maxHeight <= 0 || typeof (maxHeight) === "undefined" || textMaxWidth <= 0 || typeof (textMaxWidth) === "undefined") { continue; } if (this.orientation === "horizontal") { item.textBlock = new TextBlock(this.ctx, { x: 0, y: 0,//TBI maxWidth: textMaxWidth, maxHeight: this.itemWrap ? maxHeight : this.lineHeight, //TBI: FontSize angle: 0, text: item.text, horizontalAlign: "left",//left, center, right fontSize: this.fontSize,//in pixels fontFamily: this.fontFamily, fontWeight: this.fontWeight, //normal, bold, bolder, lighter, fontColor: this.fontColor, fontStyle: this.fontStyle, // normal, italic, oblique textBaseline: "top" }); item.textBlock.measureText(); if (this.itemWidth !== null) { item.textBlock.width = this.itemWidth - (markerSize + this.horizontalSpacing * .1 + ((item.chartType === "line" || item.chartType === "spline" || item.chartType === "stepLine") ? 2 * (this.lineHeight * .1) : 0)); } if (!row || row.width + Math.round(item.textBlock.width + this.horizontalSpacing * .1 + markerSize + (row.width === 0 ? 0 : (this.horizontalSpacing)) + ((item.chartType === "line" || item.chartType === "spline" || item.chartType === "stepLine") ? 2 * (this.lineHeight * .1) : 0)) > maxWidth) { row = { items: [], width: 0 }; rows.push(row); this.height += columnHeight; columnHeight = 0; } columnHeight = Math.max(columnHeight, item.textBlock.height); item.textBlock.x = row.width; item.textBlock.y = 0; row.width += Math.round(item.textBlock.width + this.horizontalSpacing * .1 + markerSize + (row.width === 0 ? 0 : this.horizontalSpacing) + ((item.chartType === "line" || item.chartType === "spline" || item.chartType === "stepLine") ? 2 * (this.lineHeight * .1) : 0)); row.items.push(item); this.width = Math.max(row.width, this.width); } else { item.textBlock = new TextBlock(this.ctx, { x: 0, y: 0,//TBI maxWidth: textMaxWidth, maxHeight: this.itemWrap === true ? maxHeight : this.fontSize * 1.5, //TBI: FontSize angle: 0, text: item.text, horizontalAlign: "left",//left, center, right fontSize: this.fontSize,//in pixels fontFamily: this.fontFamily, fontWeight: this.fontWeight, //normal, bold, bolder, lighter, fontColor: this.fontColor, fontStyle: this.fontStyle, // normal, italic, oblique textBaseline: "top" }); item.textBlock.measureText(); if (this.itemWidth !== null) { item.textBlock.width = this.itemWidth - (markerSize + this.horizontalSpacing * .1 + ((item.chartType === "line" || item.chartType === "spline" || item.chartType === "stepLine") ? 2 * (this.lineHeight * .1) : 0)); } if (this.height <= maxHeight) { row = { items: [], width: 0 }; rows.push(row); } else { row = rows[rowIndex]; rowIndex = (rowIndex + 1) % rows.length; } this.height += item.textBlock.height; item.textBlock.x = row.width; // relative to the row item.textBlock.y = 0; // relative to the row row.width += Math.round(item.textBlock.width + this.horizontalSpacing * .1 + markerSize + (row.width === 0 ? 0 : this.horizontalSpacing) + ((item.chartType === "line" || item.chartType === "spline" || item.chartType === "stepLine") ? 2 * (this.lineHeight * .1) : 0)); row.items.push(item); this.width = Math.max(row.width, this.width); } } if (this.itemWrap === false) { this.height = rows.length * (this.lineHeight); } else { this.height += columnHeight; } this.height = Math.min(maxHeight, this.height); this.width = Math.min(maxWidth, this.width); } if (this.verticalAlign === "top") { if (this.horizontalAlign === "left") left = freeSpace.x1; else if (this.horizontalAlign === "right") left = freeSpace.x2 - this.width; else left = freeSpace.x1 + freeSpace.width / 2 - this.width / 2; top = freeSpace.y1; } else if (this.verticalAlign === "center") { if (this.horizontalAlign === "left") left = freeSpace.x1; else if (this.horizontalAlign === "right") left = freeSpace.x2 - this.width; else left = freeSpace.x1 + freeSpace.width / 2 - this.width / 2; top = freeSpace.y1 + freeSpace.height / 2 - this.height / 2; } else if (this.verticalAlign === "bottom") { if (this.horizontalAlign === "left") left = freeSpace.x1; else if (this.horizontalAlign === "right") left = freeSpace.x2 - this.width; else left = freeSpace.x1 + freeSpace.width / 2 - this.width / 2; top = freeSpace.y2 - this.height; } this.items = items; //Assign ids to all legendItems for (var i = 0; i < this.items.length; i++) { var item = items[i]; item.id = ++this.chart._eventManager.lastObjectId; this.chart._eventManager.objectMap[item.id] = { id: item.id, objectType: "legendItem", legendItemIndex: i, dataSeriesIndex: item.dataSeriesIndex, dataPointIndex: item.dataPointIndex }; //delete item.textBlock;// Not Required anymore } var rowHeight = 0; for (var i = 0; i < rows.length; i++) { var row = rows[i]; var columnHeight = 0; for (var itemIndex = 0; itemIndex < row.items.length; itemIndex++) { var item = row.items[itemIndex]; var itemX = item.textBlock.x + left + (itemIndex === 0 ? markerSize * .2 : this.horizontalSpacing); var itemY = top + rowHeight; var ghostX = itemX; if (!this.chart.data[item.dataSeriesIndex].visible) this.ctx.globalAlpha = .5; this.ctx.save(); this.ctx.rect(left, top, maxWidth, maxHeight); this.ctx.clip(); if (item.chartType === "line" || item.chartType === "stepLine" || item.chartType === "spline") { this.ctx.strokeStyle = item.lineColor; this.ctx.lineWidth = Math.ceil(this.lineHeight / 8); this.ctx.beginPath(); this.ctx.moveTo(itemX - this.lineHeight * .1, itemY + this.lineHeight / 2); this.ctx.lineTo(itemX + this.lineHeight * .7, itemY + this.lineHeight / 2); this.ctx.stroke(); ghostX -= this.lineHeight * .1; } RenderHelper.drawMarker(itemX + markerSize / 2, itemY + (this.lineHeight / 2), this.ctx, item.markerType, item.markerSize, item.markerColor, item.markerBorderColor, item.markerBorderThickness); item.textBlock.x = itemX + this.horizontalSpacing * .1 + markerSize; if (item.chartType === "line" || item.chartType === "stepLine" || item.chartType === "spline") { item.textBlock.x = item.textBlock.x + this.lineHeight * .1; } item.textBlock.y = itemY; item.textBlock.render(true); this.ctx.restore(); if (itemIndex > 0) { columnHeight = Math.max(columnHeight, item.textBlock.height); } else { columnHeight = item.textBlock.height; } if (!this.chart.data[item.dataSeriesIndex].visible) this.ctx.globalAlpha = 1; var hexColor = intToHexColorString(item.id); this.ghostCtx.fillStyle = hexColor; this.ghostCtx.beginPath(); this.ghostCtx.fillRect(ghostX, item.textBlock.y, item.textBlock.x + item.textBlock.width - ghostX, item.textBlock.height); item.x1 = this.chart._eventManager.objectMap[item.id].x1 = ghostX; item.y1 = this.chart._eventManager.objectMap[item.id].y1 = item.textBlock.y; item.x2 = this.chart._eventManager.objectMap[item.id].x2 = item.textBlock.x + item.textBlock.width; item.y2 = this.chart._eventManager.objectMap[item.id].y2 = item.textBlock.y + item.textBlock.height; } rowHeight = rowHeight + columnHeight; } //this.ctx.beginPath(); //this.ctx.lineWidth = 2; //this.ctx.strokeStyle = "red"; //this.ctx.rect(left, top, this.width, this.height); //this.ctx.stroke(); container.layoutManager.registerSpace(position, { width: this.width + 2 + 2, height: this.height + 5 + 5 }); this.bounds = { x1: left, y1: top, x2: left + this.width, y2: top + this.height }; } //#endregion Legend //#region Class PlotArea function PlotArea(chart, options) { PlotArea.base.constructor.call(this, options); this.chart = chart; this.canvas = chart.canvas; this.ctx = this.chart.ctx; } extend(PlotArea, CanvasJSObject); PlotArea.prototype.render = function () { var freeSpace = this.chart.layoutManager.getFreeSpace(); this.ctx.fillStyle = "red"; this.ctx.fillRect(freeSpace.x1, freeSpace.y1, freeSpace.x2, freeSpace.y2); } //#endregion Class PlotArea //#region DataSeries function DataSeries(chart, options, theme, index, id) { DataSeries.base.constructor.call(this, "DataSeries", options, theme); this.chart = chart; this.canvas = chart.canvas; this._ctx = chart.canvas.ctx; this.index = index; this.noDataPointsInPlotArea = 0; //this.maxWidthInX = 0; this.id = id; this.chart._eventManager.objectMap[id] = { id: id, objectType: "dataSeries", dataSeriesIndex: index } this.dataPointIds = []; this.plotUnit = []; this.axisX = null; this.axisY = null; if (this.fillOpacity === null) { if (this.type.match(/area/i)) this.fillOpacity = .7; else this.fillOpacity = 1; } this.axisPlacement = this.getDefaultAxisPlacement(); if (typeof (this._options.indexLabelFontSize) === "undefined") { this.indexLabelFontSize = this.chart.getAutoFontSize(this.indexLabelFontSize); } } extend(DataSeries, CanvasJSObject); //Static Method that returns the axisPlacement for a given ChartType. Returns one of "normal", "xySwapped", "none" DataSeries.prototype.getDefaultAxisPlacement = function () { //if (!this.visible) // return "none"; //type = this.type.toLowerCase(); var type = this.type; if (type === "column" || type === "line" || type === "stepLine" || type === "spline" || type === "area" || type === "stepArea" || type === "splineArea" || type === "stackedColumn" || type === "stackedLine" || type === "bubble" || type === "scatter" || type === "stackedArea" || type === "stackedColumn100" || type === "stackedLine100" || type === "stackedArea100" || type === "candlestick" || type === "ohlc" || type === "rangeColumn" || type === "rangeArea" || type === "rangeSplineArea") { return "normal"; } else if (type === "bar" || type === "stackedBar" || type === "stackedBar100" || type === "rangeBar") { return "xySwapped"; } else if (type === "pie" || type === "doughnut" || type === "funnel") { return "none"; } else { window.console.log("Unknown Chart Type: " + type); return null; } } DataSeries.getDefaultLegendMarker = function (type) { //type = type.toLowerCase(); if (type === "column" || type === "stackedColumn" || type === "stackedLine" || type === "bar" || type === "stackedBar" || type === "stackedBar100" || type === "bubble" || type === "scatter" || type === "stackedColumn100" || type === "stackedLine100" || type === "stepArea" || type === "candlestick" || type === "ohlc" || type === "rangeColumn" || type === "rangeBar" || type === "rangeArea" || type === "rangeSplineArea") { return "square"; } else if (type === "line" || type === "stepLine" || type === "spline" || type === "pie" || type === "doughnut" || type === "funnel") { return "circle"; } else if (type === "area" || type === "splineArea" || type === "stackedArea" || type === "stackedArea100") { return "triangle" } else { window.console.log("Unknown Chart Type: " + type); return null; } } //Finds dataPoint with the given x value. If findClosest is set, finds dataPoint with closest x value. //Returns searchResult object if found, else returns null DataSeries.prototype.getDataPointAtX = function (x, findClosest) { if (!this.dataPoints || this.dataPoints.length === 0) return null; var searchResult = { dataPoint: null, distance: Infinity, index: NaN }; var dataPoint = null; var j = 0; var i = 0; var direction = 1; // +1 for foward and -1 for backward. var minimumXDistance = Infinity; var forwardMissCount = 0, backwardMissCount = 0; var maxMissCount = 1000; var searchStartIndex = 0; if (this.chart.plotInfo.axisPlacement !== "none") { //var xRange = (this.dataPoints[this.dataPoints.length - 1].x - this.dataPoints[0].x); //if (xRange > 0) // searchStartIndex = ((this.dataPoints.length - 1) / xRange * (x - this.dataPoints[0].x)) >> 0; //else // searchStartIndex = 0; var xRange = (this.dataPoints[this.dataPoints.length - 1].x - this.dataPoints[0].x); if (xRange > 0) searchStartIndex = Math.min(Math.max(((this.dataPoints.length - 1) / xRange * (x - this.dataPoints[0].x)) >> 0, 0), this.dataPoints.length); else searchStartIndex = 0; //searchStartIndex = ((this.dataPoints[this.dataPoints.length - 1].x - this.dataPoints[0].x) / this.dataPoints.length * (x - this.dataPoints[0].x)) >> 0; } while (true) { i = (direction > 0) ? searchStartIndex + j : searchStartIndex - j; if (i >= 0 && i < this.dataPoints.length) { dataPoint = this.dataPoints[i]; var distance = Math.abs(dataPoint.x - x); if (distance < searchResult.distance) { searchResult.dataPoint = dataPoint; searchResult.distance = distance; searchResult.index = i; } var xDistance = Math.abs(dataPoint.x - x); if (xDistance <= minimumXDistance) minimumXDistance = xDistance; else { if (direction > 0) forwardMissCount++; else backwardMissCount++; } if (forwardMissCount > maxMissCount && backwardMissCount > maxMissCount) break; } else if (searchStartIndex - j < 0 && searchStartIndex + j >= this.dataPoints.length) break; if (direction === -1) { j++; direction = 1; } else direction = -1; } if (!findClosest && searchResult.dataPoint.x === x) return searchResult; else if (findClosest && searchResult.dataPoint !== null) return searchResult; else return null; } // x & y should be in pixels. Can be used only after rendering the chart. DataSeries.prototype.getDataPointAtXY = function (x, y, getClosest) { if (!this.dataPoints || this.dataPoints.length === 0) return null; getClosest = getClosest || false; var results = []; var j = 0, i = 0; var direction = 1; // +1 for foward and -1 for backward. var foundDataPoint = false; var minimumXDistance = Infinity; var forwardMissCount = 0, backwardMissCount = 0; var maxMissCount = 1000; var searchStartIndex = 0; if (this.chart.plotInfo.axisPlacement !== "none") { var xval = this.chart.axisX.getXValueAt({ x: x, y: y }); var xRange = (this.dataPoints[this.dataPoints.length - 1].x - this.dataPoints[0].x); if (xRange > 0) searchStartIndex = Math.min(Math.max(((this.dataPoints.length - 1) / xRange * (xval - this.dataPoints[0].x)) >> 0, 0), this.dataPoints.length); else searchStartIndex = 0; //var xRange = (this.axisX._absoluteMaximum - this.axisX._absoluteMinimum); //if (xRange > 0) // searchStartIndex = Math.min(Math.max(((this.dataPoints.length - 1) / xRange * (xval - this.axisX._absoluteMinimum)) >> 0, 0), this.dataPoints.length); //else // searchStartIndex = 0; } while (true) { //i = searchStartIndex + (j * direction); i = (direction > 0) ? searchStartIndex + j : searchStartIndex - j; if (i >= 0 && i < this.dataPoints.length) { var id = this.dataPointIds[i]; var visualInfo = this.chart._eventManager.objectMap[id]; var dataPoint = this.dataPoints[i]; var distance = null; if (visualInfo) { switch (this.type) { case "column": case "stackedColumn": case "stackedColumn100": case "bar": case "stackedBar": case "stackedBar100": case "rangeColumn": case "rangeBar": if (x >= visualInfo.x1 && x <= visualInfo.x2 && y >= visualInfo.y1 && y <= visualInfo.y2) { results.push({ dataPoint: dataPoint, dataPointIndex: i, dataSeries: this, distance: Math.min(Math.abs(visualInfo.x1 - x), Math.abs(visualInfo.x2 - x), Math.abs(visualInfo.y1 - y), Math.abs(visualInfo.y2 - y)) //distance:0 }); foundDataPoint = true; } break; case "line": case "stepLine": case "spline": case "area": case "stepArea": case "stackedArea": case "stackedArea100": case "splineArea": case "scatter": var markerSize = getProperty("markerSize", dataPoint, this) || 4; var snapDistance = getClosest ? 20 : markerSize; distance = Math.sqrt(Math.pow(visualInfo.x1 - x, 2) + Math.pow(visualInfo.y1 - y, 2)); if (distance <= snapDistance) { results.push({ dataPoint: dataPoint, dataPointIndex: i, dataSeries: this, distance: distance }); } var xDistance = Math.abs(visualInfo.x1 - x); if (xDistance <= minimumXDistance) minimumXDistance = xDistance; else { if (direction > 0) forwardMissCount++; else backwardMissCount++; } if (distance <= markerSize / 2) { foundDataPoint = true; } break; case "rangeArea": case "rangeSplineArea": var markerSize = getProperty("markerSize", dataPoint, this) || 4; var snapDistance = getClosest ? 20 : markerSize; distance = Math.min(Math.sqrt(Math.pow(visualInfo.x1 - x, 2) + Math.pow(visualInfo.y1 - y, 2)), Math.sqrt(Math.pow(visualInfo.x1 - x, 2) + Math.pow(visualInfo.y2 - y, 2))); if (distance <= snapDistance) { results.push({ dataPoint: dataPoint, dataPointIndex: i, dataSeries: this, distance: distance }); } var xDistance = Math.abs(visualInfo.x1 - x); if (xDistance <= minimumXDistance) minimumXDistance = xDistance; else { if (direction > 0) forwardMissCount++; else backwardMissCount++; } if (distance <= markerSize / 2) { foundDataPoint = true; } break; case "bubble": var markerSize = visualInfo.size; distance = Math.sqrt(Math.pow(visualInfo.x1 - x, 2) + Math.pow(visualInfo.y1 - y, 2)); if (distance <= markerSize / 2) { results.push({ dataPoint: dataPoint, dataPointIndex: i, dataSeries: this, distance: distance }); foundDataPoint = true; } break; case "pie": case "doughnut": var center = visualInfo.center; var innerRadius = this.type === "doughnut" ? visualInfo.percentInnerRadius * visualInfo.radius : 0; distance = Math.sqrt(Math.pow(center.x - x, 2) + Math.pow(center.y - y, 2)); if (distance < visualInfo.radius && distance > innerRadius) { var deltaY = y - center.y; var deltaX = x - center.x; var angle = Math.atan2(deltaY, deltaX); if (angle < 0) angle += Math.PI * 2; angle = Number((((angle / Math.PI * 180 % 360) + 360) % 360).toFixed(12)); //console.log(angle); var startAngle = Number((((visualInfo.startAngle / Math.PI * 180 % 360) + 360) % 360).toFixed(12)); var endAngle = Number((((visualInfo.endAngle / Math.PI * 180 % 360) + 360) % 360).toFixed(12)); //So that data point is detected when there is only one dataPoint if (endAngle === 0 && visualInfo.endAngle > 1) { endAngle = 360; } if (startAngle >= endAngle && dataPoint.y !== 0) { endAngle += 360; if (angle < startAngle) angle += 360; } if (angle > startAngle && angle < endAngle) { results.push({ dataPoint: dataPoint, dataPointIndex: i, dataSeries: this, distance: 0 }); foundDataPoint = true; } } break; case "candlestick": if (((x >= (visualInfo.x1 - visualInfo.borderThickness / 2)) && (x <= (visualInfo.x2 + visualInfo.borderThickness / 2)) && (y >= visualInfo.y2 - visualInfo.borderThickness / 2) && (y <= visualInfo.y3 + visualInfo.borderThickness / 2)) || (Math.abs(visualInfo.x2 - x + visualInfo.x1 - x) < visualInfo.borderThickness && (y >= visualInfo.y1 && y <= visualInfo.y4))) { results.push({ dataPoint: dataPoint, dataPointIndex: i, dataSeries: this, distance: Math.min(Math.abs(visualInfo.x1 - x), Math.abs(visualInfo.x2 - x), Math.abs(visualInfo.y2 - y), Math.abs(visualInfo.y3 - y)) //distance:0 }); foundDataPoint = true; } break; case "ohlc": if ((Math.abs(visualInfo.x2 - x + visualInfo.x1 - x) < visualInfo.borderThickness && (y >= visualInfo.y2 && y <= visualInfo.y3)) || (x >= visualInfo.x1 && (x <= (visualInfo.x2 + visualInfo.x1) / 2) && (y >= visualInfo.y1 - visualInfo.borderThickness / 2) && (y <= visualInfo.y1 + visualInfo.borderThickness / 2)) || ((x >= (visualInfo.x1 + visualInfo.x2) / 2) && (x <= visualInfo.x2) && (y >= visualInfo.y4 - visualInfo.borderThickness / 2) && (y <= visualInfo.y4 + visualInfo.borderThickness / 2))) { results.push({ dataPoint: dataPoint, dataPointIndex: i, dataSeries: this, distance: Math.min(Math.abs(visualInfo.x1 - x), Math.abs(visualInfo.x2 - x), Math.abs(visualInfo.y2 - y), Math.abs(visualInfo.y3 - y)) //distance:0 }); foundDataPoint = true; } break; } if (foundDataPoint || (forwardMissCount > maxMissCount && backwardMissCount > maxMissCount)) break; } } else if (searchStartIndex - j < 0 && searchStartIndex + j >= this.dataPoints.length) break; if (direction === -1) { j++; direction = 1; } else direction = -1; } var closestResult = null; for (var m = 0; m < results.length; m++) { if (!closestResult) { closestResult = results[m]; } else if (results[m].distance <= closestResult.distance) { closestResult = results[m]; } } //if (window.console) // window.console.log("forwardMissCount: " + forwardMissCount + "; backwardMissCount: " + backwardMissCount + "; getClosest: " + getClosest); //if (window.console && closestResult) // window.console.log(j + ": distance = " + closestResult.distance); return closestResult; } DataSeries.prototype.getMarkerProperties = function (index, x, y, ctx) { var dataPoints = this.dataPoints; var dataSeries = this; var markerColor = dataPoints[index].markerColor ? dataPoints[index].markerColor : dataSeries.markerColor ? dataSeries.markerColor : dataPoints[index].color ? dataPoints[index].color : dataSeries.color ? dataSeries.color : dataSeries._colorSet[index % dataSeries._colorSet.length]; var markerBorderColor = dataPoints[index].markerBorderColor ? dataPoints[index].markerBorderColor : dataSeries.markerBorderColor ? dataSeries.markerBorderColor : null; var markerBorderThickness = dataPoints[index].markerBorderThickness ? dataPoints[index].markerBorderThickness : dataSeries.markerBorderThickness ? dataSeries.markerBorderThickness : null; var markerType = dataPoints[index].markerType ? dataPoints[index].markerType : dataSeries.markerType; var markerSize = dataPoints[index].markerSize ? dataPoints[index].markerSize : dataSeries.markerSize; return { x: x, y: y, ctx: ctx, type: markerType, size: markerSize, color: markerColor, borderColor: markerBorderColor, borderThickness: markerBorderThickness } } //#endregion DataSeries //#region Axis function Axis(chart, options, type, position) { Axis.base.constructor.call(this, "Axis", options, chart.theme); this.chart = chart; this.canvas = chart.canvas; this.ctx = chart.ctx; this.maxWidth = 0; this.maxHeight = 0; this.intervalStartPosition = 0; this.labels = []; this._labels = null; //Processed information about the data that gets plotted against this axis this.dataInfo = { min: Infinity, max: -Infinity, viewPortMin: Infinity, viewPortMax: -Infinity, minDiff: Infinity // Used only in case of axisX }; if (type === "axisX") { this.sessionVariables = this.chart.sessionVariables[type]; if (!this._options.interval) this.intervalType = null; } else { if (position === "left" || position === "top") this.sessionVariables = this.chart.sessionVariables["axisY"]; else { this.sessionVariables = this.chart.sessionVariables["axisY2"]; } } if (typeof (this._options.titleFontSize) === "undefined") { this.titleFontSize = this.chart.getAutoFontSize(this.titleFontSize); //window.console.log("titleFontSize: " + this.titleFontSize); } if (typeof (this._options.labelFontSize) === "undefined") { this.labelFontSize = this.chart.getAutoFontSize(this.labelFontSize); //window.console.log("labelFontSize: " + this.labelFontSize); } //Axis Type : axisX, axisY this.type = type; if (type === "axisX" && (!options || typeof (options.gridThickness) === "undefined")) this.gridThickness = 0; this._position = position; this.lineCoordinates = { x1: null, y1: null, x2: null, y2: null, width: null };//{x1:, y1:, x2:, y2:, width:} // { this.labelAngle = ((this.labelAngle % 360) + 360) % 360; if (this.labelAngle > 90 && this.labelAngle <= 270) this.labelAngle -= 180; else if (this.labelAngle > 180 && this.labelAngle <= 270) this.labelAngle -= 180 else if (this.labelAngle > 270 && this.labelAngle <= 360) this.labelAngle -= 360 } if (this._options.stripLines && this._options.stripLines.length > 0) { this.stripLines = []; for (var i = 0; i < this._options.stripLines.length; i++) { this.stripLines.push(new StripLine(this.chart, this._options.stripLines[i], chart.theme, ++this.chart._eventManager.lastObjectId, this)); } } this._titleTextBlock = null; if (!this.hasOptionChanged("viewportMinimum") && !isNaN(this.sessionVariables.newViewportMinimum) && this.sessionVariables.newViewportMinimum !== null) this.viewportMinimum = this.sessionVariables.newViewportMinimum; else this.sessionVariables.newViewportMinimum = null; if (!this.hasOptionChanged("viewportMaximum") && !isNaN(this.sessionVariables.newViewportMaximum) && this.sessionVariables.newViewportMaximum !== null) this.viewportMaximum = this.sessionVariables.newViewportMaximum; else this.sessionVariables.newViewportMaximum = null; if (this.minimum !== null && this.viewportMinimum !== null) this.viewportMinimum = Math.max(this.viewportMinimum, this.minimum); if (this.maximum !== null && this.viewportMaximum !== null) this.viewportMaximum = Math.min(this.viewportMaximum, this.maximum); this.trackChanges("viewportMinimum"); this.trackChanges("viewportMaximum"); } extend(Axis, CanvasJSObject); Axis.prototype.createLabels = function () { var textBlock; var i = 0; var endPoint; var labelMaxWidth = 0; var labelMaxHeight = 0; var intervalInPixels = 0; //var intervalInPixels = this.conversionParameters.pixelPerUnit * this.interval; if (this._position === "bottom" || this._position === "top") { intervalInPixels = this.lineCoordinates.width / Math.abs(this.viewportMaximum - this.viewportMinimum) * this.interval; if (this.labelAutoFit) { labelMaxWidth = typeof (this._options.labelMaxWidth) === "undefined" ? intervalInPixels * .9 >> 0 : this.labelMaxWidth; } else { labelMaxWidth = typeof (this._options.labelMaxWidth) === "undefined" ? this.chart.width * .7 >> 0 : this.labelMaxWidth; } labelMaxHeight = typeof (this._options.labelWrap) === "undefined" || this.labelWrap ? this.chart.height * .5 >> 0 : this.labelFontSize * 1.5; } else if (this._position === "left" || this._position === "right") { intervalInPixels = this.lineCoordinates.height / Math.abs(this.viewportMaximum - this.viewportMinimum) * this.interval; if (this.labelAutoFit) { labelMaxWidth = typeof (this._options.labelMaxWidth) === "undefined" ? this.chart.width * .3 >> 0 : this.labelMaxWidth; } else { labelMaxWidth = typeof (this._options.labelMaxWidth) === "undefined" ? this.chart.width * .5 >> 0 : this.labelMaxWidth; } labelMaxHeight = typeof (this._options.labelWrap) === "undefined" || this.labelWrap ? intervalInPixels * 2 >> 0 : this.labelFontSize * 1.5; } if (this.type === "axisX" && this.chart.plotInfo.axisXValueType === "dateTime") { endPoint = addToDateTime(new Date(this.viewportMaximum), this.interval, this.intervalType) //endPoint = this.viewportMaximum; for (i = this.intervalStartPosition; i < endPoint; addToDateTime(i, this.interval, this.intervalType)) { //var text = dateFormat(i, this.valueFormatString); var timeInMilliseconds = i.getTime(); var text = this.labelFormatter ? this.labelFormatter({ chart: this.chart, axis: this._options, value: i, label: this.labels[i] ? this.labels[i] : null }) : this.type === "axisX" && this.labels[timeInMilliseconds] ? this.labels[timeInMilliseconds] : dateFormat(i, this.valueFormatString, this.chart._cultureInfo); textBlock = new TextBlock(this.ctx, { x: 0, y: 0, //maxWidth: this.maxHeight, //maxHeight: this.labelFontSize, maxWidth: labelMaxWidth, maxHeight: labelMaxHeight, angle: this.labelAngle, text: this.prefix + text + this.suffix, horizontalAlign: "left",//left, center, right fontSize: this.labelFontSize,//in pixels fontFamily: this.labelFontFamily, fontWeight: this.labelFontWeight, //normal, bold, bolder, lighter, fontColor: this.labelFontColor, fontStyle: this.labelFontStyle, // normal, italic, oblique textBaseline: "middle" }); this._labels.push({ position: i.getTime(), textBlock: textBlock, effectiveHeight: null }); } } else { endPoint = this.viewportMaximum; //if ((Math.floor(this.interval) < this.interval && !this._options.interval) || true) { //Check if it should be rendered as a category axis. If yes, then ceil the interval if (this.labels && this.labels.length) { var tempInterval = Math.ceil(this.interval); var tempStartPoint = Math.ceil(this.intervalStartPosition); var hasAllLabels = false; for (i = tempStartPoint; i < this.viewportMaximum; i += tempInterval) { if (this.labels[i]) { hasAllLabels = true; } else { hasAllLabels = false; break; } } if (hasAllLabels) { this.interval = tempInterval; this.intervalStartPosition = tempStartPoint; } } //parseFloat & toPrecision are being used to avoid issues related to precision. for (i = this.intervalStartPosition; i <= endPoint; i = parseFloat((i + this.interval).toFixed(14))) { var text = this.labelFormatter ? this.labelFormatter({ chart: this.chart, axis: this._options, value: i, label: this.labels[i] ? this.labels[i] : null }) : this.type === "axisX" && this.labels[i] ? this.labels[i] : numberFormat(i, this.valueFormatString, this.chart._cultureInfo); textBlock = new TextBlock(this.ctx, { x: 0, y: 0, //maxWidth: this.maxHeight, //maxHeight: this.labelFontSize, maxWidth: labelMaxWidth, maxHeight: labelMaxHeight, angle: this.labelAngle, text: this.prefix + text + this.suffix, horizontalAlign: "left",//left, center, right fontSize: this.labelFontSize,//in pixels fontFamily: this.labelFontFamily, fontWeight: this.labelFontWeight, //normal, bold, bolder, lighter, fontColor: this.labelFontColor, fontStyle: this.labelFontStyle, // normal, italic, oblique textBaseline: "middle", borderThickness: 0 }); this._labels.push({ position: i, textBlock: textBlock, effectiveHeight: null }); } } for (var i = 0; i < this.stripLines.length; i++) { var stripLine = this.stripLines[i]; textBlock = new TextBlock(this.ctx, { x: 0, y: 0, //maxWidth: this.maxHeight, //maxHeight: this.labelFontSize, backgroundColor: stripLine.labelBackgroundColor, maxWidth: labelMaxWidth, maxHeight: labelMaxHeight, angle: this.labelAngle, text: stripLine.labelFormatter ? stripLine.labelFormatter({ chart: this.chart, axis: this, stripLine: stripLine }) : stripLine.label, horizontalAlign: "left",//left, center, right fontSize: stripLine.labelFontSize,//in pixels fontFamily: stripLine.labelFontFamily, fontWeight: stripLine.labelFontWeight, //normal, bold, bolder, lighter, fontColor: stripLine._options.labelFontColor || stripLine.color, fontStyle: stripLine.labelFontStyle, // normal, italic, oblique textBaseline: "middle", borderThickness: 0 }); this._labels.push({ position: stripLine.value, textBlock: textBlock, effectiveHeight: null, stripLine: stripLine }); } } Axis.prototype.createLabelsAndCalculateWidth = function () { var maxLabelEffectiveWidth = 0; this._labels = []; if (this._position === "left" || this._position === "right") { this.createLabels(); for (i = 0; i < this._labels.length; i++) { var textBlock = this._labels[i].textBlock; var size = textBlock.measureText(); //var hypotenuse = Math.sqrt(Math.pow(size.height / 2, 2) + Math.pow(size.width, 2)); //labelEffectiveWidth = hypotenuse * Math.cos(Math.abs(Math.PI / 180 * this.labelAngle) - Math.abs(Math.acos(size.width / hypotenuse))); var labelEffectiveWidth = 0; if (this.labelAngle === 0) labelEffectiveWidth = size.width; else labelEffectiveWidth = (size.width * Math.cos(Math.PI / 180 * Math.abs(this.labelAngle))) + (size.height / 2 * Math.sin(Math.PI / 180 * Math.abs(this.labelAngle))); if (maxLabelEffectiveWidth < labelEffectiveWidth) maxLabelEffectiveWidth = labelEffectiveWidth; this._labels[i].effectiveWidth = labelEffectiveWidth; } } var titleHeight = this.title ? getFontHeightInPixels(this.titleFontFamily, this.titleFontSize, this.titleFontWeight) + 2 : 0; var axisWidth = titleHeight + maxLabelEffectiveWidth + this.tickLength + 5; //if (isDebugMode && window.console) { // window.console.log(this.type + "--- axisWidth: " + axisWidth); //} return axisWidth; } Axis.prototype.createLabelsAndCalculateHeight = function () { var maxLabelEffectiveHeight = 0; this._labels = []; var textBlock; var i = 0; this.createLabels(); if (this._position === "bottom" || this._position === "top") { for (i = 0; i < this._labels.length; i++) { textBlock = this._labels[i].textBlock; var size = textBlock.measureText(); //var diagonal = Math.sqrt(Math.pow(size.height, 2) + Math.pow(size.width, 2)); //var hypotenuse = Math.sqrt(Math.pow(size.height / 2, 2) + Math.pow(size.width, 2)); //var labelEffectiveHeight = hypotenuse * Math.cos(Math.PI / 2 - (Math.abs(Math.PI / 180 * this.labelAngle) + Math.abs(Math.acos(size.width / hypotenuse)))); var labelEffectiveHeight = 0; if (this.labelAngle === 0) labelEffectiveHeight = size.height; else labelEffectiveHeight = (size.width * Math.sin(Math.PI / 180 * Math.abs(this.labelAngle))) + (size.height / 2 * Math.cos(Math.PI / 180 * Math.abs(this.labelAngle))); if (maxLabelEffectiveHeight < labelEffectiveHeight) maxLabelEffectiveHeight = labelEffectiveHeight; this._labels[i].effectiveHeight = labelEffectiveHeight; } } //var titleHeight = this.title ? this.titleFontSize + 5 : 0; var titleHeight = this.title ? getFontHeightInPixels(this.titleFontFamily, this.titleFontSize, this.titleFontWeight) + 2 : 0; return titleHeight + maxLabelEffectiveHeight + this.tickLength + 5; } //Static Method that co-ordinates between axisX, axisY and renders them Axis.setLayoutAndRender = function (axisX, axisY, axisY2, axisPlacement, freeSpace) { var x1, y1, x2, y2; var chart = axisX.chart; var ctx = chart.ctx; axisX.calculateAxisParameters(); if (axisY) axisY.calculateAxisParameters(); if (axisY2) axisY2.calculateAxisParameters(); //if (axisY && axisY2 && typeof (axisY._options.viewportMaximum) === "undefined" && typeof (axisY._options.viewportMinimum) === "undefined" && typeof (axisY._options.interval) === "undefined" // && typeof (axisY2._options.viewportMaximum) === "undefined" && typeof (axisY2._options.viewportMinimum) === "undefined" && typeof (axisY2._options.interval) === "undefined") { // var noTicksY = (axisY.viewportMaximum - axisY.viewportMinimum) / axisY.interval; // var noTicksY2 = (axisY2.viewportMaximum - axisY2.viewportMinimum) / axisY2.interval; // if (noTicksY > noTicksY2) { // axisY2.viewportMaximum = axisY2.interval * noTicksY + axisY2.viewportMinimum; // } else if (noTicksY2 > noTicksY) { // axisY.viewportMaximum = axisY.interval * noTicksY2 + axisY.viewportMinimum; // } //} var axisYlineThickness = axisY ? axisY.lineThickness ? axisY.lineThickness : 0 : 0; var axisY2lineThickness = axisY2 ? axisY2.lineThickness ? axisY2.lineThickness : 0 : 0; var axisYGridThickness = axisY ? axisY.gridThickness ? axisY.gridThickness : 0 : 0; var axisY2GridThickness = axisY2 ? axisY2.gridThickness ? axisY2.gridThickness : 0 : 0; var axisYMargin = axisY ? axisY.margin : 0; var axisY2Margin = axisY ? axisY.margin : 0; if (axisPlacement === "normal") { axisX.lineCoordinates = { }; var axisYWidth = Math.ceil(axisY ? axisY.createLabelsAndCalculateWidth() : 0); x1 = Math.round(freeSpace.x1 + axisYWidth + axisYMargin); axisX.lineCoordinates.x1 = x1; var axisY2Width = Math.ceil(axisY2 ? axisY2.createLabelsAndCalculateWidth() : 0); x2 = Math.round(freeSpace.x2 - axisY2Width > axisX.chart.width - 10 ? axisX.chart.width - 10 : freeSpace.x2 - axisY2Width); axisX.lineCoordinates.x2 = x2; axisX.lineCoordinates.width = Math.abs(x2 - x1); // required early on inside createLabels of axisX var axisXHeight = Math.ceil(axisX.createLabelsAndCalculateHeight()); // Position axisX based on the available free space, Margin and its height //x1 = freeSpace.x1 + axisYWidth + axisYMargin + axisYlineThickness / 2; y1 = Math.round(freeSpace.y2 - axisXHeight - axisX.margin); y2 = Math.round(freeSpace.y2 - axisX.margin); //axisX.lineCoordinates = { x1: x1, y1: y1, x2: x2, y2: y1, width: Math.abs(x2 - x1) } axisX.lineCoordinates.y1 = y1; axisX.lineCoordinates.y2 = y1; axisX.boundingRect = { x1: x1, y1: y1, x2: x2, y2: y2, width: x2 - x1, height: y2 - y1 }; //if (isDebugMode) { // axisX.ctx.rect(axisX.boundingRect.x1, axisX.boundingRect.y1, axisX.boundingRect.width, axisX.boundingRect.height); // axisX.ctx.stroke(); //} // Position axisY based on the available free space, Margin and its height if (axisY) { x1 = Math.round(freeSpace.x1 + axisY.margin); y1 = Math.round(freeSpace.y1 < 10 ? 10 : freeSpace.y1); x2 = Math.round(freeSpace.x1 + axisYWidth + axisY.margin); //y2 = freeSpace.y2 - axisXHeight - axisX.margin - axisX.lineThickness / 2; y2 = Math.round(freeSpace.y2 - axisXHeight - axisX.margin); axisY.lineCoordinates = { x1: x2, y1: y1, x2: x2, y2: y2, height: Math.abs(y2 - y1) } axisY.boundingRect = { x1: x1, y1: y1, x2: x2, y2: y2, width: x2 - x1, height: y2 - y1 }; } //if (isDebugMode && axisY) { // axisY.ctx.rect(axisY.boundingRect.x1, axisY.boundingRect.y1, axisY.boundingRect.width, axisY.boundingRect.height); // axisY.ctx.stroke(); //} // Position axisY2 based on the available free space, Margin and its height if (axisY2) { x1 = Math.round(axisX.lineCoordinates.x2); y1 = Math.round(freeSpace.y1 < 10 ? 10 : freeSpace.y1); x2 = Math.round(x1 + axisY2Width + axisY2.margin); //y2 = freeSpace.y2 - axisXHeight - axisX.margin - axisX.lineThickness / 2; y2 = Math.round(freeSpace.y2 - axisXHeight - axisX.margin); axisY2.lineCoordinates = { x1: x1, y1: y1, x2: x1, y2: y2, height: Math.abs(y2 - y1) } axisY2.boundingRect = { x1: x1, y1: y1, x2: x2, y2: y2, width: x2 - x1, height: y2 - y1 }; } axisX.calculateValueToPixelConversionParameters(); if (axisY) axisY.calculateValueToPixelConversionParameters(); if (axisY2) axisY2.calculateValueToPixelConversionParameters(); ctx.save(); ctx.rect(5, axisX.boundingRect.y1, axisX.chart.width - 10, axisX.boundingRect.height); ctx.clip(); axisX.renderLabelsTicksAndTitle(); ctx.restore(); if (axisY) axisY.renderLabelsTicksAndTitle(); if (axisY2) axisY2.renderLabelsTicksAndTitle(); chart.preparePlotArea(); var plotArea = axisX.chart.plotArea; ctx.save(); ctx.rect(plotArea.x1, plotArea.y1, Math.abs(plotArea.x2 - plotArea.x1), Math.abs(plotArea.y2 - plotArea.y1)); ctx.clip(); axisX.renderStripLinesOfThicknessType("value"); if (axisY) axisY.renderStripLinesOfThicknessType("value"); if (axisY2) axisY2.renderStripLinesOfThicknessType("value"); axisX.renderInterlacedColors(); if (axisY) axisY.renderInterlacedColors(); if (axisY2) axisY2.renderInterlacedColors(); ctx.restore(); axisX.renderGrid(); if (axisY) axisY.renderGrid(); if (axisY2) axisY2.renderGrid(); axisX.renderAxisLine(); if (axisY) axisY.renderAxisLine(); if (axisY2) axisY2.renderAxisLine(); //No need to clip to plotArea because stripLines need to render on top of gridlines axisX.renderStripLinesOfThicknessType("pixel"); if (axisY) axisY.renderStripLinesOfThicknessType("pixel"); if (axisY2) axisY2.renderStripLinesOfThicknessType("pixel"); } else { var axisXWidth = Math.ceil(axisX.createLabelsAndCalculateWidth()); if (axisY) { axisY.lineCoordinates = { }; x1 = Math.round(freeSpace.x1 + axisXWidth + axisX.margin); x2 = Math.round(freeSpace.x2 > axisY.chart.width - 10 ? axisY.chart.width - 10 : freeSpace.x2); axisY.lineCoordinates.x1 = x1; axisY.lineCoordinates.x2 = x2; axisY.lineCoordinates.width = Math.abs(x2 - x1); } if (axisY2) { axisY2.lineCoordinates = { }; x1 = Math.round(freeSpace.x1 + axisXWidth + axisX.margin); x2 = Math.round(freeSpace.x2 > axisY2.chart.width - 10 ? axisY2.chart.width - 10 : freeSpace.x2); axisY2.lineCoordinates.x1 = x1; axisY2.lineCoordinates.x2 = x2; axisY2.lineCoordinates.width = Math.abs(x2 - x1); } var axisYHeight = Math.ceil(axisY ? axisY.createLabelsAndCalculateHeight() : 0); var axisY2Height = Math.ceil(axisY2 ? axisY2.createLabelsAndCalculateHeight() : 0); // Position axisY based on the available free space, Margin and its height if (axisY) { //x1 = freeSpace.x1 + axisXWidth + axisX.margin + axisX.lineThickness / 2; //x2 = freeSpace.x2 > axisY.chart.width - 10 ? axisY.chart.width - 10 : freeSpace.x2; y1 = Math.round(freeSpace.y2 - axisYHeight - axisY.margin); y2 = Math.round(freeSpace.y2 - axisYMargin > axisY.chart.height - 10 ? axisY.chart.height - 10 : freeSpace.y2 - axisYMargin); //axisY.lineCoordinates = { x1: x1, y1: y1, x2: x2, y2: y1, width: Math.abs(x2 - x1) } axisY.lineCoordinates.y1 = y1; axisY.lineCoordinates.y2 = y1; axisY.boundingRect = { x1: x1, y1: y1, x2: x2, y2: y2, width: x2 - x1, height: axisYHeight }; } // Position axisY based on the available free space, Margin and its height if (axisY2) { //x1 = freeSpace.x1 + axisXWidth + axisX.margin + axisX.lineThickness / 2; //x2 = freeSpace.x2 > axisY2.chart.width - 10 ? axisY2.chart.width - 10 : freeSpace.x2; y1 = Math.round(freeSpace.y1 + axisY2.margin); y2 = (freeSpace.y1 + axisY2.margin + axisY2Height); //axisY2.lineCoordinates = { x1: x1, y1: y2, x2: x2, y2: y2, width: Math.abs(x2 - x1) } axisY2.lineCoordinates.y1 = y2; axisY2.lineCoordinates.y2 = y2; axisY2.boundingRect = { x1: x1, y1: y1, x2: x2, y2: y2, width: x2 - x1, height: axisY2Height }; } //axisY.ctx.rect(axisY.boundingRect.x1, axisY.boundingRect.y1, axisY.boundingRect.width, axisY.boundingRect.height); //axisY.ctx.stroke(); // Position axisX based on the available free space, Margin and its height x1 = Math.round(freeSpace.x1 + axisX.margin); y1 = Math.round(axisY2 ? axisY2.lineCoordinates.y2 : (freeSpace.y1 < 10 ? 10 : freeSpace.y1)); x2 = Math.round(freeSpace.x1 + axisXWidth + axisX.margin); y2 = Math.round(axisY ? axisY.lineCoordinates.y1 : (freeSpace.y2 - axisYMargin > axisX.chart.height - 10 ? axisX.chart.height - 10 : freeSpace.y2 - axisYMargin)); axisX.lineCoordinates = { x1: x2, y1: y1, x2: x2, y2: y2, height: Math.abs(y2 - y1) }; axisX.boundingRect = { x1: x1, y1: y1, x2: x2, y2: y2, width: x2 - x1, height: y2 - y1 }; //axisX.ctx.rect(axisX.boundingRect.x1, axisX.boundingRect.y1, axisX.boundingRect.width, axisX.boundingRect.height); //axisX.ctx.stroke(); axisX.calculateValueToPixelConversionParameters(); if (axisY) axisY.calculateValueToPixelConversionParameters(); if (axisY2) axisY2.calculateValueToPixelConversionParameters(); //ctx.save(); //ctx.rect(axisY.boundingRect.x1 - 30, axisY.boundingRect.y1, axisY.boundingRect.width + 60, axisY.boundingRect.height); //ctx.clip(); if (axisY) axisY.renderLabelsTicksAndTitle(); if (axisY2) axisY2.renderLabelsTicksAndTitle(); //ctx.restore(); axisX.renderLabelsTicksAndTitle(); chart.preparePlotArea(); var plotArea = axisX.chart.plotArea; ctx.save(); ctx.rect(plotArea.x1, plotArea.y1, Math.abs(plotArea.x2 - plotArea.x1), Math.abs(plotArea.y2 - plotArea.y1)); ctx.clip(); //No need to clip to plotArea because stripLines need to render on top of gridlines axisX.renderStripLinesOfThicknessType("value"); if (axisY) axisY.renderStripLinesOfThicknessType("value"); if (axisY2) axisY2.renderStripLinesOfThicknessType("value"); axisX.renderInterlacedColors(); if (axisY) axisY.renderInterlacedColors(); if (axisY2) axisY2.renderInterlacedColors(); ctx.restore(); axisX.renderGrid(); if (axisY) axisY.renderGrid(); if (axisY2) axisY2.renderGrid(); axisX.renderAxisLine(); if (axisY) axisY.renderAxisLine(); if (axisY2) axisY2.renderAxisLine(); axisX.renderStripLinesOfThicknessType("pixel"); if (axisY) axisY.renderStripLinesOfThicknessType("pixel"); if (axisY2) axisY2.renderStripLinesOfThicknessType("pixel"); } } Axis.prototype.renderLabelsTicksAndTitle = function () { var skipLabels = false; var totalLabelWidth = 0; var thresholdRatio = 1; var labelCount = 0; var intervalInPixels = this.conversionParameters.pixelPerUnit * this.interval; if (this.labelAngle !== 0 && this.labelAngle !== 360) thresholdRatio = 1.2; //Don't skip labels when interval is explicitely set if (typeof (this._options.interval) === "undefined") { if (this._position === "bottom" || this._position === "top") { //thresholdRatio = .9;// More space is preferred between labels when axis is horizontally aligned for (i = 0; i < this._labels.length; i++) { label = this._labels[i]; if (label.position < this.viewportMinimum || label.stripLine)// don't consider stripLine's lable continue; var width = label.textBlock.width * Math.cos(Math.PI / 180 * this.labelAngle) + label.textBlock.height * Math.sin(Math.PI / 180 * this.labelAngle); totalLabelWidth += width; } if (totalLabelWidth > this.lineCoordinates.width * thresholdRatio) { skipLabels = true; } } if (this._position === "left" || this._position === "right") { for (i = 0; i < this._labels.length; i++) { label = this._labels[i]; if (label.position < this.viewportMinimum || label.stripLine)// don't consider stripLine's lable continue; var width = label.textBlock.height * Math.cos(Math.PI / 180 * this.labelAngle) + label.textBlock.width * Math.sin(Math.PI / 180 * this.labelAngle); totalLabelWidth += width; } if (totalLabelWidth > this.lineCoordinates.height * thresholdRatio) { skipLabels = true; } } } if (this._position === "bottom") { var i = 0; var label; var xy; for (i = 0; i < this._labels.length; i++) { label = this._labels[i]; if (label.position < this.viewportMinimum || label.position > this.viewportMaximum) continue; xy = this.getPixelCoordinatesOnAxis(label.position); if ((this.tickThickness && !this._labels[i].stripLine) || (this._labels[i].stripLine && this._labels[i].stripLine._thicknessType === "pixel")) { if (this._labels[i].stripLine) { stripLine = this._labels[i].stripLine; this.ctx.lineWidth = stripLine.thickness; this.ctx.strokeStyle = stripLine.color; } else { this.ctx.lineWidth = this.tickThickness; this.ctx.strokeStyle = this.tickColor; } var tickX = (this.ctx.lineWidth % 2 === 1) ? (xy.x << 0) + .5 : (xy.x << 0); this.ctx.beginPath(); this.ctx.moveTo(tickX, xy.y << 0); this.ctx.lineTo(tickX, (xy.y + this.tickLength) << 0); this.ctx.stroke(); } //Don't skip stripLine's labels if (skipLabels && labelCount++ % 2 !== 0 && !this._labels[i].stripLine) continue; if (label.textBlock.angle === 0) { xy.x -= label.textBlock.width / 2; //xy.y += this.tickLength + label.textBlock.height / 2; xy.y += this.tickLength + label.textBlock.fontSize / 2; } else { xy.x -= (this.labelAngle < 0 ? (label.textBlock.width * Math.cos(Math.PI / 180 * this.labelAngle)) : 0); xy.y += this.tickLength + Math.abs((this.labelAngle < 0 ? label.textBlock.width * Math.sin(Math.PI / 180 * this.labelAngle) - 5 : 5)); } label.textBlock.x = xy.x; label.textBlock.y = xy.y; label.textBlock.render(true); } if (this.title) { this._titleTextBlock = new TextBlock(this.ctx, { x: this.lineCoordinates.x1,// This is recalculated again y: this.boundingRect.y2 - this.titleFontSize - 5,// This is recalculated again maxWidth: this.lineCoordinates.width, maxHeight: this.titleFontSize * 1.5, angle: 0, text: this.title, horizontalAlign: "center",//left, center, right fontSize: this.titleFontSize,//in pixels fontFamily: this.titleFontFamily, fontWeight: this.titleFontWeight, //normal, bold, bolder, lighter, fontColor: this.titleFontColor, fontStyle: this.titleFontStyle, // normal, italic, oblique textBaseline: "top" }); this._titleTextBlock.measureText(); this._titleTextBlock.x = this.lineCoordinates.x1 + this.lineCoordinates.width / 2 - this._titleTextBlock.width / 2; this._titleTextBlock.y = this.boundingRect.y2 - this._titleTextBlock.height - 3; this._titleTextBlock.render(true); } } else if (this._position === "top") { var i = 0; var label; var xy; var stripLine; for (i = 0; i < this._labels.length; i++) { label = this._labels[i]; if (label.position < this.viewportMinimum || label.position > this.viewportMaximum) continue; xy = this.getPixelCoordinatesOnAxis(label.position); if ((this.tickThickness && !this._labels[i].stripLine) || (this._labels[i].stripLine && this._labels[i].stripLine._thicknessType === "pixel")) { if (this._labels[i].stripLine) { stripLine = this._labels[i].stripLine; this.ctx.lineWidth = stripLine.thickness; this.ctx.strokeStyle = stripLine.color; } else { this.ctx.lineWidth = this.tickThickness; this.ctx.strokeStyle = this.tickColor; } var tickX = (this.ctx.lineWidth % 2 === 1) ? (xy.x << 0) + .5 : (xy.x << 0); this.ctx.beginPath(); this.ctx.moveTo(tickX, xy.y << 0); this.ctx.lineTo(tickX, (xy.y - this.tickLength) << 0); this.ctx.stroke(); } //Don't skip stripLine's labels if (skipLabels && labelCount++ % 2 !== 0 && !this._labels[i].stripLine) continue; if (label.textBlock.angle === 0) { xy.x -= label.textBlock.width / 2; xy.y -= this.tickLength + label.textBlock.height / 2; } else { xy.x -= (this.labelAngle > 0 ? (label.textBlock.width * Math.cos(Math.PI / 180 * this.labelAngle)) : 0); xy.y -= this.tickLength + Math.abs((this.labelAngle > 0 ? label.textBlock.width * Math.sin(Math.PI / 180 * this.labelAngle) + 5 : 5)); } label.textBlock.x = xy.x; label.textBlock.y = xy.y; label.textBlock.render(true); } if (this.title) { this._titleTextBlock = new TextBlock(this.ctx, { x: this.lineCoordinates.x1,// This is recalculated again y: this.boundingRect.y1 + 1, maxWidth: this.lineCoordinates.width, maxHeight: this.titleFontSize * 1.5, angle: 0, text: this.title, horizontalAlign: "center",//left, center, right fontSize: this.titleFontSize,//in pixels fontFamily: this.titleFontFamily, fontWeight: this.titleFontWeight, //normal, bold, bolder, lighter, fontColor: this.titleFontColor, fontStyle: this.titleFontStyle, // normal, italic, oblique textBaseline: "top" }); this._titleTextBlock.measureText(); this._titleTextBlock.x = this.lineCoordinates.x1 + this.lineCoordinates.width / 2 - this._titleTextBlock.width / 2; this._titleTextBlock.render(true); } } else if (this._position === "left") { var label; var xy; for (var i = 0; i < this._labels.length; i++) { label = this._labels[i]; if (label.position < this.viewportMinimum || label.position > this.viewportMaximum) continue; xy = this.getPixelCoordinatesOnAxis(label.position); if ((this.tickThickness && !this._labels[i].stripLine) || (this._labels[i].stripLine && this._labels[i].stripLine._thicknessType === "pixel")) { if (this._labels[i].stripLine) { stripLine = this._labels[i].stripLine; this.ctx.lineWidth = stripLine.thickness; this.ctx.strokeStyle = stripLine.color; } else { this.ctx.lineWidth = this.tickThickness; this.ctx.strokeStyle = this.tickColor; } var tickY = (this.ctx.lineWidth % 2 === 1) ? (xy.y << 0) + .5 : (xy.y << 0); this.ctx.beginPath(); this.ctx.moveTo(xy.x << 0, tickY); this.ctx.lineTo((xy.x - this.tickLength) << 0, tickY); this.ctx.stroke(); } //Don't skip stripLine's labels if (skipLabels && labelCount++ % 2 !== 0 && !this._labels[i].stripLine) continue; label.textBlock.x = xy.x - (label.textBlock.width * Math.cos(Math.PI / 180 * this.labelAngle)) - this.tickLength - 5; if (this.labelAngle === 0) { label.textBlock.y = xy.y; } else label.textBlock.y = xy.y - (label.textBlock.width * Math.sin(Math.PI / 180 * this.labelAngle)); label.textBlock.render(true); } if (this.title) { this._titleTextBlock = new TextBlock(this.ctx, { x: this.boundingRect.x1 + 1, y: this.lineCoordinates.y2, maxWidth: this.lineCoordinates.height, maxHeight: this.titleFontSize * 1.5, angle: -90, text: this.title, horizontalAlign: "center",//left, center, right fontSize: this.titleFontSize,//in pixels fontFamily: this.titleFontFamily, fontWeight: this.titleFontWeight, //normal, bold, bolder, lighter, fontColor: this.titleFontColor, fontStyle: this.titleFontStyle, // normal, italic, oblique textBaseline: "top" }); var size = this._titleTextBlock.measureText(); //this._titleTextBlock.x -= 4; this._titleTextBlock.y = (this.lineCoordinates.height / 2 + this._titleTextBlock.width / 2 + this.lineCoordinates.y1); this._titleTextBlock.render(true); //if (isDebugMode) { // window.console.log("titleFontSize: " + this.titleFontSize + "; width: " + size.width + "; height: " + size.height); // window.console.log("this.boundingRect.x1: " + this.boundingRect.x1); // //this.ctx.rect(this._titleTextBlock.x, this._titleTextBlock.y, this._titleTextBlock.height, -this._titleTextBlock.width); // //this.ctx.stroke(); //} } } else if (this._position === "right") { var label; var xy; for (var i = 0; i < this._labels.length; i++) { label = this._labels[i]; if (label.position < this.viewportMinimum || label.position > this.viewportMaximum) continue; xy = this.getPixelCoordinatesOnAxis(label.position); if ((this.tickThickness && !this._labels[i].stripLine) || (this._labels[i].stripLine && this._labels[i].stripLine._thicknessType === "pixel")) { if (this._labels[i].stripLine) { stripLine = this._labels[i].stripLine; this.ctx.lineWidth = stripLine.thickness; this.ctx.strokeStyle = stripLine.color; } else { this.ctx.lineWidth = this.tickThickness; this.ctx.strokeStyle = this.tickColor; } var tickY = (this.ctx.lineWidth % 2 === 1) ? (xy.y << 0) + .5 : (xy.y << 0); this.ctx.beginPath(); this.ctx.moveTo(xy.x << 0, tickY); this.ctx.lineTo((xy.x + this.tickLength) << 0, tickY); this.ctx.stroke(); } //Don't skip stripLine's labels if (skipLabels && labelCount++ % 2 !== 0 && !this._labels[i].stripLine) continue; label.textBlock.x = xy.x + this.tickLength + 5; //label.textBlock.y = xy.y - (label.textBlock.width * Math.sin(Math.PI / 180 * this.labelAngle)); if (this.labelAngle === 0) { label.textBlock.y = xy.y; } else label.textBlock.y = xy.y; label.textBlock.render(true); } if (this.title) { this._titleTextBlock = new TextBlock(this.ctx, { x: this.boundingRect.x2 - 1, y: this.lineCoordinates.y2, maxWidth: this.lineCoordinates.height, maxHeight: this.titleFontSize * 1.5, angle: 90, text: this.title, horizontalAlign: "center",//left, center, right fontSize: this.titleFontSize,//in pixels fontFamily: this.titleFontFamily, fontWeight: this.titleFontWeight, //normal, bold, bolder, lighter, fontColor: this.titleFontColor, fontStyle: this.titleFontStyle, // normal, italic, oblique textBaseline: "top" }); this._titleTextBlock.measureText(); this._titleTextBlock.y = (this.lineCoordinates.height / 2 - this._titleTextBlock.width / 2 + this.lineCoordinates.y1); this._titleTextBlock.render(true); } } } Axis.prototype.renderInterlacedColors = function () { var ctx = this.chart.plotArea.ctx; //return; var interlacedGridStartPoint; var interlacedGridEndPoint; var plotAreaCoordinates = this.chart.plotArea; var i = 0, renderInterlacedGrid = true; if ((this._position === "bottom" || this._position === "top") && this.interlacedColor) { ctx.fillStyle = this.interlacedColor; for (i = 0; i < this._labels.length; i++) { if (this._labels[i].stripLine) continue; if (renderInterlacedGrid) {//So that the interlaced color alternates interlacedGridStartPoint = this.getPixelCoordinatesOnAxis(this._labels[i].position); if (i + 1 >= this._labels.length - 1) interlacedGridEndPoint = this.getPixelCoordinatesOnAxis(this.viewportMaximum); else interlacedGridEndPoint = this.getPixelCoordinatesOnAxis(this._labels[i + 1].position); ctx.fillRect(interlacedGridStartPoint.x, plotAreaCoordinates.y1, Math.abs(interlacedGridEndPoint.x - interlacedGridStartPoint.x), Math.abs(plotAreaCoordinates.y1 - plotAreaCoordinates.y2)); renderInterlacedGrid = false; } else renderInterlacedGrid = true; } } else if ((this._position === "left" || this._position === "right") && this.interlacedColor) { ctx.fillStyle = this.interlacedColor; for (i = 0; i < this._labels.length; i++) { if (this._labels[i].stripLine) continue; if (renderInterlacedGrid) {//So that the interlaced color alternates interlacedGridEndPoint = this.getPixelCoordinatesOnAxis(this._labels[i].position); if (i + 1 >= this._labels.length - 1) interlacedGridStartPoint = this.getPixelCoordinatesOnAxis(this.viewportMaximum); else interlacedGridStartPoint = this.getPixelCoordinatesOnAxis(this._labels[i + 1].position); ctx.fillRect(plotAreaCoordinates.x1, interlacedGridStartPoint.y, Math.abs(plotAreaCoordinates.x1 - plotAreaCoordinates.x2), Math.abs(interlacedGridStartPoint.y - interlacedGridEndPoint.y)); renderInterlacedGrid = false; } else renderInterlacedGrid = true; } //throw "123"; } ctx.beginPath(); } //Renders stripLines of given thickness type. Axis.prototype.renderStripLinesOfThicknessType = function (thicknessType) { if (!(this.stripLines && this.stripLines.length > 0) || !thicknessType) return; var _this = this; var i = 0; for (i = 0; i < this.stripLines.length; i++) { var stripLine = this.stripLines[i]; if (stripLine._thicknessType !== thicknessType) continue; //Should be skipped only if thicknessType is "pixel". If it is "value" then clipping is automatically applied before calling. if (thicknessType === "pixel" && (stripLine.value < this.viewportMinimum || stripLine.value > this.viewportMaximum)) continue; if (stripLine.showOnTop) { this.chart.addEventListener("dataAnimationIterationEnd", stripLine.render, stripLine); } else stripLine.render(); } }; Axis.prototype.renderGrid = function () { if (!(this.gridThickness && this.gridThickness > 0)) return; //var ctx = this.chart.plotArea.ctx; var ctx = this.chart.ctx; var xy; var plotAreaCoordinates = this.chart.plotArea; var stripLine; var tempLineWidth, tempStrokeStyle; //return; ctx.lineWidth = this.gridThickness; ctx.strokeStyle = this.gridColor; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(this.gridDashType, this.gridThickness)); } if (this._position === "bottom" || this._position === "top") { for (i = 0; i < this._labels.length && !this._labels[i].stripLine; i++) { if (this._labels[i].position < this.viewportMinimum || this._labels[i].position > this.viewportMaximum) continue; ctx.beginPath(); xy = this.getPixelCoordinatesOnAxis(this._labels[i].position); var gridX = (ctx.lineWidth % 2 === 1) ? (xy.x << 0) + .5 : (xy.x << 0); ctx.moveTo(gridX, plotAreaCoordinates.y1 << 0); ctx.lineTo(gridX, plotAreaCoordinates.y2 << 0); ctx.stroke(); } } else if (this._position === "left" || this._position === "right") { for (var i = 0; i < this._labels.length && !this._labels[i].stripLine; i++) { if (i === 0 && this.type === "axisY" && this.chart.axisX && this.chart.axisX.lineThickness) continue; if (this._labels[i].position < this.viewportMinimum || this._labels[i].position > this.viewportMaximum) continue; ctx.beginPath(); xy = this.getPixelCoordinatesOnAxis(this._labels[i].position); var gridY = (ctx.lineWidth % 2 === 1) ? (xy.y << 0) + .5 : (xy.y << 0); ctx.moveTo(plotAreaCoordinates.x1 << 0, gridY); ctx.lineTo(plotAreaCoordinates.x2 << 0, gridY); ctx.stroke(); } } } Axis.prototype.renderAxisLine = function () { //var ctx = this.chart.plotArea.ctx; var ctx = this.chart.ctx; if (this._position === "bottom" || this._position === "top") { if (this.lineThickness) { ctx.lineWidth = this.lineThickness; ctx.strokeStyle = this.lineColor ? this.lineColor : "black"; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(this.lineDashType, this.lineThickness)); } var lineY = (this.lineThickness % 2 === 1) ? (this.lineCoordinates.y1 << 0) + .5 : (this.lineCoordinates.y1 << 0); ctx.beginPath(); ctx.moveTo(this.lineCoordinates.x1, lineY); ctx.lineTo(this.lineCoordinates.x2, lineY); ctx.stroke(); } } else if (this._position === "left" || this._position === "right") { if (this.lineThickness) { ctx.lineWidth = this.lineThickness; ctx.strokeStyle = this.lineColor; if (ctx.setLineDash) { ctx.setLineDash(getLineDashArray(this.lineDashType, this.lineThickness)); } var lineX = (this.lineThickness % 2 === 1) ? (this.lineCoordinates.x1 << 0) + .5 : (this.lineCoordinates.x1 << 0); ctx.beginPath(); ctx.moveTo(lineX, this.lineCoordinates.y1); ctx.lineTo(lineX, this.lineCoordinates.y2); ctx.stroke(); } } } Axis.prototype.getPixelCoordinatesOnAxis = function (value) { var xy = { }; var width = this.lineCoordinates.width; var height = this.lineCoordinates.height; if (this._position === "bottom" || this._position === "top") { //var pixelPerUnit = width / Math.abs(this.viewportMaximum - this.viewportMinimum); var pixelPerUnit = this.conversionParameters.pixelPerUnit; //xy.x = this.lineCoordinates.x1 + (pixelPerUnit * (value - this.viewportMinimum)); xy.x = this.conversionParameters.reference + (pixelPerUnit * (value - this.viewportMinimum)); xy.y = this.lineCoordinates.y1; } if (this._position === "left" || this._position === "right") { //var pixelPerUnit = height / Math.abs(this.viewportMaximum - this.viewportMinimum); var pixelPerUnit = -this.conversionParameters.pixelPerUnit; //xy.y = this.lineCoordinates.y2 - (pixelPerUnit * (value - this.viewportMinimum)); xy.y = this.conversionParameters.reference - (pixelPerUnit * (value - this.viewportMinimum)); xy.x = this.lineCoordinates.x2; } return xy; } Axis.prototype.convertPixelToValue = function (pixel) { if (!pixel) return null; var value = 0; var p = (this._position === "left" || this._position === "right") ? pixel.y : pixel.x; value = this.conversionParameters.minimum + (p - this.conversionParameters.reference) / this.conversionParameters.pixelPerUnit; return value; } Axis.prototype.setViewPortRange = function (viewportMinimum, viewportMaximum) { this.sessionVariables.newViewportMinimum = this.viewportMinimum = Math.min(viewportMinimum, viewportMaximum); this.sessionVariables.newViewportMaximum = this.viewportMaximum = Math.max(viewportMinimum, viewportMaximum); } Axis.prototype.getXValueAt = function (pixel) { if (!pixel) return null; var xval = null; if (this._position === "left") { xval = (this.chart.axisX.viewportMaximum - this.chart.axisX.viewportMinimum) / this.chart.axisX.lineCoordinates.height * ((this.chart.axisX.lineCoordinates.y2 - pixel.y)) + this.chart.axisX.viewportMinimum; } else if (this._position === "bottom") { xval = (this.chart.axisX.viewportMaximum - this.chart.axisX.viewportMinimum) / this.chart.axisX.lineCoordinates.width * (pixel.x - this.chart.axisX.lineCoordinates.x1) + this.chart.axisX.viewportMinimum; } return xval; } Axis.prototype.calculateValueToPixelConversionParameters = function (value) { this.reversed = false; var conversionParameters = { pixelPerUnit: null, minimum: null, reference: null }; var width = this.lineCoordinates.width; var height = this.lineCoordinates.height; conversionParameters.minimum = this.viewportMinimum; if (this._position === "bottom" || this._position === "top") { conversionParameters.pixelPerUnit = (this.reversed ? -1 : 1) * width / Math.abs(this.viewportMaximum - this.viewportMinimum); conversionParameters.reference = (this.reversed ? this.lineCoordinates.x2 : this.lineCoordinates.x1); } if (this._position === "left" || this._position === "right") { conversionParameters.pixelPerUnit = (this.reversed ? 1 : -1) * height / Math.abs(this.viewportMaximum - this.viewportMinimum); conversionParameters.reference = (this.reversed ? this.lineCoordinates.y1 : this.lineCoordinates.y2); } this.conversionParameters = conversionParameters; } Axis.prototype.calculateAxisParameters = function () { var freeSpace = this.chart.layoutManager.getFreeSpace(); var availableWidth = 0; var availableHeight = 0; var isLessThanTwoDataPoints = false; if (this._position === "bottom" || this._position === "top") { this.maxWidth = freeSpace.width; this.maxHeight = freeSpace.height; } else { this.maxWidth = freeSpace.height; this.maxHeight = freeSpace.width; } var noTicks = this.type === "axisX" ? (this.maxWidth < 500 ? 8 : Math.max(6, Math.floor(this.maxWidth / 62))) : Math.max(Math.floor(this.maxWidth / 40), 2); var min, max; var minDiff; var range; var rangePadding = 0; if (this.viewportMinimum === null || isNaN(this.viewportMinimum)) this.viewportMinimum = this.minimum; if (this.viewportMaximum === null || isNaN(this.viewportMaximum)) this.viewportMaximum = this.maximum; if (this.type === "axisX") { min = (this.viewportMinimum !== null) ? this.viewportMinimum : this.dataInfo.viewPortMin; max = (this.viewportMaximum !== null) ? this.viewportMaximum : this.dataInfo.viewPortMax; if (max - min === 0) { rangePadding = typeof (this._options.interval) === "undefined" ? .4 : this._options.interval; max += rangePadding; min -= rangePadding; } if (this.dataInfo.minDiff !== Infinity) minDiff = this.dataInfo.minDiff; else if (max - min > 1) { minDiff = Math.abs(max - min) * .5; } else { minDiff = 1; if (this.chart.plotInfo.axisXValueType === "dateTime") isLessThanTwoDataPoints = true; } } else if (this.type === "axisY") { //min = typeof (this._options.viewportMinimum) === "undefined" || this._options.viewportMinimum === null ? this.dataInfo.viewPortMin : this._options.viewportMinimum; //max = typeof (this._options.viewportMaximum) === "undefined" || this._options.viewportMaximum === null ? this.dataInfo.viewPortMax : this._options.viewportMaximum; min = (this.viewportMinimum !== null) ? this.viewportMinimum : this.dataInfo.viewPortMin; max = (this.viewportMaximum !== null) ? this.viewportMaximum : this.dataInfo.viewPortMax; if (!isFinite(min) && !isFinite(max)) { max = typeof (this._options.interval) === "undefined" ? -Infinity : this._options.interval; min = 0; } else if (!isFinite(min)) { min = max; } else if (!isFinite(max)) { max = min; } if (min === 0 && max === 0) {// When all dataPoints are zero max += 9; min = 0; } else if (max - min === 0) {// When there is only a single dataPoint or when all dataPoints have same Y Value rangePadding = Math.min(Math.abs(Math.abs(max) * .01), 5); max += rangePadding; min -= rangePadding; } else if (min > max) { rangePadding = Math.min(Math.abs(Math.abs(max - min) * .01), 5); if (max >= 0) min = max - rangePadding; else max = min + rangePadding; } else { rangePadding = Math.min(Math.abs(Math.abs(max - min) * .01), .05); if (max !== 0) max += rangePadding; if (min !== 0) min -= rangePadding; } if (this.dataInfo.minDiff !== Infinity) minDiff = this.dataInfo.minDiff; else if (max - min > 1) { minDiff = Math.abs(max - min) * .5; } else { minDiff = 1; } //Apply includeZero if (this.includeZero && (this.viewportMinimum === null || isNaN(this.viewportMinimum))) { if (min > 0) min = 0; } if (this.includeZero && (this.viewportMaximum === null || isNaN(this.viewportMaximum))) { if (max < 0) max = 0; } } range = (isNaN(this.viewportMaximum) || this.viewportMaximum === null ? max : this.viewportMaximum) - (isNaN(this.viewportMinimum) || this.viewportMinimum === null ? min : this.viewportMinimum); if (this.type === "axisX" && this.chart.plotInfo.axisXValueType === "dateTime") { if (!this.intervalType) { if (range / (1 * 1) <= noTicks) { this.interval = 1; this.intervalType = "millisecond"; } else if (range / (1 * 2) <= noTicks) { this.interval = 2; this.intervalType = "millisecond"; } else if (range / (1 * 5) <= noTicks) { this.interval = 5; this.intervalType = "millisecond"; } else if (range / (1 * 10) <= noTicks) { this.interval = 10; this.intervalType = "millisecond"; } else if (range / (1 * 20) <= noTicks) { this.interval = 20; this.intervalType = "millisecond"; } else if (range / (1 * 50) <= noTicks) { this.interval = 50; this.intervalType = "millisecond"; } else if (range / (1 * 100) <= noTicks) { this.interval = 100; this.intervalType = "millisecond"; } else if (range / (1 * 200) <= noTicks) { this.interval = 200; this.intervalType = "millisecond"; } else if (range / (1 * 250) <= noTicks) { this.interval = 250; this.intervalType = "millisecond"; } else if (range / (1 * 300) <= noTicks) { this.interval = 300; this.intervalType = "millisecond"; } else if (range / (1 * 400) <= noTicks) { this.interval = 400; this.intervalType = "millisecond"; } else if (range / (1 * 500) <= noTicks) { this.interval = 500; this.intervalType = "millisecond"; } else if (range / (constants.secondDuration * 1) <= noTicks) { this.interval = 1; this.intervalType = "second"; } else if (range / (constants.secondDuration * 2) <= noTicks) { this.interval = 2; this.intervalType = "second"; } else if (range / (constants.secondDuration * 5) <= noTicks) { this.interval = 5; this.intervalType = "second"; } else if (range / (constants.secondDuration * 10) <= noTicks) { this.interval = 10; this.intervalType = "second"; } else if (range / (constants.secondDuration * 15) <= noTicks) { this.interval = 15; this.intervalType = "second"; } else if (range / (constants.secondDuration * 20) <= noTicks) { this.interval = 20; this.intervalType = "second"; } else if (range / (constants.secondDuration * 30) <= noTicks) { this.interval = 30; this.intervalType = "second"; } else if (range / (constants.minuteDuration * 1) <= noTicks) { this.interval = 1; this.intervalType = "minute"; } else if (range / (constants.minuteDuration * 2) <= noTicks) { this.interval = 2; this.intervalType = "minute"; } else if (range / (constants.minuteDuration * 5) <= noTicks) { this.interval = 5; this.intervalType = "minute"; } else if (range / (constants.minuteDuration * 10) <= noTicks) { this.interval = 10; this.intervalType = "minute"; } else if (range / (constants.minuteDuration * 15) <= noTicks) { this.interval = 15; this.intervalType = "minute"; } else if (range / (constants.minuteDuration * 20) <= noTicks) { this.interval = 20; this.intervalType = "minute"; } else if (range / (constants.minuteDuration * 30) <= noTicks) { this.interval = 30; this.intervalType = "minute"; } else if (range / (constants.hourDuration * 1) <= noTicks) { this.interval = 1; this.intervalType = "hour"; } else if (range / (constants.hourDuration * 2) <= noTicks) { this.interval = 2; this.intervalType = "hour"; } else if (range / (constants.hourDuration * 3) <= noTicks) { this.interval = 3; this.intervalType = "hour"; } else if (range / (constants.hourDuration * 6) <= noTicks) { this.interval = 6; this.intervalType = "hour"; } else if (range / (constants.dayDuration * 1) <= noTicks) { this.interval = 1; this.intervalType = "day"; } else if (range / (constants.dayDuration * 2) <= noTicks) { this.interval = 2; this.intervalType = "day"; } else if (range / (constants.dayDuration * 4) <= noTicks) { this.interval = 4; this.intervalType = "day"; } else if (range / (constants.weekDuration * 1) <= noTicks) { this.interval = 1; this.intervalType = "week"; } else if (range / (constants.weekDuration * 2) <= noTicks) { this.interval = 2; this.intervalType = "week"; } else if (range / (constants.weekDuration * 3) <= noTicks) { this.interval = 3; this.intervalType = "week"; } else if (range / (constants.monthDuration * 1) <= noTicks) { this.interval = 1; this.intervalType = "month"; } else if (range / (constants.monthDuration * 2) <= noTicks) { this.interval = 2; this.intervalType = "month"; } else if (range / (constants.monthDuration * 3) <= noTicks) { this.interval = 3; this.intervalType = "month"; } else if (range / (constants.monthDuration * 6) <= noTicks) { this.interval = 6; this.intervalType = "month"; } else if (range / (constants.yearDuration * 1) <= noTicks) { this.interval = 1; this.intervalType = "year"; } else if (range / (constants.yearDuration * 2) <= noTicks) { this.interval = 2; this.intervalType = "year"; } else if (range / (constants.yearDuration * 4) <= noTicks) { this.interval = 4; this.intervalType = "year"; } else { this.interval = Math.floor(Axis.getNiceNumber(range / (noTicks - 1), true) / constants.yearDuration); this.intervalType = "year"; } } if (this.viewportMinimum === null || isNaN(this.viewportMinimum)) this.viewportMinimum = min - minDiff / 2; if (this.viewportMaximum === null || isNaN(this.viewportMaximum)) this.viewportMaximum = max + minDiff / 2; if (!this.valueFormatString) { if (isLessThanTwoDataPoints) { this.valueFormatString = "MMM DD YYYY HH:mm"; } else if (this.intervalType === "year") { this.valueFormatString = "YYYY"; } else if (this.intervalType === "month") { this.valueFormatString = "MMM YYYY"; } else if (this.intervalType === "week") { this.valueFormatString = "MMM DD YYYY"; } else if (this.intervalType === "day") { this.valueFormatString = "MMM DD YYYY"; } else if (this.intervalType === "hour") { this.valueFormatString = "hh:mm TT"; } else if (this.intervalType === "minute") { this.valueFormatString = "hh:mm TT"; } else if (this.intervalType === "second") { this.valueFormatString = "hh:mm:ss TT"; } else if (this.intervalType === "millisecond") { this.valueFormatString = "fff'ms'"; } } } else { this.intervalType = "number"; range = Axis.getNiceNumber(range, false); if (this._options && this._options.interval) this.interval = this._options.interval; else { this.interval = Axis.getNiceNumber(range / (noTicks - 1), true); } if (this.viewportMinimum === null || isNaN(this.viewportMinimum)) { if (this.type === "axisX") this.viewportMinimum = min - minDiff / 2; else this.viewportMinimum = Math.floor(min / this.interval) * this.interval; } if (this.viewportMaximum === null || isNaN(this.viewportMaximum)) { if (this.type === "axisX") this.viewportMaximum = max + minDiff / 2; else this.viewportMaximum = Math.ceil(max / this.interval) * this.interval; } if (this.viewportMaximum === 0 && this.viewportMinimum === 0) { if (this._options.viewportMinimum === 0) { this.viewportMaximum += 10; } else if (this._options.viewportMaximum === 0) { this.viewportMinimum -= 10; } if (this._options && typeof (this._options.interval) === "undefined") { this.interval = Axis.getNiceNumber((this.viewportMaximum - this.viewportMinimum) / (noTicks - 1), true); } } } //Calculate minimum and maximum if not provided by the user if (this.minimum === null || this.maximum === null) { if (this.type === "axisX") { min = (this.minimum !== null) ? this.minimum : this.dataInfo.min; max = (this.maximum !== null) ? this.maximum : this.dataInfo.max; if (max - min === 0) { rangePadding = typeof (this._options.interval) === "undefined" ? .4 : this._options.interval; max += rangePadding; min -= rangePadding; } if (this.dataInfo.minDiff !== Infinity) minDiff = this.dataInfo.minDiff; else if (max - min > 1) { minDiff = Math.abs(max - min) * .5; } else { minDiff = 1; } } else if (this.type === "axisY") { min = (this.minimum !== null) ? this.minimum : this.dataInfo.min; max = (this.maximum !== null) ? this.maximum : this.dataInfo.max; if (!isFinite(min) && !isFinite(max)) { max = typeof (this._options.interval) === "undefined" ? -Infinity : this._options.interval; min = 0; } else if (min === 0 && max === 0) {// When all dataPoints are zero max += 9; min = 0; } else if (max - min === 0) {// When there is only a single dataPoint or when all dataPoints have same Y Value rangePadding = Math.min(Math.abs(Math.abs(max) * .01), 5); max += rangePadding; min -= rangePadding; } else if (min > max) { rangePadding = Math.min(Math.abs(Math.abs(max - min) * .01), 5); if (max >= 0) min = max - rangePadding; else max = min + rangePadding; } else { rangePadding = Math.min(Math.abs(Math.abs(max - min) * .01), .05); if (max !== 0) max += rangePadding; if (min !== 0) min -= rangePadding; } if (this.dataInfo.minDiff !== Infinity) minDiff = this.dataInfo.minDiff; else if (max - min > 1) { minDiff = Math.abs(max - min) * .5; } else { minDiff = 1; } //Apply includeZero if (this.includeZero && (this.minimum === null || isNaN(this.minimum))) { if (min > 0) min = 0; } if (this.includeZero && (this.maximum === null || isNaN(this.maximum))) { if (max < 0) max = 0; } } range = max - min; if (this.type === "axisX" && this.chart.plotInfo.axisXValueType === "dateTime") { if (this.minimum === null || isNaN(this.minimum)) this.minimum = min - minDiff / 2; if (this.maximum === null || isNaN(this.maximum)) this.maximum = max + minDiff / 2; } else { this.intervalType = "number"; if (this.minimum === null) { if (this.type === "axisX") this.minimum = min - minDiff / 2; else this.minimum = Math.floor(min / this.interval) * this.interval; this.minimum = Math.min(this.minimum, this.sessionVariables.viewportMinimum === null || isNaN(this.sessionVariables.viewportMinimum) ? Infinity : this.sessionVariables.viewportMinimum); } if (this.maximum === null) { if (this.type === "axisX") this.maximum = max + minDiff / 2; else this.maximum = Math.ceil(max / this.interval) * this.interval; this.maximum = Math.max(this.maximum, this.sessionVariables.viewportMaximum === null || isNaN(this.sessionVariables.viewportMaximum) ? -Infinity : this.sessionVariables.viewportMaximum); } //var nfrac = Math.max(-Math.floor(Math.log(d)/Math.LN10), 0); //number of fractional digits to show if (this.maximum === 0 && this.minimum === 0) { if (this._options.minimum === 0) { this.maximum += 10; } else if (this._options.maximum === 0) { this.minimum -= 10; } } } } this.viewportMinimum = Math.max(this.viewportMinimum, this.minimum); this.viewportMaximum = Math.min(this.viewportMaximum, this.maximum); if (this.type === "axisX" && this.chart.plotInfo.axisXValueType === "dateTime") this.intervalStartPosition = this.getLabelStartPoint(new Date(this.viewportMinimum), this.intervalType, this.interval); else this.intervalStartPosition = Math.floor((this.viewportMinimum + (this.interval * .2)) / this.interval) * this.interval; //Set valueFormatString if (!this.valueFormatString) { this.valueFormatString = "#,##0.##"; range = Math.abs(this.viewportMaximum - this.viewportMinimum); if (range < 1) { var numberOfDecimals = Math.floor(Math.abs(Math.log(range) / Math.LN10)) + 2; if (isNaN(numberOfDecimals) || !isFinite(numberOfDecimals)) numberOfDecimals = 2; if (numberOfDecimals > 2) { for (var i = 0; i < numberOfDecimals - 2; i++) this.valueFormatString += "#"; } } } //if (isDebugMode && window.console) { // window.console.log(this.type + ": Min = " + this.viewportMinimum); // window.console.log(this.type + ": Max = " + this.viewportMaximum); // window.console.log(this.type + ": Interval = " + this.interval); //} } Axis.getNiceNumber = function (x, round) { var exp = Math.floor(Math.log(x) / Math.LN10); var f = x / Math.pow(10, exp); var nf; if (round) { if (f < 1.5) nf = 1; else if (f < 3) nf = 2; else if (f < 7) nf = 5; else nf = 10; } else { if (f <= 1) nf = 1; else if (f <= 2) nf = 2; else if (f <= 5) nf = 5; else nf = 10; } return Number((nf * Math.pow(10, exp)).toFixed(20)); } Axis.prototype.getLabelStartPoint = function () { var intervalInMilliseconds = convertToNumber(this.interval, this.intervalType); var minimum = Math.floor((this.viewportMinimum) / intervalInMilliseconds) * intervalInMilliseconds; var dateTime = new Date(minimum); if (this.intervalType === "millisecond") { //millisecond = dateTime.getMilliSecond(); //millisecond = Math.floor((millisecond + this.interval) / this.interval) * this.interval; } else if (this.intervalType === "second") { if (dateTime.getMilliseconds() > 0) { dateTime.setSeconds(dateTime.getSeconds() + 1); dateTime.setMilliseconds(0); } } else if (this.intervalType === "minute") { if (dateTime.getSeconds() > 0 || dateTime.getMilliseconds() > 0) { dateTime.setMinutes(dateTime.getMinutes() + 1); dateTime.setSeconds(0); dateTime.setMilliseconds(0); } } else if (this.intervalType === "hour") { if (dateTime.getMinutes() > 0 || dateTime.getSeconds() > 0 || dateTime.getMilliseconds() > 0) { dateTime.setHours(dateTime.getHours() + 1); dateTime.setMinutes(0); dateTime.setSeconds(0); dateTime.setMilliseconds(0); } } else if (this.intervalType === "day") { if (dateTime.getHours() > 0 || dateTime.getMinutes() > 0 || dateTime.getSeconds() > 0 || dateTime.getMilliseconds() > 0) { dateTime.setDate(dateTime.getDate() + 1); dateTime.setHours(0); dateTime.setMinutes(0); dateTime.setSeconds(0); dateTime.setMilliseconds(0); } } else if (this.intervalType === "week") { if (dateTime.getDay() > 0 || dateTime.getHours() > 0 || dateTime.getMinutes() > 0 || dateTime.getSeconds() > 0 || dateTime.getMilliseconds() > 0) { dateTime.setDate(dateTime.getDate() + (7 - dateTime.getDay())); dateTime.setHours(0); dateTime.setMinutes(0); dateTime.setSeconds(0); dateTime.setMilliseconds(0); } } else if (this.intervalType === "month") { if (dateTime.getDate() > 1 || dateTime.getHours() > 0 || dateTime.getMinutes() > 0 || dateTime.getSeconds() > 0 || dateTime.getMilliseconds() > 0) { dateTime.setMonth(dateTime.getMonth() + 1); dateTime.setDate(1); dateTime.setHours(0); dateTime.setMinutes(0); dateTime.setSeconds(0); dateTime.setMilliseconds(0); } } else if (this.intervalType === "year") { if (dateTime.getMonth() > 0 || dateTime.getDate() > 1 || dateTime.getHours() > 0 || dateTime.getMinutes() > 0 || dateTime.getSeconds() > 0 || dateTime.getMilliseconds() > 0) { dateTime.setFullYear(dateTime.getFullYear() + 1); dateTime.setMonth(0); dateTime.setDate(1); dateTime.setHours(0); dateTime.setMinutes(0); dateTime.setSeconds(0); dateTime.setMilliseconds(0); } } return dateTime; } //#endregion Axis //#region StripLine function StripLine(chart, options, theme, id, axis) { StripLine.base.constructor.call(this, "StripLine", options, theme, axis); this.id = id; this.chart = chart; this.ctx = this.chart.ctx; this.label = this.label; this._thicknessType = "pixel"; if (this.startValue !== null && this.endValue !== null) { this.value = ((this.startValue.getTime ? this.startValue.getTime() : this.startValue) + (this.endValue.getTime ? this.endValue.getTime() : this.endValue)) / 2; this.thickness = Math.max(this.endValue - this.startValue); this._thicknessType = "value"; } } extend(StripLine, CanvasJSObject); StripLine.prototype.render = function () { var xy = this.parent.getPixelCoordinatesOnAxis(this.value); var lineWidth = Math.abs(this._thicknessType === "pixel" ? this.thickness : this.parent.conversionParameters.pixelPerUnit * this.thickness); if (lineWidth > 0) { //var opacity = this.opacity === null ? ( this.showOnTop && this._thicknessType === "pixel" ? 1 : 1) : this.opacity; var opacity = this.opacity === null ? 1 : this.opacity; this.ctx.strokeStyle = this.color; this.ctx.beginPath(); var oldGlobalAlpha = this.ctx.globalAlpha; this.ctx.globalAlpha = opacity; var hexColor = intToHexColorString(this.id); var x1, x2, y1, y2; this.ctx.lineWidth = lineWidth; if (this.ctx.setLineDash) { this.ctx.setLineDash(getLineDashArray(this.lineDashType, lineWidth)); } if (this.parent._position === "bottom" || this.parent._position === "top") { var stripX = (this.ctx.lineWidth % 2 === 1) ? (xy.x << 0) + .5 : (xy.x << 0); x1 = x2 = stripX; y1 = this.chart.plotArea.y1; y2 = this.chart.plotArea.y2; } else if (this.parent._position === "left" || this.parent._position === "right") { var stripY = (this.ctx.lineWidth % 2 === 1) ? (xy.y << 0) + .5 : (xy.y << 0); y1 = y2 = stripY; x1 = this.chart.plotArea.x1; x2 = this.chart.plotArea.x2; } this.ctx.moveTo(x1, y1); this.ctx.lineTo(x2, y2); this.ctx.stroke(); this.ctx.globalAlpha = oldGlobalAlpha; } }; //#endregion StripLine //#region ToolTip function ToolTip(chart, options, theme) { ToolTip.base.constructor.call(this, "ToolTip", options, theme); this.chart = chart; this.canvas = chart.canvas; this.ctx = this.chart.ctx; this.currentSeriesIndex = -1; this.currentDataPointIndex = -1; this._timerId = 0; this._prevX = NaN; this._prevY = NaN; this._initialize(); } extend(ToolTip, CanvasJSObject); ToolTip.prototype._initialize = function () { if (this.enabled) { this.container = document.createElement("div"); this.container.setAttribute("class", "canvasjs-chart-tooltip"); this.container.style.position = "absolute"; this.container.style.height = "auto"; this.container.style.boxShadow = "1px 1px 2px 2px rgba(0,0,0,0.1)"; this.container.style.zIndex = "1000"; //this.container.style.pointerEvents = "none"; this.container.style.display = "none"; //this.container.style.whiteSpace = "no-wrap"; var toolTipHtml = "
Sample Tooltip
"; this.container.innerHTML = toolTipHtml; this.contentDiv = this.container.firstChild; this.container.style.borderRadius = this.contentDiv.style.borderRadius; this.chart._canvasJSContainer.appendChild(this.container); } } ToolTip.prototype.mouseMoveHandler = function (x, y) { if (!(this._lastUpdated && (new Date().getTime() - this._lastUpdated) < 40)) { this._lastUpdated = new Date().getTime(); this._updateToolTip(x, y); } } ToolTip.prototype._updateToolTip = function (mouseX, mouseY) { //return; if (this.chart.disableToolTip) // Disabled during animation, etc return; if (typeof (mouseX) === "undefined" || typeof (mouseY) === "undefined") { if (isNaN(this._prevX) || isNaN(this._prevY)) return; else { mouseX = this._prevX; mouseY = this._prevY; } } else { this._prevX = mouseX; this._prevY = mouseY; } var dataPoint = null; var dataSeries = null; var toolTipContent = ""; var entries = []; var toolTipRight; var toolTipBottom; var x = 0; if (this.shared && this.enabled && this.chart.plotInfo.axisPlacement !== "none") { // && this.chart.plotInfo.axisPlacement !== "none" if (this.chart.plotInfo.axisPlacement === "xySwapped") { x = (this.chart.axisX.viewportMaximum - this.chart.axisX.viewportMinimum) / this.chart.axisX.lineCoordinates.height * ((this.chart.axisX.lineCoordinates.y2 - mouseY)) + this.chart.axisX.viewportMinimum; } else { x = (this.chart.axisX.viewportMaximum - this.chart.axisX.viewportMinimum) / this.chart.axisX.lineCoordinates.width * (mouseX - this.chart.axisX.lineCoordinates.x1) + this.chart.axisX.viewportMinimum; } var nearbyEntries = []; for (var i = 0; i < this.chart.data.length; i++) { var entry = this.chart.data[i].getDataPointAtX(x, true); if (entry && entry.index >= 0) { entry.dataSeries = this.chart.data[i]; if (entry.dataPoint.y !== null) nearbyEntries.push(entry); } } if (nearbyEntries.length === 0) return; nearbyEntries.sort(function (entry1, entry2) { return entry1.distance - entry2.distance; }); var closest = nearbyEntries[0]; for (i = 0; i < nearbyEntries.length; i++) { if (nearbyEntries[i].dataPoint.x.valueOf() === closest.dataPoint.x.valueOf()) entries.push(nearbyEntries[i]); } nearbyEntries = null; } else { var dataPointInfo = this.chart.getDataPointAtXY(mouseX, mouseY, true); //dataPointInfo = null; if (dataPointInfo) { this.currentDataPointIndex = dataPointInfo.dataPointIndex; this.currentSeriesIndex = dataPointInfo.dataSeries.index; } else if (isCanvasSupported) { var id = getObjectId(mouseX, mouseY, this.chart._eventManager.ghostCtx); if (id > 0 && typeof this.chart._eventManager.objectMap[id] !== "undefined") {//DataPoint/DataSeries event eventObject = this.chart._eventManager.objectMap[id]; if (eventObject.objectType === "legendItem") return; //if (this.currentSeriesIndex === eventObject.dataSeriesIndex && this.currentDataPointIndex === eventObject.dataPointIndex) // return; //else { this.currentSeriesIndex = eventObject.dataSeriesIndex; this.currentDataPointIndex = eventObject.dataPointIndex >= 0 ? eventObject.dataPointIndex : -1; //} //window.console.log("id: " + id + "; hex: " + intToHexColorString(id)); } else this.currentDataPointIndex = -1; } else this.currentDataPointIndex = -1; if (this.currentSeriesIndex >= 0) { dataSeries = this.chart.data[this.currentSeriesIndex]; var entry = { }; if (this.currentDataPointIndex >= 0) { dataPoint = dataSeries.dataPoints[this.currentDataPointIndex]; entry.dataSeries = dataSeries; entry.dataPoint = dataPoint; entry.index = this.currentDataPointIndex; entry.distance = Math.abs(dataPoint.x - x); } else if (this.enabled && (dataSeries.type === "line" || dataSeries.type === "stepLine" || dataSeries.type === "spline" || dataSeries.type === "area" || dataSeries.type === "stepArea" || dataSeries.type === "splineArea" || dataSeries.type === "stackedArea" || dataSeries.type === "stackedArea100" || dataSeries.type === "rangeArea" || dataSeries.type === "rangeSplineArea" || dataSeries.type === "candlestick" || dataSeries.type === "ohlc")) { //var x = (this.chart.axisX.viewportMaximum - this.chart.axisX.viewportMinimum) / this.chart.axisX.lineCoordinates.width * (mouseX - this.chart.axisX.lineCoordinates.x1) + this.chart.axisX.viewportMinimum.valueOf(); var x = dataSeries.axisX.conversionParameters.minimum + (mouseX - dataSeries.axisX.conversionParameters.reference) / dataSeries.axisX.conversionParameters.pixelPerUnit; entry = dataSeries.getDataPointAtX(x, true); entry.dataSeries = dataSeries; this.currentDataPointIndex = entry.index; dataPoint = entry.dataPoint; } else { //this.hide(); return; } if (entry.dataPoint.y !== null) { if (entry.dataSeries.axisY) { if (entry.dataPoint.y.length > 0) { var unboundToViewport = 0; for (var i = 0; i < entry.dataPoint.y.length; i++) if (entry.dataPoint.y[i] < entry.dataSeries.axisY.viewportMinimum) unboundToViewport--; else if (entry.dataPoint.y[i] > entry.dataSeries.axisY.viewportMaximum) unboundToViewport++; if (unboundToViewport < entry.dataPoint.y.length && unboundToViewport > -entry.dataPoint.y.length) entries.push(entry); } else { if (entry.dataPoint.y >= entry.dataSeries.axisY.viewportMinimum && entry.dataPoint.y <= entry.dataSeries.axisY.viewportMaximum) entries.push(entry); } } else entries.push(entry); } } } if (entries.length > 0) { this.highlightObjects(entries); if (this.enabled) { var toolTipInnerHtml = ""; toolTipInnerHtml = this.getToolTipInnerHTML({ entries: entries }); if (toolTipInnerHtml !== null) { this.contentDiv.innerHTML = toolTipInnerHtml; this.contentDiv.innerHTML = toolTipInnerHtml; var previouslyHidden = false; if (this.container.style.display === "none") { previouslyHidden = true; this.container.style.display = "block"; } try { this.contentDiv.style.background = this.backgroundColor ? this.backgroundColor : isCanvasSupported ? "rgba(255,255,255,.9)" : "rgb(255,255,255)"; this.contentDiv.style.borderRightColor = this.contentDiv.style.borderLeftColor = this.contentDiv.style.borderColor = this.borderColor ? this.borderColor : entries[0].dataPoint.color ? entries[0].dataPoint.color : entries[0].dataSeries.color ? entries[0].dataSeries.color : entries[0].dataSeries._colorSet[entries[0].index % entries[0].dataSeries._colorSet.length]; this.contentDiv.style.borderWidth = (this.borderThickness || this.borderThickness === 0) ? this.borderThickness + "px" : 2 + "px"; this.contentDiv.style.borderRadius = (this.cornerRadius || this.cornerRadius === 0) ? this.cornerRadius + "px" : 5 + "px"; this.container.style.borderRadius = this.contentDiv.style.borderRadius; this.contentDiv.style.fontSize = (this.fontSize || this.fontSize === 0) ? this.fontSize + "px" : 14 + "px"; this.contentDiv.style.color = this.fontColor ? this.fontColor : "#000000"; this.contentDiv.style.fontFamily = this.fontFamily ? this.fontFamily : "Calibri, Arial, Georgia, serif;"; this.contentDiv.style.fontWeight = this.fontWeight ? this.fontWeight : "normal"; this.contentDiv.style.fontStyle = this.fontStyle ? this.fontStyle : isCanvasSupported ? "italic" : "normal"; } catch (e) { } if (entries[0].dataSeries.type === "pie" || entries[0].dataSeries.type === "doughnut" || entries[0].dataSeries.type === "funnel" || entries[0].dataSeries.type === "bar" || entries[0].dataSeries.type === "rangeBar" || entries[0].dataSeries.type === "stackedBar" || entries[0].dataSeries.type === "stackedBar100") { toolTipLeft = mouseX - 10 - this.container.clientWidth; } else { //toolTipLeft = (((this.chart.axisX.lineCoordinates.width / Math.abs(this.chart.axisX.viewportMaximum - this.chart.axisX.viewportMinimum)) * Math.abs(entries[0].dataPoint.x - this.chart.axisX.viewportMinimum)) + this.chart.axisX.lineCoordinates.x1 + .5) - this.container.clientWidth << 0; toolTipLeft = entries[0].dataSeries.axisX.conversionParameters.reference + entries[0].dataSeries.axisX.conversionParameters.pixelPerUnit * (entries[0].dataPoint.x - entries[0].dataSeries.axisX.conversionParameters.minimum) - this.container.clientWidth << 0; toolTipLeft -= 10; } if (toolTipLeft < 0) { toolTipLeft += this.container.clientWidth + 20; } if (toolTipLeft + this.container.clientWidth > this.chart._container.clientWidth) toolTipLeft = Math.max(0, this.chart._container.clientWidth - this.container.clientWidth); toolTipLeft += "px"; if (entries.length === 1 && !this.shared && (entries[0].dataSeries.type === "line" || entries[0].dataSeries.type === "stepLine" || entries[0].dataSeries.type === "spline" || entries[0].dataSeries.type === "area" || entries[0].dataSeries.type === "stepArea" || entries[0].dataSeries.type === "splineArea" || entries[0].dataSeries.type === "stackedArea" || entries[0].dataSeries.type === "stackedArea100")) { //toolTipBottom = (entries[0].dataSeries.axisY.lineCoordinates.y2 - entries[0].dataSeries.axisY.lineCoordinates.height / Math.abs(entries[0].dataSeries.axisY.maximum - entries[0].dataSeries.axisY.viewportMinimum) * Math.abs(entries[0].dataPoint.y - entries[0].dataSeries.axisY.viewportMinimum) + .5) << 0; toolTipBottom = entries[0].dataSeries.axisY.conversionParameters.reference + entries[0].dataSeries.axisY.conversionParameters.pixelPerUnit * (entries[0].dataPoint.y - entries[0].dataSeries.axisY.viewportMinimum) + .5 << 0; } else if (entries[0].dataSeries.type === "bar" || entries[0].dataSeries.type === "rangeBar" || entries[0].dataSeries.type === "stackedBar" || entries[0].dataSeries.type === "stackedBar100") { //toolTipBottom = (entries[0].dataSeries.axisX.lineCoordinates.y2 - entries[0].dataSeries.axisX.lineCoordinates.height / Math.abs(entries[0].dataSeries.axisX.maximum - entries[0].dataSeries.axisX.viewportMinimum) * Math.abs(entries[0].dataPoint.x - entries[0].dataSeries.axisX.viewportMinimum) + .5) << 0; toolTipBottom = entries[0].dataSeries.axisX.conversionParameters.reference + entries[0].dataSeries.axisX.conversionParameters.pixelPerUnit * (entries[0].dataPoint.x - entries[0].dataSeries.axisX.viewportMinimum) + .5 << 0; } else { toolTipBottom = mouseY; } toolTipBottom = (-toolTipBottom + 10); if (toolTipBottom + this.container.clientHeight + 5 > 0) { toolTipBottom -= toolTipBottom + this.container.clientHeight + 5 - 0 } toolTipBottom += "px"; //this.container.style.right = toolTipRight; this.container.style.left = toolTipLeft; this.container.style.bottom = toolTipBottom; if (!this.animationEnabled || previouslyHidden) { this.disableAnimation(); } else this.enableAnimation(); } else { this.hide(false); } } //if (isDebugMode) // console.log("searchX: " + x + " x: " + searchResult.dataPoint.x + "; y: " + searchResult.dataPoint.y + "; distance: " + searchResult.distance + "; steps: " + steps); } } ToolTip.prototype.highlightObjects = function (entries) { //if (!this.enabled) // return; //this.chart.overlaidCanvasCtx.clearRect(0, 0, this.chart.overlaidCanvas.width, this.chart.overlaidCanvas.height); var overlaidCanvasCtx = this.chart.overlaidCanvasCtx; this.chart.resetOverlayedCanvas(); overlaidCanvasCtx.clearRect(0,0,this.chart.width, this.chart.height); overlaidCanvasCtx.save(); var plotArea = this.chart.plotArea; var offset = 0; overlaidCanvasCtx.rect(plotArea.x1, plotArea.y1, plotArea.x2 - plotArea.x1, plotArea.y2 - plotArea.y1); overlaidCanvasCtx.clip(); for (var i = 0; i < entries.length; i++) { var entry = entries[i]; var eventObject = this.chart._eventManager.objectMap[entry.dataSeries.dataPointIds[entry.index]]; if (!eventObject || !eventObject.objectType || eventObject.objectType !== "dataPoint") continue; var dataSeries = this.chart.data[eventObject.dataSeriesIndex]; var dataPoint = dataSeries.dataPoints[eventObject.dataPointIndex]; var index = eventObject.dataPointIndex; if (dataPoint.highlightEnabled !== false && (dataSeries.highlightEnabled === true || dataPoint.highlightEnabled === true)) { if (dataSeries.type === "line" || dataSeries.type === "stepLine" || dataSeries.type === "spline" || dataSeries.type === "scatter" || dataSeries.type === "area" || dataSeries.type === "stepArea" || dataSeries.type === "splineArea" || dataSeries.type === "stackedArea" || dataSeries.type === "stackedArea100" || dataSeries.type === "rangeArea" || dataSeries.type === "rangeSplineArea") { var markerProps = dataSeries.getMarkerProperties(index, eventObject.x1, eventObject.y1, this.chart.overlaidCanvasCtx); markerProps.size = Math.max(markerProps.size * 1.5 << 0, 10); markerProps.borderColor = markerProps.borderColor || "#FFFFFF"; markerProps.borderThickness = markerProps.borderThickness || Math.ceil(markerProps.size * .1); //overlaidCanvasCtx.globalAlpha = .8; RenderHelper.drawMarkers([markerProps]); //overlaidCanvasCtx.globalAlpha = .8; if (typeof (eventObject.y2) !== "undefined") { var markerProps = dataSeries.getMarkerProperties(index, eventObject.x1, eventObject.y2, this.chart.overlaidCanvasCtx); markerProps.size = Math.max(markerProps.size * 1.5 << 0, 10); markerProps.borderColor = markerProps.borderColor || "#FFFFFF"; markerProps.borderThickness = markerProps.borderThickness || Math.ceil(markerProps.size * .1); //overlaidCanvasCtx.globalAlpha = .8; RenderHelper.drawMarkers([markerProps]); //overlaidCanvasCtx.globalAlpha = .8; } } else if (dataSeries.type === "bubble") { var markerProps = dataSeries.getMarkerProperties(index, eventObject.x1, eventObject.y1, this.chart.overlaidCanvasCtx); markerProps.size = eventObject.size; markerProps.color = "white"; markerProps.borderColor = "white"; //markerProps.borderThickness = 2; overlaidCanvasCtx.globalAlpha = .3; RenderHelper.drawMarkers([markerProps]); overlaidCanvasCtx.globalAlpha = 1; } else if (dataSeries.type === "column" || dataSeries.type === "stackedColumn" || dataSeries.type === "stackedColumn100" || dataSeries.type === "bar" || dataSeries.type === "rangeBar" || dataSeries.type === "stackedBar" || dataSeries.type === "stackedBar100" || dataSeries.type === "rangeColumn") { drawRect(overlaidCanvasCtx, eventObject.x1, eventObject.y1, eventObject.x2, eventObject.y2, "white", 0, null, false, false, false, false, .3); } else if (dataSeries.type === "pie" || dataSeries.type === "doughnut") { drawSegment(overlaidCanvasCtx, eventObject.center, eventObject.radius, "white", dataSeries.type, eventObject.startAngle, eventObject.endAngle, .3, eventObject.percentInnerRadius); } else if (dataSeries.type === "candlestick") { overlaidCanvasCtx.globalAlpha = 1; overlaidCanvasCtx.strokeStyle = eventObject.color; overlaidCanvasCtx.lineWidth = eventObject.borderThickness * 2; offset = (overlaidCanvasCtx.lineWidth) % 2 === 0 ? 0 : .5; overlaidCanvasCtx.beginPath(); overlaidCanvasCtx.moveTo(eventObject.x3 - offset, eventObject.y2); overlaidCanvasCtx.lineTo(eventObject.x3 - offset, Math.min(eventObject.y1, eventObject.y4)); overlaidCanvasCtx.stroke(); overlaidCanvasCtx.beginPath(); overlaidCanvasCtx.moveTo(eventObject.x3 - offset, Math.max(eventObject.y1, eventObject.y4)); overlaidCanvasCtx.lineTo(eventObject.x3 - offset, eventObject.y3); overlaidCanvasCtx.stroke(); drawRect(overlaidCanvasCtx, eventObject.x1, Math.min(eventObject.y1, eventObject.y4), eventObject.x2, Math.max(eventObject.y1, eventObject.y4), "transparent", eventObject.borderThickness * 2, eventObject.color, false, false, false, false); overlaidCanvasCtx.globalAlpha = 1; } else if (dataSeries.type === "ohlc") { overlaidCanvasCtx.globalAlpha = 1; overlaidCanvasCtx.strokeStyle = eventObject.color; overlaidCanvasCtx.lineWidth = eventObject.borderThickness * 2; offset = (overlaidCanvasCtx.lineWidth) % 2 === 0 ? 0 : .5; overlaidCanvasCtx.beginPath(); overlaidCanvasCtx.moveTo(eventObject.x3 - offset, eventObject.y2); overlaidCanvasCtx.lineTo(eventObject.x3 - offset, eventObject.y3); overlaidCanvasCtx.stroke(); overlaidCanvasCtx.beginPath(); overlaidCanvasCtx.moveTo(eventObject.x3, eventObject.y1); overlaidCanvasCtx.lineTo(eventObject.x1, eventObject.y1); overlaidCanvasCtx.stroke(); overlaidCanvasCtx.beginPath(); overlaidCanvasCtx.moveTo(eventObject.x3, eventObject.y4); overlaidCanvasCtx.lineTo(eventObject.x2, eventObject.y4); overlaidCanvasCtx.stroke(); overlaidCanvasCtx.globalAlpha = 1; } } } overlaidCanvasCtx.restore(); overlaidCanvasCtx.globalAlpha = 1; overlaidCanvasCtx.beginPath(); return; } ToolTip.prototype.getToolTipInnerHTML = function (e) { var entries = e.entries; var toolTipInnerHtml = null; var dataSeries = null; var dataPoint = null; var index = 0; var color = null; var toolTipContent = ""; var isToolTipDefinedInData = true; for (var i = 0; i < entries.length; i++) { if (entries[i].dataSeries.toolTipContent || entries[i].dataPoint.toolTipContent) { isToolTipDefinedInData = false; break; } } if (isToolTipDefinedInData && ((this.content && typeof (this.content) === "function") || this.contentFormatter)) { var param = { chart: this.chart, toolTip: this._options, entries: entries }; toolTipInnerHtml = this.contentFormatter ? this.contentFormatter(param) : this.content(param); } else { if (this.shared && this.chart.plotInfo.axisPlacement !== "none") { var toolTipInnerHtmlPrefix = ""; for (var i = 0; i < entries.length; i++) { dataSeries = entries[i].dataSeries; dataPoint = entries[i].dataPoint; index = entries[i].index; toolTipContent = ""; if (i === 0 && isToolTipDefinedInData && !this.content) { toolTipInnerHtmlPrefix += typeof (this.chart.axisX.labels[dataPoint.x]) !== "undefined" ? this.chart.axisX.labels[dataPoint.x] : "{x}"; toolTipInnerHtmlPrefix += "
"; toolTipInnerHtmlPrefix = this.chart.replaceKeywordsWithValue(toolTipInnerHtmlPrefix, dataPoint, dataSeries, index); } //Allows disabling of toolTip for individual dataPoints/dataSeries if (dataPoint.toolTipContent === null || (typeof (dataPoint.toolTipContent) === "undefined" && dataSeries._options.toolTipContent === null)) continue; if (dataSeries.type === "line" || dataSeries.type === "stepLine" || dataSeries.type === "spline" || dataSeries.type === "area" || dataSeries.type === "stepArea" || dataSeries.type === "splineArea" || dataSeries.type === "column" || dataSeries.type === "bar" || dataSeries.type === "scatter" || dataSeries.type === "stackedColumn" || dataSeries.type === "stackedColumn100" || dataSeries.type === "stackedBar" || dataSeries.type === "stackedBar100" || dataSeries.type === "stackedArea" || dataSeries.type === "stackedArea100") { toolTipContent += dataPoint.toolTipContent ? dataPoint.toolTipContent : dataSeries.toolTipContent ? dataSeries.toolTipContent : this.content && typeof (this.content) !== "function" ? this.content : "{name}:  {y}"; } else if (dataSeries.type === "bubble") { toolTipContent += dataPoint.toolTipContent ? dataPoint.toolTipContent : dataSeries.toolTipContent ? dataSeries.toolTipContent : this.content && typeof (this.content) !== "function" ? this.content : "{name}:  {y},   {z}"; } else if (dataSeries.type === "pie" || dataSeries.type === "doughnut" || dataSeries.type === "funnel") { toolTipContent += dataPoint.toolTipContent ? dataPoint.toolTipContent : dataSeries.toolTipContent ? dataSeries.toolTipContent : this.content && typeof (this.content) !== "function" ? this.content : "  {y}"; } else if (dataSeries.type === "rangeColumn" || dataSeries.type === "rangeBar" || dataSeries.type === "rangeArea" || dataSeries.type === "rangeSplineArea") { toolTipContent += dataPoint.toolTipContent ? dataPoint.toolTipContent : dataSeries.toolTipContent ? dataSeries.toolTipContent : this.content && typeof (this.content) !== "function" ? this.content : "{name}:  {y[0]}, {y[1]}"; } else if (dataSeries.type === "candlestick" || dataSeries.type === "ohlc") { toolTipContent += dataPoint.toolTipContent ? dataPoint.toolTipContent : dataSeries.toolTipContent ? dataSeries.toolTipContent : this.content && typeof (this.content) !== "function" ? this.content : "{name}:" + "
Open:   {y[0]}" + "
High:    {y[1]}" + "
Low:   {y[2]}" + "
Close:   {y[3]}"; } if (toolTipInnerHtml === null) toolTipInnerHtml = ""; if (this.reversed === true) { toolTipInnerHtml = this.chart.replaceKeywordsWithValue(toolTipContent, dataPoint, dataSeries, index) + toolTipInnerHtml; if (i < entries.length - 1) toolTipInnerHtml = "
" + toolTipInnerHtml; } else { toolTipInnerHtml += this.chart.replaceKeywordsWithValue(toolTipContent, dataPoint, dataSeries, index); if (i < entries.length - 1) toolTipInnerHtml += "
"; } } if (toolTipInnerHtml !== null) toolTipInnerHtml = toolTipInnerHtmlPrefix + toolTipInnerHtml; } else { dataSeries = entries[0].dataSeries; dataPoint = entries[0].dataPoint; index = entries[0].index; //Allows disabling of toolTip for individual dataPoints/dataSeries if (dataPoint.toolTipContent === null || (typeof (dataPoint.toolTipContent) === "undefined" && dataSeries._options.toolTipContent === null)) return null; if (dataSeries.type === "line" || dataSeries.type === "stepLine" || dataSeries.type === "spline" || dataSeries.type === "area" || dataSeries.type === "stepArea" || dataSeries.type === "splineArea" || dataSeries.type === "column" || dataSeries.type === "bar" || dataSeries.type === "scatter" || dataSeries.type === "stackedColumn" || dataSeries.type === "stackedColumn100" || dataSeries.type === "stackedBar" || dataSeries.type === "stackedBar100" || dataSeries.type === "stackedArea" || dataSeries.type === "stackedArea100") { toolTipContent = dataPoint.toolTipContent ? dataPoint.toolTipContent : dataSeries.toolTipContent ? dataSeries.toolTipContent : this.content && typeof (this.content) !== "function" ? this.content : "" + (dataPoint.label ? "{label}" : "{x}") + " :  {y}"; } else if (dataSeries.type === "bubble") { toolTipContent = dataPoint.toolTipContent ? dataPoint.toolTipContent : dataSeries.toolTipContent ? dataSeries.toolTipContent : this.content && typeof (this.content) !== "function" ? this.content : "" + (dataPoint.label ? "{label}" : "{x}") + ":  {y},   {z}"; } else if (dataSeries.type === "pie" || dataSeries.type === "doughnut" || dataSeries.type === "funnel") { toolTipContent = dataPoint.toolTipContent ? dataPoint.toolTipContent : dataSeries.toolTipContent ? dataSeries.toolTipContent : this.content && typeof (this.content) !== "function" ? this.content : (dataPoint.name ? "{name}:  " : dataPoint.label ? "{label}:  " : "") + "{y}"; } else if (dataSeries.type === "rangeColumn" || dataSeries.type === "rangeBar" || dataSeries.type === "rangeArea" || dataSeries.type === "rangeSplineArea") { toolTipContent = dataPoint.toolTipContent ? dataPoint.toolTipContent : dataSeries.toolTipContent ? dataSeries.toolTipContent : this.content && typeof (this.content) !== "function" ? this.content : "" + (dataPoint.label ? "{label}" : "{x}") + " :  {y[0]},  {y[1]}"; } else if (dataSeries.type === "candlestick" || dataSeries.type === "ohlc") { toolTipContent = dataPoint.toolTipContent ? dataPoint.toolTipContent : dataSeries.toolTipContent ? dataSeries.toolTipContent : this.content && typeof (this.content) !== "function" ? this.content : "" + (dataPoint.label ? "{label}" : "{x}") + "" + "
Open:   {y[0]}" + "
High:    {y[1]}" + "
Low:     {y[2]}" + "
Close:   {y[3]}"; } if (toolTipInnerHtml === null) toolTipInnerHtml = ""; toolTipInnerHtml += this.chart.replaceKeywordsWithValue(toolTipContent, dataPoint, dataSeries, index); } } return toolTipInnerHtml; } ToolTip.prototype.enableAnimation = function () { if (this.container.style.WebkitTransition) return; this.container.style.WebkitTransition = "left .2s ease-out, bottom .2s ease-out"; this.container.style.MozTransition = "left .2s ease-out, bottom .2s ease-out"; this.container.style.MsTransition = "left .2s ease-out, bottom .2s ease-out"; this.container.style.transition = "left .2s ease-out, bottom .2s ease-out"; } ToolTip.prototype.disableAnimation = function () { if (!this.container.style.WebkitTransition) return; this.container.style.WebkitTransition = ""; this.container.style.MozTransition = ""; this.container.style.MsTransition = ""; this.container.style.transition = ""; } ToolTip.prototype.hide = function (resetOverlayedCanvas) { if (!this.enabled) return; resetOverlayedCanvas = typeof (resetOverlayedCanvas) === "undefined" ? true : resetOverlayedCanvas; this.container.style.display = "none"; this.currentSeriesIndex = -1; this._prevX = NaN; this._prevY = NaN; //this.chart.overlaidCanvasCtx.clearRect(0, 0, this.chart.overlaidCanvas.width, this.chart.overlaidCanvas.height); if (resetOverlayedCanvas) this.chart.resetOverlayedCanvas(); } Chart.prototype.getPercentAndTotal = function (ds, dp) { var dpX = null; var total = null; var percent = null; if (ds.type.indexOf("stacked") >= 0) { total = 0; dpX = dp.x.getTime ? dp.x.getTime() : dp.x; if (dpX in ds.plotUnit.yTotals) { total = ds.plotUnit.yTotals[dpX]; if (!isNaN(dp.y)) { if (total === 0) percent = 0; else percent = (dp.y / total) * 100; } else percent = 0; } } else if (ds.type === "pie" || ds.type === "doughnut") { total = 0; for (i = 0; i < ds.dataPoints.length; i++) { if (!isNaN(ds.dataPoints[i].y)) total += ds.dataPoints[i].y; } if (!isNaN(dp.y)) percent = (dp.y / total) * 100; else percent = 0; } return { percent: percent, total: total }; } Chart.prototype.replaceKeywordsWithValue = function (str, dp, ds, dpIndex, indexKeywordValue) { //var regex = /\{\s*[a-zA-Z]+\s*\}|"[^"]*"|'[^']*'/g; var regex = /\{.*?\}|"[^"]*"|'[^']*'/g; var chart = this; indexKeywordValue = typeof (indexKeywordValue) === "undefined" ? 0 : indexKeywordValue; if ((ds.type.indexOf("stacked") >= 0 || (ds.type === "pie" || ds.type === "doughnut")) && (str.indexOf("#percent") >= 0 || str.indexOf("#total") >= 0)) { var percent = "#percent"; var total = "#total"; var dpX = null; var percentAndTotal = this.getPercentAndTotal(ds, dp); total = isNaN(percentAndTotal.total) ? total : percentAndTotal.total; percent = isNaN(percentAndTotal.percent) ? percent : percentAndTotal.percent; do { var percentFormatString = ""; if (ds.percentFormatString) percentFormatString = ds.percentFormatString; else { percentFormatString = "#,##0."; var numberOfDecimals = Math.max(Math.ceil(Math.log(1 / Math.abs(percent)) / Math.LN10), 2); if (isNaN(numberOfDecimals) || !isFinite(numberOfDecimals)) numberOfDecimals = 2; for (var n = 0; n < numberOfDecimals; n++) { percentFormatString += "#"; } } str = str.replace("#percent", numberFormat(percent, percentFormatString, chart._cultureInfo)); str = str.replace("#total", numberFormat(total, ds.yValueFormatString ? ds.yValueFormatString : "#,##0.########")); } while (str.indexOf("#percent") >= 0 || str.indexOf("#total") >= 0); } var fcn = function ($0) { if (($0[0] === "\"" && $0[$0.length - 1] === "\"") || ($0[0] === "\'" && $0[$0.length - 1] === "\'")) return $0.slice(1, $0.length - 1); var key = trimString($0.slice(1, $0.length - 1)); key = key.replace("#index", indexKeywordValue); var index = null; try { var match = key.match(/(.*?)\s*\[\s*(.*?)\s*\]/); if (match && match.length > 0) { index = trimString(match[2]); key = trimString(match[1]); } } catch (e) { }; var obj = null; if (key === "color") { return dp.color ? dp.color : ds.color ? ds.color : ds._colorSet[dpIndex % ds._colorSet.length]; } if (dp.hasOwnProperty(key)) obj = dp; else if (ds.hasOwnProperty(key)) obj = ds; else return ""; var value = obj[key]; if (index !== null) value = value[index]; if (key === "x") { if (chart.axisX && chart.plotInfo.axisXValueType === "dateTime") return dateFormat(value, dp.xValueFormatString ? dp.xValueFormatString : ds.xValueFormatString ? ds.xValueFormatString : chart.axisX && chart.axisX.valueFormatString ? chart.axisX.valueFormatString : "DD MMM YY", chart._cultureInfo); else return numberFormat(value, dp.xValueFormatString ? dp.xValueFormatString : ds.xValueFormatString ? ds.xValueFormatString : "#,##0.########", chart._cultureInfo); } else if (key === "y") return numberFormat(value, dp.yValueFormatString ? dp.yValueFormatString : ds.yValueFormatString ? ds.yValueFormatString : "#,##0.########", chart._cultureInfo); else if (key === "z") return numberFormat(value, dp.zValueFormatString ? dp.zValueFormatString : ds.zValueFormatString ? ds.zValueFormatString : "#,##0.########", chart._cultureInfo); else return value; } return str.replace(regex, fcn); } //#endregion ToolTip //#region Event Manager function EventManager(chart) { this.chart = chart; this.lastObjectId = 0; var _this = this; this.objectMap = []; this.rectangularRegionEventSubscriptions = []; this.previousDataPointEventObject = null; //this.previousDataSeriesEventObject = null; this.ghostCanvas = createCanvas(this.chart.width, this.chart.height); //this.ghostCanvas.width = this.chart.width; //this.ghostCanvas.height = this.chart.height; this.ghostCtx = this.ghostCanvas.getContext("2d"); var eventHandler = function (ev) { _this.mouseEventHandler.call(_this, ev); }; this.mouseoveredObjectMaps = []; //this.chart.canvas.addEventListener("mouseover", eventHandler); //this.chart.canvas.addEventListener("mousemove", eventHandler); //this.chart.canvas.addEventListener("mouseout", eventHandler); //this.chart.canvas.addEventListener("click", eventHandler); } EventManager.prototype.reset = function () { this.lastObjectId = 0; this.objectMap = []; this.rectangularRegionEventSubscriptions = []; this.previousDataPointEventObject = null; this.eventObjects = []; //this.ghostCanvas.width = this.chart.width; //this.ghostCanvas.height = this.chart.height; if (isCanvasSupported) { this.ghostCtx.clearRect(0, 0, this.chart.width, this.chart.height); this.ghostCtx.beginPath(); } } EventManager.prototype.getNewObjectTrackingId = function () { return ++this.lastObjectId; } EventManager.prototype.mouseEventHandler = function (ev) { if (ev.type !== "mousemove" && ev.type !== "click") return; var eventObjectMaps = []; var xy = getMouseCoordinates(ev); var id = null; //var dataPointInfo = this.chart.getDataPointAtXY(xy.x, xy.y, false); //if (dataPointInfo) { // id = dataPointInfo.dataSeries.dataPointIds[dataPointInfo.dataPointIndex]; //} else if (isCanvasSupported) {//IE9+ // id = getObjectId(xy.x, xy.y, this.ghostCtx); //} id = this.chart.getObjectAtXY(xy.x, xy.y, false); if (id && typeof (this.objectMap[id]) !== "undefined") { var eventObjectMap = this.objectMap[id]; if (eventObjectMap.objectType === "dataPoint") { var dataSeries = this.chart.data[eventObjectMap.dataSeriesIndex]; var dataPoint = dataSeries.dataPoints[eventObjectMap.dataPointIndex]; var dataPointIndex = eventObjectMap.dataPointIndex; //Event Parameter should not contain reference to dataSeries directly. But to its options. eventObjectMap.eventParameter = { x: xy.x, y: xy.y, dataPoint: dataPoint, dataSeries: dataSeries._options, dataPointIndex: dataPointIndex, dataSeriesIndex: dataSeries.index, chart: this.chart._publicChartReference }; eventObjectMap.eventContext = { context: dataPoint, userContext: dataPoint, mouseover: "mouseover", mousemove: "mousemove", mouseout: "mouseout", click: "click" }; eventObjectMaps.push(eventObjectMap); //Add Dataseries too because mouse event on dataPoint also means there is an event on dataSeries. DataSeries is not present on ghost canvas eventObjectMap = this.objectMap[dataSeries.id]; //Event Parameter should not contain reference to dataSeries directly. But to its options. eventObjectMap.eventParameter = { x: xy.x, y: xy.y, dataPoint: dataPoint, dataSeries: dataSeries._options, dataPointIndex: dataPointIndex, dataSeriesIndex: dataSeries.index, chart: this.chart._publicChartReference }; eventObjectMap.eventContext = { context: dataSeries, userContext: dataSeries._options, mouseover: "mouseover", mousemove: "mousemove", mouseout: "mouseout", click: "click" }; eventObjectMaps.push(this.objectMap[dataSeries.id]); } //else if (eventObjectMap.objectType === "stripLine") { // //Event Parameter should not contain reference to stripLine directly. But to its options. // eventObjectMap.eventParameter = { x: xy.x, y: xy.y, stripLine: eventObjectMap.stripLine._options, axis: eventObjectMap.axis._options, stripLineIndex: eventObjectMap.stripLineIndex }; // eventObjectMap.eventContext = { context: eventObjectMap.stripLine, userContext: eventObjectMap.stripLine._options, mouseover: "mouseover", mousemove: "mousemove", mouseout: "mouseout", click: "click" }; // eventObjectMaps.push(eventObjectMap); //} else if (eventObjectMap.objectType === "legendItem") { var dataSeries = this.chart.data[eventObjectMap.dataSeriesIndex]; var dataPoint = eventObjectMap.dataPointIndex !== null ? dataSeries.dataPoints[eventObjectMap.dataPointIndex] : null; //Event Parameter should not contain reference to DataSeries directly. But to its options. eventObjectMap.eventParameter = { x: xy.x, y: xy.y, dataSeries: dataSeries._options, dataPoint: dataPoint, dataPointIndex: eventObjectMap.dataPointIndex, dataSeriesIndex: eventObjectMap.dataSeriesIndex, chart: this.chart._publicChartReference }; eventObjectMap.eventContext = { context: this.chart.legend, userContext: this.chart.legend._options, mouseover: "itemmouseover", mousemove: "itemmousemove", mouseout: "itemmouseout", click: "itemclick" }; eventObjectMaps.push(eventObjectMap); } } //Fire mouseout if existing mouseovered objects are not present in the objectmap. var mouseOutObjectMapsExcluded = []; for (var i = 0; i < this.mouseoveredObjectMaps.length; i++) { var mouseOut = true; for (var j = 0; j < eventObjectMaps.length; j++) { if (eventObjectMaps[j].id === this.mouseoveredObjectMaps[i].id) { mouseOut = false; break; } } if (mouseOut) { this.fireEvent(this.mouseoveredObjectMaps[i], "mouseout", ev); } else { mouseOutObjectMapsExcluded.push(this.mouseoveredObjectMaps[i]); } } this.mouseoveredObjectMaps = mouseOutObjectMapsExcluded; //Process new eventObectMaps //If they already don't exist, add them and fire mouseover //If ev.type is mousemove, then just fire mousemove //If ev.type is click, then fire two events - click followed by mousemove for (var i = 0; i < eventObjectMaps.length; i++) { var existing = false; for (var j = 0; j < this.mouseoveredObjectMaps.length; j++) { if (eventObjectMaps[i].id === this.mouseoveredObjectMaps[j].id) { existing = true; break; } } if (!existing) { this.fireEvent(eventObjectMaps[i], "mouseover", ev); this.mouseoveredObjectMaps.push(eventObjectMaps[i]); } if (ev.type === "click") { this.fireEvent(eventObjectMaps[i], "click", ev); } else if (ev.type === "mousemove") { this.fireEvent(eventObjectMaps[i], "mousemove", ev); } } } EventManager.prototype.fireEvent = function (eventObjectMap, eventType, ev) { if (!eventObjectMap || !eventType) return; var eventParameter = eventObjectMap.eventParameter; var eventContext = eventObjectMap.eventContext; //var context = eventObjectMap.eventContext.context; var userContext = eventObjectMap.eventContext.userContext if (userContext && eventContext && userContext[eventContext[eventType]]) userContext[eventContext[eventType]].call(userContext, eventParameter); if (eventType !== "mouseout") { if (userContext.cursor && userContext.cursor !== ev.target.style.cursor) { ev.target.style.cursor = userContext.cursor; } } else { ev.target.style.cursor = this.chart._defaultCursor; delete eventObjectMap.eventParameter; // reference no longer required. delete eventObjectMap.eventContext; // reference no longer required. } //This is just a quick fix. Need to find a better way of calling internal event handlers. if (eventType === "click" && eventObjectMap.objectType === "dataPoint" && this.chart.pieDoughnutClickHandler) { this.chart.pieDoughnutClickHandler.call(this.chart.data[eventObjectMap.dataSeriesIndex], eventParameter); } } //#endregion Event Manager //#region Class CultureInfo function CultureInfo(culture) { var cultureInfo; if (culture && cultures[culture]) cultureInfo = cultures[culture]; CultureInfo.base.constructor.call(this, "CultureInfo", cultureInfo); } extend(CultureInfo, CanvasJSObject); //#endregion Class CultureInfo //#region Animator function Animator(chart) { this.chart = chart; this.ctx = this.chart.plotArea.ctx; this.animations = []; this.animationRequestId = null; } //Animator.prototype.animate = function (duration, base, dest, source, animationCallback, onComplete) { Animator.prototype.animate = function (startDelay, duration, animationCallback, onComplete, easingFunction) { var _this = this; this.chart.isAnimating = true; easingFunction = easingFunction || AnimationHelper.easing.linear; if (animationCallback) { this.animations.push({ startTime: (new Date()).getTime() + (startDelay ? startDelay : 0), duration: duration, animationCallback: animationCallback, onComplete: onComplete }); } var remainingAnimations = []; while (this.animations.length > 0) { var animation = this.animations.shift(); var now = (new Date()).getTime(); var fractionComplete = 0; //var fractionComplete = Math.min(((new Date()).getTime() - animation.startTime) / animation.duration, 1); if (animation.startTime <= now) { fractionComplete = easingFunction(Math.min((now - animation.startTime), animation.duration), 0, 1, animation.duration); //var fractionComplete = AnimationHelper.easing.easeOutQuad(Math.min(((new Date()).getTime() - animation.startTime), animation.duration), 0, 1, animation.duration); fractionComplete = Math.min(fractionComplete, 1); if (isNaN(fractionComplete) || !isFinite(fractionComplete)) fractionComplete = 1; } if (fractionComplete < 1) { remainingAnimations.push(animation); } animation.animationCallback(fractionComplete); if (fractionComplete >= 1 && animation.onComplete) animation.onComplete(); } this.animations = remainingAnimations; if (this.animations.length > 0) { this.animationRequestId = this.chart.requestAnimFrame.call(window, function () { _this.animate.call(_this); }); } else { this.chart.isAnimating = false; } } Animator.prototype.cancelAllAnimations = function () { this.animations = []; if (this.animationRequestId) { this.chart.cancelRequestAnimFrame.call(window, this.animationRequestId); } this.animationRequestId = null; this.chart.isAnimating = false; } var AnimationHelper = { yScaleAnimation: function (fractionComplete, animationInfo) { if (fractionComplete === 0) return; var ctx = animationInfo.dest; var sourceCanvas = animationInfo.source.canvas; var base = animationInfo.animationBase; var offsetY = (base - base * fractionComplete); ctx.drawImage(sourceCanvas, 0, 0, sourceCanvas.width, sourceCanvas.height, 0, offsetY, ctx.canvas.width / devicePixelBackingStoreRatio, fractionComplete * ctx.canvas.height / devicePixelBackingStoreRatio); }, xScaleAnimation: function (fractionComplete, animationInfo) { if (fractionComplete === 0) return; var ctx = animationInfo.dest; var sourceCanvas = animationInfo.source.canvas; var base = animationInfo.animationBase; var offsetX = (base - base * fractionComplete); ctx.drawImage(sourceCanvas, 0, 0, sourceCanvas.width, sourceCanvas.height, offsetX, 0, fractionComplete * ctx.canvas.width / devicePixelBackingStoreRatio, ctx.canvas.height / devicePixelBackingStoreRatio); }, xClipAnimation: function (fractionComplete, animationInfo) { if (fractionComplete === 0) return; var ctx = animationInfo.dest; var sourceCanvas = animationInfo.source.canvas; ctx.save(); if (fractionComplete > 0) ctx.drawImage(sourceCanvas, 0, 0, sourceCanvas.width * fractionComplete, sourceCanvas.height, 0, 0, sourceCanvas.width * fractionComplete / devicePixelBackingStoreRatio, sourceCanvas.height / devicePixelBackingStoreRatio); ctx.restore(); }, fadeInAnimation: function (fractionComplete, animationInfo) { if (fractionComplete === 0) return; var ctx = animationInfo.dest; var sourceCanvas = animationInfo.source.canvas; ctx.save(); ctx.globalAlpha = fractionComplete; ctx.drawImage(sourceCanvas, 0, 0, sourceCanvas.width, sourceCanvas.height, 0, 0, ctx.canvas.width / devicePixelBackingStoreRatio, ctx.canvas.height / devicePixelBackingStoreRatio); ctx.restore(); }, easing: { linear: function (t, b, c, d) { return c * t / d + b; }, easeOutQuad: function (t, b, c, d) { return -c * (t /= d) * (t - 2) + b; }, easeOutQuart: function (t, b, c, d) { return -c * ((t = t / d - 1) * t * t * t - 1) + b; }, easeInQuad: function (t, b, c, d) { return c * (t /= d) * t + b; }, easeInQuart: function (t, b, c, d) { return c * (t /= d) * t * t * t + b; } } } //#endregion Animator //#region Render Helper var RenderHelper = { drawMarker: function (x, y, ctx, markerType, markerSize, markerColor, markerBorderColor, markerBorderThickness) { if (!ctx) return; var alpha = 1; ctx.fillStyle = markerColor ? markerColor : "#000000"; ctx.strokeStyle = markerBorderColor ? markerBorderColor : "#000000"; ctx.lineWidth = markerBorderThickness ? markerBorderThickness : 0; if (markerType === "circle") { ctx.moveTo(x, y); ctx.beginPath(); //return; ctx.arc(x, y, markerSize / 2, 0, Math.PI * 2, false); if (markerColor) ctx.fill(); if (markerBorderThickness) { if (!markerBorderColor) { alpha = ctx.globalAlpha; ctx.globalAlpha = .15; ctx.strokeStyle = "black"; ctx.stroke(); ctx.globalAlpha = alpha; } else ctx.stroke(); } } else if (markerType === "square") { //ctx.moveTo(x - markerSize / 2, y - markerSize / 2); ctx.beginPath(); ctx.rect(x - markerSize / 2, y - markerSize / 2, markerSize, markerSize); if (markerColor) ctx.fill(); if (markerBorderThickness) { if (!markerBorderColor) { alpha = ctx.globalAlpha; ctx.globalAlpha = .15; ctx.strokeStyle = "black"; ctx.stroke(); ctx.globalAlpha = alpha; } else ctx.stroke(); } } else if (markerType === "triangle") { ctx.beginPath(); ctx.moveTo(x - markerSize / 2, y + markerSize / 2); ctx.lineTo(x + markerSize / 2, y + markerSize / 2); ctx.lineTo(x, y - markerSize / 2); ctx.closePath(); if (markerColor) ctx.fill(); if (markerBorderThickness) { if (!markerBorderColor) { alpha = ctx.globalAlpha; ctx.globalAlpha = .15; ctx.strokeStyle = "black"; ctx.stroke(); ctx.globalAlpha = alpha; } else ctx.stroke(); } ctx.beginPath(); } else if (markerType === "cross") { ctx.strokeStyle = markerColor; markerBorderThickness = markerSize / 4; ctx.lineWidth = markerBorderThickness; ctx.beginPath(); ctx.moveTo(x - markerSize / 2, y - markerSize / 2); ctx.lineTo(x + markerSize / 2, y + markerSize / 2); ctx.stroke(); ctx.moveTo(x + markerSize / 2, y - markerSize / 2); ctx.lineTo(x - markerSize / 2, y + markerSize / 2); ctx.stroke(); } }, drawMarkers: function (markers) { for (var i = 0; i < markers.length; i++) { var marker = markers[i]; RenderHelper.drawMarker(marker.x, marker.y, marker.ctx, marker.type, marker.size, marker.color, marker.borderColor, marker.borderThickness); } } //, //draw1pxLine: function (x1, y1, x2, y2, color, ctx) { // ctx.beginPath(); // ctx.drawRect(x1, y1, x2 - x1, y2 - y1); // ctx.stroke(); //} } //#endregion Render Helper //#endregion Class Definitions //#region Public API var CanvasJS = { Chart: function (containerId, options) { var _chart = new Chart(containerId, options, this); this.render = function () { _chart.render(this.options) }; //console.log(_chart); this.options = _chart._options; }, addColorSet: function (name, colorSet) { colorSets[name] = colorSet; }, addCultureInfo: function (name, cultureInfo) { cultures[name] = cultureInfo; }, formatNumber: function (number, formatString, culture) { culture = culture || "en"; formatString = formatString || "#,##0.##"; if (!cultures[culture]) throw "Unknown Culture Name"; else { return numberFormat(number, formatString, new CultureInfo(culture)); } }, formatDate: function (date, formatString, culture) { culture = culture || "en"; formatString = formatString || "DD MMM YYYY"; if (!cultures[culture]) throw "Unknown Culture Name"; else { return dateFormat(date, formatString, new CultureInfo(culture)); } } } CanvasJS.Chart.version = "v1.8.0 Beta 3"; window.CanvasJS = CanvasJS; //#endregion Public API })();