diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..bfe4e38 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,150 @@ +Version 2.3.1 + +1. New Features + + * N/A + +2. Bug Fixes + + * Fixed IE canvas support check for IE10+ + +Version 2.3 + +1. New Features + + * Added pointStyle property to LineSeries and AreaSeries + * Added even_shading and odd_shading properties to LinearAxis + * Added onUserEndZoom event to Chart + * Vertical Axis Captions (check cross-browser) + * Added legend_position, legend_height, and legend_width to Chart + * Added setAxis method to Chart + * New Series: ErrorSeries + * New Series: AlarmSeries + * New Series: OverUnderSeries + * New Series: StepSeries + * New Series: DonutSeries + * New Axis: DateAxis + +2. Bug Fixes + + * Fixed issues with displaying charts in IE9 + + +Version 2.2 + +1. New Features + + * Added a public method to return an array of series in the chart + * Added support for context menu + * Added onBeforeDrawSeries and onAfterDrawSeries events + * Added support for BarSeries points with NULL for Y data + * Added support for LineSeries and ScatterSeries points with NULL for Y data + * Added support for individual grid line and tick styles via the OnNeedsTicks event + +2. Bug Fixes + + * Fixed issues with all series reloading data + * Fixed support for AnalogGaugeSeries to use x_axis_formatter to format it's ticks + * Fixed issues with charts not displaying in IE when inside a TABLE + * Fixed issues with the NumberFormatter not formatting text correctly + * Fixed issues with the LineSeries and AreaSeries not drawing correctly when zoomed in + * Fixed issues with multiple BarSeries not spacing themselves correctly when the base axis data is dates + * Fixed zooming issues with LogarithmicAxis + * Fixed zooming issues with LinearAxis + * Fixed issues in IE with hiding a chart with a container with a border + * Fixed issues with BarSeries ranges + * Fixed issues with removing BarSeries and PieSeries + * Fixed default hint string for PieSeries + * Fixed issues with point selection with BarSeries and FloatingBarSeries (using distance) + * Fixed issues with hint data for StackedBarSeries + * Fixed issues with VML sometimes not showing up in IE8 in STRICT mode + * Fixed issues with exporting SVG when no axes are being used + * Fixed issues with hint pointer z-index + * Fixed default hint texts for AreaSeries, ScatterSeries, PieSeries, and TrendSeries + * Fixed issues with unselecting point before hiding a series + * Fixed issue with stock series when open or close attributes were null + + +Version 2.1 + +1. New Features + + * Added treeLegendRoot property to StackedBarSeries + * Added left and right as valid property values for PieSeries.position and AnalogGaugeSeries.position + * Added defaultColors, useColorArray and onBarNeedsColor to StackedBarSeries + * Added the ability for StackedBarSeries to share a single color array among all owned series + * Added support for IE8 RC1 (and most previous betas) in all modes + * Added chart.setShowLegend method + * Performance increases in IE when drawing multiple series and/or series with large numbers of points + * Added support for gaps in LineSeries (use a blank string as Y) + * Added getBarSizeInPoints to BarSeries and descendant classes + +2. Bug Fixes + + * Corrected several IE related memory leaks + * Corrected memory leaks related to Ajax requests (affected all browsers) + * Corrected memory leaks when removing charts from a page repeatedly without refreshing + * Corrected parameter name for CSVStringDataHandler.setCSV + * Fixed AreaSeries draw when series contains more than 5000 points in IE + * Fixed incorrect parsing of numeric Y values in Stock Series + * Updated chart.onBeforeSelectPoint, onAfterSelectPoint, onShowHint to send the correct value for the HoverOrSelect parameter + * Corrected error when trying to get/set axis captions + * Corrected issues with certain data handlers corrupting data when Object.prototype has been modified + * Fixed issues with legend movement when the chart was inside a scrollable container + * Corrected Axis.pointToPixel so that it returns the proper value when given a string (bin) + * Corrected double loading of data when using JSONStringDataHandler + * Removed the ability for closeLine to affect drawing in LineSeries + * Corrected issues with AreaSeries drawing when closeLine was false in Safari + * Corrected AreaSeries drawing in IE when more than 5000 points exist + * Corrected issues with extremes calculations in StackedBarSeries + + +Version 2.0.1 + +1. New Features + * Added Series.getPadding, Series.setPadding methods + * Added Series.padding property + * Added Chart.legend_state (normal, minimized) + * Added Chart.legendMinimize, Chart.legendRestore, Chart.getLegendState methods + * Added ignoreBounds parameter to Axis.pointToPixel + +2. Bug Fixes + * BarSeries / FloatingBarSeries - Fixed dimension calculation which caused bars + to be missed if the mouse was close to an axis + * BarSeries / FloatingBarSeries - Updated point location / distance to return + exact matches when the mouse is within a bar + * Fixed issue with chart files when in a directory named ejschart_ + * Corrected issue with axis.force_static_points and cursor position + * Corrected issue with legend icons causing non-secure warning in https session + * Added catch to all try/finally statements to account for IE bug + * Corrected adjustment to so that crosshairs position exactly under the cursor + * Renamed variable int to interval to correct Safari 2 parse issue + * Updated default series padding to account for axis assignment + * Corrected issue with horizontal bars not drawing when rendered 1 pixel wide + +3. Documentation Updates + * Added documentation for META tag options + * Added documentation for Series.findClosestByPoint and Series.findClosestByPixel + + +Version 2.0 + +1. New Features: + * Multiple X and Y Axes + * Logarithmic Axis + * JSON String and File Data Handlers + * Stacked Bar Series + * Floating Bar Series + * String Formatter for prefixing and appending to labels + * OpenHighLowClose Series + * Candlestick Series + * Filled Scatter Series Points in IE + * Much optimized rendering of axis labels and ticks + * Cleaner and smaller HTML generation of chart objects + * Faster chart rendering and much better support for many + charts in a single page + * Removed the need for EJSChartIE.css and EJSChartIE6.css + * Better control over styling of major tick marks + * More styling options for grid lines and axis borders + * Easier customization of cursor position elements + * Background coloring with opacity of the chart area and axes \ No newline at end of file diff --git a/EJSChart.css b/EJSChart.css new file mode 100644 index 0000000..115603e --- /dev/null +++ b/EJSChart.css @@ -0,0 +1,132 @@ +/* Emprise JavaScript Charts Stylesheet - Version 2.1 */ +/* Copyright (C) 2006-2009 Emprise Corporation. All Rights Reserved. */ + +.ejschart, .ejschart * { font-family: Verdana; font-size: 10px; cursor: crosshair; padding: 0px; margin: 0px; border: 0px; outline: none; MozUserSelect: none; KhtmlUserSelect: none; -moz-user-select: none; -webkit-user-select: none; -khtml-user-select: none; -o-user-select: none; user-select: none; } +.ejschart { position: relative; width: 100%; height: 100%; } +.ejsc-chart, .ejsc-axes-canvas, .ejsc-series-canvas, .ejsc-series-canvas-div, .ejsc-hint-canvas, .ejsc-canvas-cover, .ejsc-labels { position: absolute; width: 100%; height: 100%; top: 0px; left: 0px; } +.ejsc-canvas-cover { cursor: crosshair; background-color: #ffffff; opacity: 0; alpha: 0; -moz-opacity: 0; -khtml-opacity: 0; filter: alpha(opacity=0); } +.ejsc-canvas-cover { height: expression((this.parentNode.offsetHeight)+"px"); width: expression((this.parentNode.offsetWidth)+"px"); -moz-user-select: none; -webkit-user-select: none; -khtml-user-select: none; user-select: none; } +.ejsc-legend { position: absolute; top: 40px; right: 20px; height: 114px; width: 180px; background-color: #fff; border: 1px solid #aaa; opacity: 0.9; z-index: 9999; filter: alpha(opacity=90); } +.ejsc-legend, .ejsc-legend *, +.ejsc-legend-right, .ejsc-legend-right *, +.ejsc-legend-bottom, .ejsc-legend-bottom * { alpha: 1; -moz-opacity: 1; -khtml-opacity: 1; opacity: 1; filter: alpha(opacity=100); cursor: default; } +.ejsc-legend-minimized { height: 14px; } +.ejsc-legend-minimized .ejsc-legend-minimize { display: none; } +.ejsc-legend-minimized .ejsc-legend-maximize { display: block; } +.ejsc-legend-minimized .ejsc-legend-series-container { display: none; } +.ejsc-legend-minimized .ejsc-legend-owner { display: none; } +.ejsc-legend-caption { padding-left: 3px; background-color: #EEE; background-image: url("images/images.gif"); background-position: 0px -18px; background-repeat: repeat-x; border-bottom: 1px solid #aaa; height: 16px; line-height: 16px; font-weight: bold; text-align: left; overflow: hidden; } +.ejsc-legend-minimize { height: 16px; width: 16px; float: right; } +.ejsc-legend-minimize-mouseout { height: 16px; width: 16px; overflow: hidden; background: url('images/images.gif') -4px -86px no-repeat; } +.ejsc-legend-minimize-mouseover { height: 16px; width: 16px; overflow: hidden; background: url('images/images.gif') -4px -102px no-repeat; } +.ejsc-legend-maximize { display: none; height: 16px; width: 16px; float: right; } +.ejsc-legend-maximize-mouseout { height: 16px; width: 16px; overflow: hidden; background: url('images/images.gif') -4px -54px no-repeat; } +.ejsc-legend-maximize-mouseover { height: 16px; width: 16px; overflow: hidden; background: url('images/images.gif') -4px -70px no-repeat; } +.ejsc-legend-grabber { width: 10px; height: 16px; float: left; overflow: hidden; background: url('images/images.gif') -8px -119px no-repeat; } +.ejsc-legend-owner { background: url('images/images.gif') 0px -34px repeat-x #f4f4f4; height: 14px; line-height: 14px; text-align: left; border-top: 1px solid #aaa; overflow: hidden; padding: 0px 3px; } +.ejsc-legend-owner-icon { background: url('images/images.gif') -6px -328px no-repeat; height: 14px; width: 14px; float: left; } +.ejsc-legend-series-container { height: 80px; padding: 0px; overflow: auto; padding: 1px; position: relative; } +.ejsc-legend-series-div { overflow: hidden; } +.ejsc-legend-series-out { display: block; height: 17px; line-height: 17px; text-decoration: none; margin: 0px; background-image: none; text-decoration: none; background-color: #fff; padding: 2px 3px; position: relative; } +.ejsc-legend-series-over { display: block; height: 17px; line-height: 17px; text-decoration: none; margin: 0px; background-image: url("images/images.gif"); background-position: 0px -34px; background-repeat: repeat-x; text-decoration: none; background-color: #eee; padding: 1px 2px; border: 1px solid #aaa; position: relative; } +.ejsc-legend-series-icon { position: absolute; left: 0px; top: 0px; border: 0px; height: 18px; width: 18px; margin-right: 4px; } +.ejsc-legend-series-icon img { background-image: url("images/images.gif"); background-repeat: no-repeat; height: 16px; width: 16px; border: 0px; position: relative; top: 2px; left: 2px; } +.ejsc-legend-series-over .ejsc-legend-series-icon img { margin: -1px; } +.ejsc-legend-series-caption { margin-left: 18px; margin-right: 18px; position: absolute; width: 100%; line-height: 16px; text-decoration: none; background-image: none; border-bottom: 0px; font-weight: normal; text-align: left; overflow: hidden; height: 17px; white-space: nowrap; } +.ejsc-legend-series-visibility { position: absolute; right: 0px; top: 2px; height: 16px; width: 16px; margin-left: 4px; overflow: hidden; } +.ejsc-legend-series-visibility div { cursor: pointer; height: 16px; width: 16px; overflow: hidden; } +.ejsc-legend-series-on { background: url('images/images.gif') -4px -486px no-repeat; } +.ejsc-legend-series-off { background: url('images/images.gif') -4px -470px no-repeat; } +.ejsc-legend-series-over .ejsc-legend-series-visibility div { margin: -1px 1px; } +.ejsc-canvas-container { position: absolute; } +.ejsc-series-canvas-container { position: absolute; overflow: hidden; } +.ejsc-titlebar { position: absolute; width: 100%; height: 20px; } +.ejsc-titlebar-text { font-size: 11px; font-weight: bold; vertical-align: top; margin-left: 4px; } +.ejsc-zoombox { position: absolute; background-color: #3a6ea5; alpha: .20; -moz-opacity: .20; -khtml-opacity: .20; opacity: .20; filter: alpha(opacity=20); overflow: hidden; } +.ejsc-h-crosshair { position: absolute; height: 100%; width: 1px; left: 50%; margin-left: -1px; overflow: hidden; display: none; } +.ejsc-v-crosshair { position: absolute; width: 100%; height: 1px; top: 50%; margin-top: -1px; overflow: hidden; display: none; } +.ejsc-message { position: absolute; padding: 6px 6px; text-align: center; } +.ejsc-message-info { background-color: #FFFFE1; color: #000; font-weight: bold; border: 1px solid #000; alpha: .80; -moz-opacity: .80; -khtml-opacity: .80; opacity: .80; filter: alpha(opacity=80); } +.ejsc-message-error { background-color: #DA2424; color: #000; font-weight: bold; border: 1px solid #000; alpha: .80; -moz-opacity: .80; -khtml-opacity: .80; opacity: .80; filter: alpha(opacity=80); } +.ejsc-message-nodata { background-color: #fff; color: #000; font-weight: bold; border: 1px solid #000; alpha: .80; -moz-opacity: .80; -khtml-opacity: .80; opacity: .80; filter: alpha(opacity=80); } +.ejsc-message-progress { background-color: #77B3D6; color: #3B5768; font-weight: bold; border: 1px solid #5A869F; alpha: .80; -moz-opacity: .80; -khtml-opacity: .80; opacity: .80; filter: alpha(opacity=80); } +.ejsc-key-grabber { position: absolute; top: 0px; left: 0px; height: 1px; width: 1px; overflow: hidden; display: block; background-color: transparent; border: none; } +.ejsc-h-axis-labels, .ejsc-v-axis-labels, .ejsc-v-label, .ejsc-h-label { position: absolute; } +.ejsc-h-axis-labels, .ejsc-v-axis-labels { height: 100%; width: 100%; } +.ejsc-v-label { text-align: right; } +.ejsc-h-label { text-align: center; } +.ejsc-cursor-position { position: relative; width: 100%; height: 100%; } +.ejsc-cursor-position-marker { overflow: hidden; position: absolute; } +.ejsc-cursor-position-label { padding: 1px; position: absolute; white-space: nowrap; } +.ejsc-cursor-position-h .ejsc-cursor-position-marker { height: 100%; width: 1px; } +.ejsc-cursor-position-v .ejsc-cursor-position-marker { width: 100%; height: 1px; } +.ejsc-cursor-position-marker-left { left: 0px; } +.ejsc-cursor-position-marker-top { top: 0px; } +.ejsc-cursor-position-marker-right { right: 0px; } +.ejsc-cursor-position-marker-bottom { bottom: 0px; } +.ejsc-cursor-position-label-left { text-align: left; left: 0px; } +.ejsc-cursor-position-label-top { top: 0px; } +.ejsc-cursor-position-label-right { text-align: right; right: 0px; } +.ejsc-cursor-position-label-bottom { bottom: 0px; } +.ejsc-v-axis-caption { position: absolute; top: 50%; font-size: 12px; font-weight: bold; white-space: nowrap; text-align: center; overflow: visible; } +.ejsc-left-axis-caption { -moz-transform: rotate(-90deg); -webkit-transform: rotate(-90deg); -o-transform: rotate(-90deg); -ms-transform: rotate(-90deg); } +.ejsc-right-axis-caption { -moz-transform: rotate(90deg); -webkit-transform: rotate(90deg); -o-transform: rotate(90deg); -ms-transform: rotate(90deg); } +.ejsc-h-axis-caption { position: absolute; font-size: 12px; height: 20px; line-height: 20px; font-weight: bold; text-align: center; white-space: nowrap; } +.ejsc-v-axis-caption span, .ejsc-h-axis-caption span { font-size: 12px; font-weight: bold; white-space: nowrap; text-align: center; position: relative; } +.ejsc-v-axis-caption span { font-size: 12px; font-weight: bold; white-space: nowrap; text-align: center; } +.ejsc-hint { z-index: 1; white-space: nowrap; font-family: Helvetica, sans-serif; font-size: 10px; display: none; position: absolute; top: 0px; left: 0px;/* alpha: .70; -moz-opacity: .70; -khtml-opacity: .70; opacity: .70; filter: alpha(opacity=70); */border: 1px solid #999999; background-color: #ffffff; padding: 3px; text-align: left; } +.ejsc-hint a { cursor: pointer; } +.ejsc-hint label { font-weight: bold; font-size: 11px; display: inline; } +.ejsc-hint-canvas-container label { font-weight: bold; display: inline; } +.ejsc-hint-pointer { z-index: 1; display: none; position: absolute; top: 0px; left: 0px; height: 12px; width: 12px; overflow: hidden; } +.ejsc-hint-pointer div { height: 12px; width: 12px; overflow: hidden; } +.ejsc-hint-tl { background: url('images/images.gif') 0px -208px no-repeat; } +.ejsc-hint-tr { background: url('images/images.gif') -12px -208px no-repeat; } +.ejsc-hint-bl { background: url('images/images.gif') 0px -220px no-repeat; } +.ejsc-hint-br { background: url('images/images.gif') -12px -220px no-repeat; } +.ejsc-hidden { display: none; } +.ejsc-invisible { visibility: hidden; } +.ejsc-visible { visibility: visible; } +.ejsc-legend-tree { height: auto; } +.ejsc-legend-tree-items { margin-left: 32px; margin-right: 4px; } +.ejsc-legend-tree-items .ejsc-legend-tree-items { margin-left: 8px; margin-right: 4px; } +.ejsc-legend-tree-item { height: 16px; line-height: 16px; overflow: hidden; white-space: nowrap; text-decoration: none; display: block; cursor: pointer; } +.ejsc-legend-tree-item:hover { font-weight: bold; } +.ejschart, .ejschart * { text-align: left; } + +/* BEGIN SERIES ICONS */ + .ejsc-legend-series-icon img.undefined { background-position: 0px 0px; } + .ejsc-legend-series-icon img.line { background-position: -4px -438px; } + .ejsc-legend-series-icon img.area { background-position: -4px -390px; } + .ejsc-legend-series-icon img.scatter { background-position: -4px -406px; } + .ejsc-legend-series-icon img.pie { background-position: -4px -422px; } + .ejsc-legend-series-icon img.bar-vertical { background-position: -4px -454px; } + .ejsc-legend-series-icon img.bar-horizontal { background-position: -4px -374px; } + .ejsc-legend-series-icon img.floating-bar-vertical { background-position: -4px -518px; } + .ejsc-legend-series-icon img.floating-bar-horizontal { background-position: -4px -502px; } + .ejsc-legend-series-icon img.stacked-bar-vertical { background-position: -4px -264px; } + .ejsc-legend-series-icon img.stacked-bar-horizontal { background-position: -4px -534px; } + .ejsc-legend-series-icon img.function { background-position: -4px -312px; } + .ejsc-legend-series-icon img.trend { background-position: -4px -296px; } + .ejsc-legend-series-icon img.candlestick { background-position: -4px -248px; } + .ejsc-legend-series-icon img.ohlc { background-position: -4px -232px; } + .ejsc-legend-series-icon img.analog-gauge { background-position: -4px -280px; } +/* END SERIES ICONS */ + +.ejsc-legend-right { position: absolute; top: 0px; right: 0px; border: 1px solid #ccc; background: #fff; overflow: auto;; } +.ejsc-legend-bottom { position: absolute; bottom: 0px; left: 0px; border: 1px solid #ccc; background: #fff; overflow: auto; } +.ejsc-legend-bottom .ejsc-legend-series { width: 100px; float: left; } +.ejsc-legend-bottom .ejsc-legend-series-container { height: auto !important; } +.ejsc-zoom-buttons { position: absolute; top: 0px; right: 0px; } +.ejsc-zoom-buttons button { position: absolute; top: 0; right: 0; padding: 0; margin: 0 !important; height: 12px; width: 12px; border: 0; background-color: transparent; background-image: url('./images/zoom.gif'); overflow: hidden; } +.ejsc-zoom-buttons button.zoom-in { background-position: -12px 0; } +.ejsc-zoom-buttons button.zoom-out { right: 16px; background-position: 0 0; } + +@media print { + .ejsc-canvas-cover { display: none !important; } + .ejsc-legend, .ejsc-legend * { filter: none !important; opacity: 1 !important; } + .ejsc-v-axis-caption { filter: flipH !important; } + .ejsc-zoom-buttons { display: none !important; } +} + + diff --git a/EJSChart.js b/EJSChart.js new file mode 100644 index 0000000..d9bf67c --- /dev/null +++ b/EJSChart.js @@ -0,0 +1,20054 @@ +/********************************************************************** +/* Emprise JavaScript Charts 2.3.1 http://www.ejschart.com/ +/* Copyright (C) 2006-2013 Emprise Corporation. All Rights Reserved. +/* +/* WARNING: This software program is protected by copyright law +/* and international treaties. Unauthorized reproduction or +/* distribution of this program, or any portion of it, may result +/* in severe civil and criminal penalties, and will be prosecuted +/* to the maximum extent possible under the law. +/* +/* See http://www.ejschart.com/license.html for full license. +/**********************************************************************/ +//(function() { + + if (window.console == undefined) { + window.console = { + log: function(msg) {} + }; + }; + + // -------------------------------------------------------------------------- + // IE Fix: Background Image Cache + // -------------------------------------------------------------------------- + // Force IE to cache all background images to reduce/eliminate the "flashing" + // of hover events when a background image is present on the item affected. + // -------------------------------------------------------------------------- + try { document.execCommand("BackgroundImageCache", false, true); } catch(e) {}; + + // -------------------------------------------------------------------------- + // Component Information + // -------------------------------------------------------------------------- + // Holds all of the information describing this component. This information + // is shown in the "About EJSChart" window. + // -------------------------------------------------------------------------- + var __title = 'Emprise JavaScript Charts'; // Title of the chart component + var __short_title = 'EJSChart'; // Short version of the title + var __version = '2.3'; // Version + var __url = 'http://www.ejschart.com/'; // URL to web site + var __urlHelp = 'http://www.ejschart.com/help/'; // URL to documentation + var __curyear = (new Date()).getFullYear(); // Current year (used in copyright) + var __about = '' + __title + '' + + '
' + + 'Current Version: ' + __version + + '
 
' + + 'Copyright 2006 - ' + __curyear + ' by' + + '
' + + 'Emprise Corporation'; + + // -------------------------------------------------------------------------- + // Global Constants + // -------------------------------------------------------------------------- + // These variables store all of the values that are constant between charts + // including storing values in easy-to-remember variable names. + // -------------------------------------------------------------------------- + var _x = 0; // Constant used when coverting px-pt or pt-px + var _y = 1; // Constant used when coverting px-pt or pt-px + var __default_canvas_height = 300; // Default height of the CANVAS element + var __default_canvas_width = 400; // Default width of the CANVAS element + var __DefaultColors = [ + 'rgb(120,90,59)' , + 'rgb(53,115,53)' , + 'rgb(178,87,56)' , + 'rgb(203,143,71)' , + 'rgb(55,106,155)' , + 'rgb(205,197,51)' , + 'rgb(209,130,139)' , + 'rgb(159,153,57)' , + 'rgb(206,173,136)' , + 'rgb(191,132,72)' , + 'rgb(151,135,169)' , + 'rgb(140,48,51)' , + 'rgb(59,144,187)' , + 'rgb(197,190,104)' , + 'rgb(109,136,79)' , + 'rgb(144,100,144)' , + 'rgb(181,94,94)' , + 'rgb(59,144,144)' , + 'rgb(204,136,92)' , + 'rgb(139,167,55)' , + 'rgb(205,171,66)' , + 'rgb(150,184,211)' + ]; + + // Cache math functions and variables + var m_SQRT = Math.sqrt, + m_POW = Math.pow, + m_SIN = Math.sin, + m_COS = Math.cos, + m_TAN = Math.tan, + m_ATAN = Math.atan, + m_ROUND = Math.round, + m_FLOOR = Math.floor, + m_CEIL = Math.ceil, + m_ABS = Math.abs, + m_LOG = Math.log, + m_EXP = Math.exp, + m_PI = Math.PI, + m_PIx2 = m_PI * 2, + m_PId2 = m_PI / 2; + + // -------------------------------------------------------------------------- + // NAMESPACE: EJSC + // -------------------------------------------------------------------------- + // Declare chart namespace to avoid conflicts with any other JavaScript + // packages. + // -------------------------------------------------------------------------- + (EJSC = { + +/* JGD - 2011-03-02 - Added */ + __ready: false, + + // V2 - STRINGS object is used to hold all static strings in order to allow for easier localization + STRINGS: { + building_message: "Building Chart...", + max_zoom_message: "Max Zoom Reached", + drawing_message: "Drawing...", + chart_legend_title: "Chart Legend", + y_axis_caption: "Y Axis", + x_axis_caption: "X Axis" + }, + + // Used to store chart source path + __srcPath: undefined, + + // -------------------------------------------------------------------------- + // Axis Arrays + // -------------------------------------------------------------------------- + // These arrays hold all of the values needed to draw the axis data and + // the grid on the chart. The values decide how to split up the "tick marks" + // and how to round the shown values. + // -------------------------------------------------------------------------- + __ticks: new Array( 29030400000, 7257600000, 2419200000, 604800000, 259200000, 86400000, 21600000, 14400000, 3600000, 1000000, 500000, 250000, 100000, 50000, 25000, 10000, 5000, 2500, 1000, 500, 250, 100, 50, 25, 10, 5, 2.5, 1, .5, .25, .1, .05, .025, .01, .005, .0025, .001, .0005, .00025, .0001, .00005, .000025, .00001, .000005, .0000025, .000001, .0000005, .00000025, .0000001, .00000005, .000000025, .00000001, .000000005, .0000000025, .000000001 ), + __subticks: new Array( 7257600000, 2419200000, 604800000, 86400000, 86400000, 21600000, 3600000, 3600000, 600000, 250000, 100000, 50000, 25000, 10000, 5000, 2500, 1000, 500, 250, 100, 50, 25, 10, 5, 2, 1, .5, .25, .1, .05, .025, .01, .005, .0025, .001, .0005, .00025, .0001, .00005, .000025, .00001, .000005, .0000025, .000001, .0000005, .00000025, .0000001, .00000005, .000000025, .00000001, .000000005, .0000000025, .000000001, .0000000005, .00000000025 ), + __tickRound: new Array( undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7, 6, 7, 8, 7, 8, 9, 8, 9, 10, 9, 10, 11 ), + + // -------------------------------------------------------------------------- + // Formatting Arrays + // -------------------------------------------------------------------------- + // These arrays hold all of the values needed to format values that are + // displayed on the axis and in the hints. + // -------------------------------------------------------------------------- + __months: new Array( 'January' , 'February' , 'March' , 'April' , 'May' , 'June' , 'July' , 'August' , 'September' , 'October' , 'November' , 'December' ), + __days: new Array( 'Sunday' , 'Monday' , 'Tuesday' , 'Wednesday' , 'Thursday' , 'Friday' , 'Saturday' ), + + // -------------------------------------------------------------------------- + // Event Array + // -------------------------------------------------------------------------- + // This array holds all of the attached events that are added to DOM + // elements in order to be removed on page unload. + // -------------------------------------------------------------------------- + __events: [], // Saves all attached events for removal + + // Reference to page header for inserting styles and scripts + __headTag: document.getElementsByTagName("head")[0], + // Reference to html tag, used for positioning + __htmlTag: document.getElementsByTagName("html")[0], + // JHM: 2008-09-24 - Save reference to link tag in order to properly free when page is unloaded + // Reference to link tag, used for loading style sheet + __linkTag: null, + __isIE: !window.CanvasRenderingContext2D, + + DefaultImagePath: 'images/', + + // -------------------------------------------------------------------------- + // Default Color Arrays + // -------------------------------------------------------------------------- + // This array is used in each chart to specify available series colors. Add + // to this array in order to affect all chart created after modification or + // modify the AvailableColors property of the chart object after it's been + // created to specify colors specific to an instance of a chart + // -------------------------------------------------------------------------- + DefaultColors: __DefaultColors.slice(), + DefaultPieColors: __DefaultColors.slice(), + DefaultBarColors: __DefaultColors.slice(), + + // -------------------------------------------------------------------------- + // Container Arrays + // -------------------------------------------------------------------------- + // These arrays hold all of the objects that the ESJC object creates. + // -------------------------------------------------------------------------- + __Charts: [], // Holds all charts that have been created + __ResizeTimeouts: [], // Holds all resize timeouts that have been created + __ResizeCharts: [], // Holds all charts to be resized automatically + + // JHM: Added storage for resize interval + __ResizeInterval: undefined, // Holds the window resize interval reference + + // -------------------------------------------------------------------------- + // Unique Series Identifier + // -------------------------------------------------------------------------- + // This holds the unique index that is incremented for every series that is + // created, and stored as that series' index. + // -------------------------------------------------------------------------- + __CurrentUniqueSeriesIndex: 0, // Unique Series Index + + // -------------------------------------------------------------------------- + // FUNCTION: __init + // -------------------------------------------------------------------------- + // Top level initialization function that is called after the entire EJSC + // object has been defined. Attaches unload event to window to detach all + // events. + // -------------------------------------------------------------------------- + // => This function is called when the EJSC namespace has been defined. + // -------------------------------------------------------------------------- + __init: function() { + + // Register window events for cleanup + // interval in order to monitor the chart container size and resize as necessary + EJSC.utility.__attachEvent(window, "load", EJSC.__doLoad); + EJSC.utility.__attachEvent(window, "unload", EJSC.__doUnload); + + // Create base classes + EJSC.Inheritable.__extendTo(EJSC.__Series); + this.Series = EJSC.__Series; + EJSC.Inheritable.__extendTo(EJSC.__Point); + this.Point = EJSC.__Point; + EJSC.Inheritable.__extendTo(EJSC.__DataHandler); + this.DataHandler = EJSC.__DataHandler; + this.Formatter = new EJSC.__Formatter(); + EJSC.Inheritable.__extendTo(EJSC.__Axis); + this.Axis = EJSC.__Axis; + + // Check for META tag options + var i; + var metaTags = EJSC.__headTag.getElementsByTagName("meta"); + EJSC.loadCompatibilityFile = false; + try { + for (i = 0; i < metaTags.length; i++) { + if (metaTags[i].name == "ejsc-src-path") { + EJSC.__srcPath = metaTags[i].content; + } else if (metaTags[i].name == "ejsc-auto-load-support-files") { + if (metaTags[i].content.match(/false/i) != null) { + return; + } + } else if (metaTags[i].name == "ejsc-v1-compatibility") { + if (metaTags[i].content.match(/true/i) != null) { + EJSC.loadCompatibilityFile = true; + } + } + } + } catch (e) { + } finally { + delete metaTags; + metaTags = null; + } + + // JHM: 2007-04-15 - Added to insert scripts/styles as necessary in order to reduce the + // coding requirements of end users and ensure styles exist before chart html is added + // to the page + + // Extract chart source path from script tag + // This will only function properly if the javascript file is named EJSChart.js + // or EJSChart_*.js where * is some additional string + if (EJSC.__srcPath == undefined) { + var scriptTags = EJSC.__headTag.getElementsByTagName("script"); + // JHM: 2007-06-01 - Updated document.getElementsByTagName to __headTag.getElementsByTagName + // JHM: 2008-08-08 - Updated regexp to ignore directories named ejschart_ + for (i = 0; i < scriptTags.length; i++) { + if (scriptTags[i].src && scriptTags[i].src.match(/EJSChart(\_[^\/\\]*)?\.js(\?.*)?$/i)) { + // JHM: 2007-09-11 - Updated to use EJSC.__srcPath + EJSC.__srcPath = scriptTags[i].src.replace(/EJSChart(\_[^\/\\]*)?\.js(\?.*)?$/i, ""); + break; + } + } + delete scriptTags; + scriptTags = null; + } + + // Update default image path + // JHM: 2007-09-11 - Updated to use EJSC.__srcPath + EJSC.DefaultImagePath = EJSC.__srcPath + EJSC.DefaultImagePath; + + // Internet Explorer needs special handling of style insert + // and additional scripts and styles + if (EJSC.__isIE) { + var req = EJSC.utility.XMLRequestPool.sendRequest(EJSC.__srcPath + "excanvas.js"); + // Update namespace and css to support IE8RC1 + req = req.responseText.replace('urn:schemas-microsoft-com:vml");','urn:schemas-microsoft-com:vml","#default#VML");'); + req = req.replace('"g_vml_\\:*{behavior:url(#default#VML)}";', '"g_vml_\\:*{behavior:url(#default#VML)} g_vml_\\:shape {display: inline-block;}";'); + eval(req); + fixUpExCanvas(); + } + + // JHM: 2008-09-24 - Save reference to link tag so it can be properly freed on page unload + // Insert style tag into header + EJSC.__linkTag = document.createElement("link"); + EJSC.__linkTag.rel = "stylesheet"; + EJSC.__linkTag.type = "text/css"; + EJSC.__linkTag.href = EJSC.__srcPath + "EJSChart.css"; + EJSC.__linkTag.media = "screen,print"; + EJSC.__headTag.appendChild(EJSC.__linkTag); + + + if (EJSC.__isIE) { + + // precompute "00" to "FF" + var dec2hex = []; + for (var i = 0; i < 16; i++) { + for (var j = 0; j < 16; j++) { + dec2hex[i * 16 + j] = i.toString(16) + j.toString(16); + } + }; + + function processLineCap(lineCap) { + switch (lineCap) { + case "butt": return "flat"; + case "round": return "round"; + case "square": + default: + return "square"; + } + }; + + function processStyle(styleString) { + var str, alpha = 1; + + styleString = String(styleString); + if (styleString.substring(0, 3) == "rgb") { + var start = styleString.indexOf("(", 3); + var end = styleString.indexOf(")", start + 1); + var guts = styleString.substring(start + 1, end).split(","); + + str = "#"; + for (var i = 0; i < 3; i++) { + str += dec2hex[parseInt(guts[i])]; + } + + if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) { + alpha = guts[3]; + } + } else { + str = styleString; + } + + return [str, alpha]; + }; + + contextPrototype = window.CanvasRenderingContext2D.prototype; + contextPrototype.stroke = function(aFill) { + + // JHM + if (this.updating == undefined || this.updating == false) { + var lineStr = []; + } else { + if (this.lineStr == undefined) { this.lineStr = []; } + var lineStr = this.lineStr; + } + + var lineOpen = false; + var a = processStyle(aFill ? this.fillStyle : this.strokeStyle); + var color = a[0]; + var opacity = a[1] * this.globalAlpha; + + lineStr.push(' max.x) { + max.x = c.x; + } + if (min.y == null || c.y < min.y) { + min.y = c.y; + } + if (max.y == null || c.y > max.y) { + max.y = c.y; + } + } + } + lineStr.push(' ">'); + + if (typeof this.fillStyle == "object") { + var focus = {x: "50%", y: "50%"}; + var width = (max.x - min.x); + var height = (max.y - min.y); + var dimension = (width > height) ? width : height; + + focus.x = m_ROUND((this.fillStyle.focus_.x / width) * 100 + 50) + "%"; + focus.y = m_ROUND((this.fillStyle.focus_.y / height) * 100 + 50) + "%"; + + var colors = []; + + // inside radius (%) + if (this.fillStyle.type_ == "gradientradial") { + var inside = (this.fillStyle.radius1_ / dimension * 100); + + // percentage that outside radius exceeds inside radius + var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside; + } else { + var inside = 0; + var expansion = 100; + } + + var insidecolor = {offset: null, color: null}; + var outsidecolor = {offset: null, color: null}; + + // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie + // won't interpret it correctly + this.fillStyle.colors_.sort(function (cs1, cs2) { + return cs1.offset - cs2.offset; + }); + + for (var i = 0; i < this.fillStyle.colors_.length; i++) { + var fs = this.fillStyle.colors_[i]; + + colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ","); + + if (fs.offset > insidecolor.offset || insidecolor.offset == null) { + insidecolor.offset = fs.offset; + insidecolor.color = fs.color; + } + + if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) { + outsidecolor.offset = fs.offset; + outsidecolor.color = fs.color; + } + } + colors.pop(); + + lineStr.push(''); + } else if (aFill) { + lineStr.push(''); + } else { + lineStr.push('' + ); + } + + lineStr.push(""); + + // JHM + if (this.updating == undefined || this.updating == false) { + this.element_.insertAdjacentHTML("beforeEnd", lineStr.join("")); + } + + this.lineStr = lineStr; + this.currentPath_ = []; + }; + + // JHM + contextPrototype.render = function() { + if (EJSC.__ieVersion >= 5.8) { + this.element_.insertAdjacentHTML("beforeEnd", this.lineStr.join("")); + } else { + var frag = document.createDocumentFragment(); + var el = document.createElement("DIV"); + el.innerHTML = this.lineStr.join(""); + frag.appendChild(el); + this.element_.appendChild(frag.cloneNode(true)); + } + }; + + contextPrototype.beginUpdate = function() { + this.updating = true; + }; + + contextPrototype.endUpdate = function() { + this.updating = false; + this.render(); + }; + } else { + if (window.CanvasRenderingContext2D) { + window.CanvasRenderingContext2D.prototype.beginUpdate = function() { }; + window.CanvasRenderingContext2D.prototype.endUpdate = function() { }; + } + } + + }, + + // -------------------------------------------------------------------------- + // FUNCTION: __doLoad + // -------------------------------------------------------------------------- + // Attaches all document-level events to the document. + // -------------------------------------------------------------------------- + // => This function is called when the document is finished loading. + // -------------------------------------------------------------------------- + __doLoad: function() { + + // JHM: 2007-06-11 - Modified to attach events instead of assigning to global event + var ae = EJSC.utility.__attachEvent; + ae(document, 'mouseup', doAllMouseUp); + ae(document, 'mousemove', doAllMouseMove); + + // JHM: 2007-07-29 - Added to force safari to calculate element widths + var safLoadBug = document.body.offsetWidth; + EJSC.__doResize(); + + }, + + // -------------------------------------------------------------------------- + // FUNCTION: __doUnload + // -------------------------------------------------------------------------- + // Detaches all events from page to reduce memory leaks. + // -------------------------------------------------------------------------- + // => This function is called when the document is closed or unloaded. + // -------------------------------------------------------------------------- + __doUnload: function() { + + // JHM: 2007-09-03 - Clear reference to head tag (leak fix) + EJSC.__headTag = undefined; + EJSC.__htmlTag = undefined; + + // JHM: Remove resize interval + if (EJSC.__ResizeInterval != undefined) { + window.clearInterval(EJSC.__ResizeInterval); + EJSC.__ResizeInterval = undefined; + } + + var e, i, j, r; + + // Detach all events + // JHM: 2007-09-11 - Modified references from __events to EJSC.__events + while( EJSC.__events.length > 0 ) { + e = EJSC.__events.pop(); + // JHM: 2007-09-03 - Added check to ensure event is valid, event array entry is set to null + // if delete chart method is called + if (e != null) { + EJSC.utility.__detachEvent( e[0] , e[1] , e[2] , e[3] ); + } + delete e; + } + + // Remove JavaScript <-> DOM references + for ( i = 0 ; i < EJSC.__Charts.length ; i++ ) { + EJSC.__deleteChart(i, true, false); + } + + // Remove all XML pending requests + try { + while (EJSC.utility.XMLRequestPool.__requestPool.length > 0) { + r = EJSC.utility.XMLRequestPool.__requestPool.pop(); + // JHM: 2008-09-25 - Updated request cleanup to correct memory leak in IE + r.onreadystatechange = function() { }; + delete r; + r = null; + } + EJSC.utility.XMLRequestPool.__requestPool = null; + } catch (e) { + } + + // Remove all XML active requests + try { + while (EJSC.utility.XMLRequestPool.__activePool.length > 0) { + r = EJSC.utility.XMLRequestPool.__activePool.pop(); + // JHM: 2008-09-25 - Updated request cleanup to correct memory leak in IE + r.onreadystatechange = function() { }; + delete r; + r = null; + } + EJSC.utility.XMLRequestPool.__activePool = null; + EJSC.utility.XMLRequestPool = null; + } catch (e) { + } + + EJSC = null; + + // Force IE garbage collection + if (typeof window.CollectGarbage != "undefined") { + window.CollectGarbage(); + } + + }, + + // JHM: 2007-09-25 - Added __deleteChart method to cleanup after single chart deletion + __deleteChart: function(index, clearEl, detachEvents) { + + if (EJSC.__Charts[index] == null) { return; } + try { + var chart = EJSC.__Charts[index]; + EJSC.__Charts[index] = null; + + // JHM: 2008-09-24 - Remove chart specific events (fixes memory leak in IE) + EJSC.__removeChartResize(chart); + + if (chart.__message_timeout != undefined) { + window.clearTimeout(chart.__message_timeout); + chart.__message_timeout = undefined; + } + chart.__el.__chart = null; + + // JHM: 2008-09-24 - Added to correct memory leak when removing charts without a page refresh (all browsers!) + // Remove chart events if necessary + if (detachEvents == undefined || detachEvents == true) { + var e, i; + for (i = 0; i < EJSC.__events.length; i++) { + e = EJSC.__events[i]; + if (e != null) { + if (e[4] == chart) { + EJSC.utility.__detachEvent( e[0] , e[1] , e[2] , e[3] ); + delete e; + EJSC.__events[i] = null; + } + } + } + } + + // Cleanup the axes + chart.axis_left.__free(); + chart.axis_bottom.__free(); + chart.axis_right.__free(); + chart.axis_top.__free(); + + // Cleanup the series + for (var j = 0; j < chart.__series.length; j++) { + if (chart.__series[j].__free) { + chart.__series[j].__free(); + } + } + chart.__series = []; + + // Fix excanvas leaks + chart.__el_axes_canvas.getContext = null; + chart.__el_series_canvas.getContext = null; + chart.__el_hint_canvas.getContext = null; + + if (chart.__el_axes_canvas.context_) { + chart.__el_axes_canvas.context_.element_ = null; + chart.__el_axes_canvas.context_ = null; + } + chart.__axes_context = null; + chart.__el_axes_canvas = null; + + if (chart.__el_series_canvas.context_) { + chart.__el_series_canvas.context_.element_ = null; + chart.__el_series_canvas.context_ = null; + } + chart.__series_context = null; + chart.__el_series_canvas = null; + + if (chart.__el_hint_canvas.context_) { + chart.__el_hint_canvas.context_.element_ = null; + chart.__el_hint_canvas.context_ = null; + } + chart.__hint_context = null; + chart.__el_hint_canvas = null; + + chart.__el_container = null; + chart.__el_chart_container = null; + chart.__el_axes_canvas_container = null; + chart.__el_series_canvas_container = null; + chart.__el_series_canvas_div = null; + chart.__el_titlebar = null; + chart.__el_mouse_position = null; + chart.__el_titlebar_text = null; + chart.__el_labels = null; + chart.__el_hint_labels = null; + chart.__el_zoombox = null; + chart.__el_message = null; + chart.__el_key_grabber = null; + chart.__el_canvas_cover = null; + chart.__el_legend = null; + chart.__el_legend_title = null; + chart.__el_legend_series.innerHTML = ""; + chart.__el_legend_series = null; + chart.__el_legend_owner = null; + chart.__el_legend_owner_title = null; + chart.__el_legend_minimize = null; + chart.__el_legend_maximize = null; + chart.__el_hint = null; + chart.__el_hint_text = null; + chart.__el_hint_pointer = null; + + + if (clearEl === true) { + chart.__el.innerHTML = ""; + } + + chart.__el = null; + + delete chart; + chart = null; + + } catch (e) { + } + + }, + + // -------------------------------------------------------------------------- + // FUNCTION: __doResize + // -------------------------------------------------------------------------- + // Calls for all charts on the page to resize and redraw themselves. + // -------------------------------------------------------------------------- + // => This function is called when the window is resized. + // -------------------------------------------------------------------------- + __doResize: function() { + if (EJSC.__ResizeInterval == undefined) { return false; } + + var i; + for (i = 0; i < EJSC.__ResizeTimeouts.length; i++) { + try { window.clearTimeout(EJSC.__ResizeTimeouts[i]); } catch (e) {} + } + EJSC.__ResizeTimeouts = []; + for (i = 0; i < EJSC.__ResizeCharts.length; i++) { + // JHM: 2007-09-03 - Added check to ensure chart reference is valid + if (EJSC.__ResizeCharts[i] != null) { + EJSC.__ResizeTimeouts[i] = window.setTimeout("EJSC.__doResizeTimeout(" + i + ")",1); + } + } + }, + __doResizeTimeout: function(index) { +/* JGD - 2011-03-02 - Added */ + if( EJSC.__ready == false && document.documentMode == 8 ) return; + if (EJSC.__ResizeCharts[index] != null) { + EJSC.__ResizeCharts[index].__resize(true, false, true); + } + }, + __addChartResize: function(chart) { + // Make sure its not already in the array + for (var i = 0; i < EJSC.__ResizeCharts.length; i++) { + if (EJSC.__ResizeCharts[i] == chart) { return; } + } + EJSC.__ResizeCharts.push(chart); + + // Start up resize timeout if not already running + if (EJSC.__ResizeInterval == undefined) { + // JHM: Added storage of the interval reference so it can be removed + EJSC.__ResizeInterval = window.setInterval(function() { EJSC.__doResize(); }, 500); + } + }, + __removeChartResize: function(chart) { + // Find the index of the chart + var i; + for (i = 0; i < EJSC.__ResizeCharts.length; i++) { + if (EJSC.__ResizeCharts[i] == chart) { break; } + } + + if (i < EJSC.__ResizeCharts.length) { + EJSC.__ResizeCharts.splice(i, 1); + } + + // Stop resize timeout if no more charts to resize + if (EJSC.__ResizeInterval != undefined) { + try { window.clearInterval(EJSC.__ResizeInterval); } catch (e) {} + } + }, + + // -------------------------------------------------------------------------- + // FUNCTION: __getUniqueSeriesIndex + // -------------------------------------------------------------------------- + // Finds the next unique series index and returns it to the series calling + // it. Then increments the unique index. + // -------------------------------------------------------------------------- + // => This function is called whenever a series is created. + // -------------------------------------------------------------------------- + __getUniqueSeriesIndex: function() { + return ++EJSC.__CurrentUniqueSeriesIndex; + }, + + // -------------------------------------------------------------------------- + // OBJECT: utility + // -------------------------------------------------------------------------- + utility: { + + // JHM: 2007-05-25 - Added __stringIsNumber for validating series data text labels + // -------------------------------------------------------------------------- + // FUNCTION: __stringIsNumber + // -------------------------------------------------------------------------- + // Checks the input string for existance of only characters which could + // be part of a number, double checks for mismatches with isNaN + // -------------------------------------------------------------------------- + __stringIsNumber: function(s) { + var result = true; + try { + if (typeof s == 'string') { + if ((s.match(/^[-.0-9Ee]+$/) != null)) { + if (isNaN(parseFloat(s))) { + result = false; + } + } else { + result = false; + } + } + } catch (e) { + result = true; + } + return result; + }, + // JHM: 2007-06-12 - Added __decToHex method + __decToHex: function(n) { + var hex = "0123456789ABCDEF"; + if (n < 0) { return "00"; } + else if (n > 255) { return "FF"; } + else { return hex.charAt(m_FLOOR(n / 16)) + hex.charAt(n % 16); } + }, + __getColor: function(str, opacity) { + var r, g, b, o, parts; + if (str.indexOf("rgb") != -1) { + parts = str.split("("); + parts = parts[1].split(")"); + parts = parts[0].split(","); + r = parts[0]; + g = parts[1]; + b = parts[2]; + if (opacity != undefined) { o = opacity; } + else if (parts.length > 3) { o = parts[3]; } + else { o = 1; } + } else { + str = str.replace("#", ""); + // JHM: 2007-01-25 - Updated to fix issues in IE + if (str.match(/\,/) != null) { + parts = str.split(","); + var color = "" + parts[0]; + var op = parts[1]; + } else { + var color = "" + str; + var op = 1; + } + if (opacity != undefined) { o = opacity; } + else { o = 1; } + + if (color.length == 3) { + parts = color.split(""); + color = parts[0] + parts[0] + parts[1] + parts[1] + parts[2] + parts[2]; + } else { + while (color.length < 6) { color += "0"; } + } + parts = color.split(""); + r = parseInt(parts[0] + "" + parts[1], 16); + g = parseInt(parts[2] + "" + parts[3], 16); + b = parseInt(parts[4] + "" + parts[5], 16); + } + + return { + hex: "#" + EJSC.utility.__decToHex(r) + EJSC.utility.__decToHex(g) + EJSC.utility.__decToHex(b), + red: r, + green: g, + blue: b, + opacity: o, + rgba: "rgba(" + r + "," + g + "," + b + "," + o + ")", + rgb: "rgb(" + r + "," + g + "," + b + ")" + }; + }, + __borderSize: function(el, side) { + try { + side = side.replace(/^([lrtb])(.*)/, function(str, p1, p2) { return p1.toUpperCase() + p2; }); + return el.style["border" + side + "Width"].replace(/([0-9]+)([^0-9]*)/, + function(str, p1, p2) { + if (p2 == "px") { return parseInt(p1); } + else if (p2 == "em") { + console.log("EJSChart: em border size is not currently supported."); + } else if (p2 == "%") { + console.log("EJSChart: % border size is not currently supported."); + } + return 0; + } + ); + } catch (e) { + return 0; + } + }, + // JHM: 2007-09-26 - Added __removeChildren method, removes all child nodes from the + // given node with DOM methods + __removeChildren: function(node) { + while (node.firstChild) { node.removeChild(node.firstChild); } + }, + // -------------------------------------------------------------------------- + // FUNCTION: __realXY + // -------------------------------------------------------------------------- + // Determines the true position of the cursor. + // -------------------------------------------------------------------------- + __realXY: function(e) { + // Variables + var xValue; + var yValue; + + // Find real X value + if (e.pageX) xValue = e.pageX; + else if (e.clientX) xValue = e.clientX + ( document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft ); + else xValue = null; + + // Find real Y value + if (e.pageY) yValue = e.pageY; + else if (e.clientY) yValue = e.clientY + ( document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ); + else yValue = null; + + // Return coordinates + // JHM: 2007-09-03 - Updated to use html tag reference stored in EJSC + xValue += EJSC.utility.__documentOffsetLeft(EJSC.__htmlTag); + yValue += EJSC.utility.__documentOffsetTop(EJSC.__htmlTag); + + // JGD: 2007-07-18 + // Added checks on mouse movement to pick up on all parentNodes' scrollTop and scrollLeft + var el = ((e.srcElement)?(e.srcElement):(e.target)); + while( el.parentNode != undefined ) { +/* JGD - 2011-03-07 - Fixed */ + if (el.parentNode == document.body || el.parentNode == EJSC.__htmlTag) { break; } + if (el.parentNode.scrollTop != undefined) { + yValue += el.parentNode.scrollTop; + } + if (el.parentNode.scrollLeft != undefined) { + xValue += el.parentNode.scrollLeft; + } + el = el.parentNode; + } + + if( EJSC.__htmlTag.clientTop ) { + xValue -= EJSC.__htmlTag.clientLeft; + yValue -= EJSC.__htmlTag.clientTop; + } + + // Return coordinates + return { x: xValue, y: yValue }; + }, + // -------------------------------------------------------------------------- + // FUNCTION: EJSC.utility.__documentOffsetTop + // -------------------------------------------------------------------------- + // Determines the distance an [[element]] is from the top of the page, and + // returns that value. + // -------------------------------------------------------------------------- + __documentOffsetTop: function(element, includeScroll) { + var offset = element.offsetTop; + + // JHM: 2008-10-02 - Added to correct issue with legend movement in a scrollable container + if (includeScroll == true) { + var se = element; + while (se.parentNode != null) { + if (se.parentNode.scrollTop > 0) offset -= se.parentNode.scrollTop; + if (se.parentNode.pageYOffset > 0) offset -= se.parentNode.pageYOffset; + se = se.parentNode; + }; + }; + + while (element.offsetParent != null) { + offset += element.offsetParent.offsetTop; + element = element.offsetParent; + }; + + // JHM: 2007-09-03 - Updated to use html tag reference stored in EJSC + offset += EJSC.__htmlTag.offsetTop; + return offset; + }, + // -------------------------------------------------------------------------- + // FUNCTION: EJSC.utility.__documentOffsetLeft + // -------------------------------------------------------------------------- + // Determines the distance an [[element]] is from the left side of the page, + // and returns that value. + // -------------------------------------------------------------------------- + __documentOffsetLeft: function(element, includeScroll) { + var offset = element.offsetLeft; + + // JHM: 2008-10-02 - Added to correct issue with legend movement in a scrollable container + if (includeScroll == true) { + var se = element; + while (se.parentNode != null) { + if (se.parentNode.scrollLeft > 0) offset -= se.parentNode.scrollLeft; + if (se.parentNode.pageXOffset > 0) offset -= se.parentNode.pageXOffset; + se = se.parentNode; + }; + }; + + while (element.offsetParent != null) { + offset += element.offsetParent.offsetLeft; + element = element.offsetParent; + }; + + // JHM: 2007-09-03 - Updated to use html tag reference stored in EJSC + offset += EJSC.__htmlTag.offsetLeft; + return offset; + }, + // -------------------------------------------------------------------------- + // FUNCTION: __attachEvent + // -------------------------------------------------------------------------- + // Attaches an event to the object in the documents and registers it in the + // events array to be deleted on unload. + // -------------------------------------------------------------------------- + __attachEvent: function( el, evt, proc, capture, reference ) { + capture = capture!=undefined?capture:false; + if (typeof el == "string") { + el = document.getElementById(el); + } + // Attach event to object + if (window.attachEvent) { + // JHM: 2007-07-29 - Removed validation of event attachment as it caused issues in Opera (returning false) + el.attachEvent( 'on' + evt , proc ); + } else if (window.addEventListener) { + el.addEventListener( evt , proc , ( capture != undefined ? capture : false ) ); + } + + // Save details on the attached event to it can be detached later + // JHM: 2007-09-11 - Modified references from __events to EJSC.__events + EJSC.__events[EJSC.__events.length] = [ el, evt, proc, capture, reference ]; + }, + // -------------------------------------------------------------------------- + // FUNCTION: __detachEvent + // -------------------------------------------------------------------------- + // Detaches an event from the object it was originally attached to. + // -------------------------------------------------------------------------- + __detachEvent: function( el, evt, proc, capture ) { + if (typeof el == "string") { + el = document.getElementById(el); + } + // Detach event from object + if( window.detachEvent ) { + el.detachEvent( 'on' + evt , proc ); + } else if( window.removeEventListener ) { + el.removeEventListener( evt , proc , (capture != undefined ? capture : false ) ); + } + }, + // -------------------------------------------------------------------------- + // FUNCTION: __createDOMArray + // -------------------------------------------------------------------------- + // Creates and returns an array of DOM objects from a JavaScript array to + // be attached to an element in the document. + // -------------------------------------------------------------------------- + __createDOMArray: function(nodes) { + // Create element + var newNode = document.createElement(nodes[0]); + var ref = undefined; + var refVar = undefined; + var styles, events, attr, style, event; + + // Attach attributes to element + var nodeAttrs = nodes[1]; + for(attr in nodeAttrs) { + switch (attr) { + case "__styles": + styles = nodeAttrs[attr]; + for (style in styles) { + newNode.style[style] = styles[style]; + }; + break; + case "__events": + events = nodeAttrs[attr]; + for (event in events) { + EJSC.utility.__attachEvent(newNode, event, events[event], false, ref); + } + break; + case "__ref": + ref = nodeAttrs[attr]; + break; + case "__refVar": + refVar = nodeAttrs[attr]; + break; + default: + newNode[attr] = nodeAttrs[attr]; + } + } + + // Save back a reference + if (ref != undefined && refVar != undefined) { + ref[refVar] = newNode; + } + + // Attach child elements (if any defined) + if (nodes.length >= 3) { + for (var index = 2; index < nodes.length; index++) { + newNode.appendChild(EJSC.utility.__createDOMArray(nodes[index])); + } + } + + // Return element + return newNode; + }, + // -------------------------------------------------------------------------- + // OBJECT: XMLRequestPool + // -------------------------------------------------------------------------- + // + // -------------------------------------------------------------------------- + XMLRequestPool: { + + __activePool: [], + __requestPool: [], + __activeXObjects: ["MSXML2.XMLHTTP.5.0","MSXML2.XMLHTTP.4.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"], + __activeRequestType: null, + __activeRequestIndex: undefined, + // JHM: 2007-09-25 - Added fatalErrors property in order to allow special processing for certain server response codes which normally indicate fatal error + // Removing a code from this list will allow requests to pass through to their callback + fatalErrors: [400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,500,501,502,503,504,505], + + MaxPoolSize: 8, + + // Create a new http request object + __createHTTPRequest: function() { + + // Determine the type of request object to create, only has to be done once + if (this.__activeRequestType == null) { + + // Native XMLHttpRequest object (Mozilla, etc.) + try { + this.__activeRequestType = "Native"; + return new XMLHttpRequest(); + } catch (e) {} + + // Microsoft ActiveX object + for (var i = 0; i < this.__activeXObjects.length; i++) { + try { + this.__activeRequestType = this.__activeXObjects[i]; + this.__activeRequestIndex = i; + return new ActiveXObject(this.__activeXObjects[i]); + } catch (e) {} + } + + } else { + if (this.__activeRequestType == "Native") { + try { return new XMLHttpRequest(); } catch (e) {} + } else { + try { return new ActiveXObject(this.__activeRequestType); } catch (e) {} + } + } + + // No request object found + this.__activeRequestType = null; + throw "Unable to create XMLHttpRequest object!"; + + }, + // Pull a request object from the pool or create one + __getHttpRequest: function() { + if (this.__requestPool.length > 0) { + return this.__requestPool.pop(); + } + return this.__createHTTPRequest(); + }, + // Return a request object to the pool + __returnHttpRequest: function(request) { + // Make sure we don't have too many objects floating around + request.onreadystatechange = function() { }; + if (this.__requestPool.length >= this.MaxPoolSize) { delete request; } + else { this.__requestPool.push(request); } + }, + // JHM: 2007-09-25 - Added onError parameter, defined as function(message, reference) callback + sendRequest: function(url, handler, data, reference, onError) { + + if (onError == undefined) { onError = null; } + + // Get a request object from the pool + // JHM: 2007-09-25 - Added try/catch to handle failure to create any request object + try { + var request = this.__getHttpRequest(); + } catch (e) { + if (onError != null) { + onError("Error Creating XMLHttpRequest: " + e.message, reference); + } + return false; + } + + // JHM: 2007-08-15 - Added IE check, when using 7 and the native XMLHttpRequest + // object or 6/7 and the 5.0 version of the ActiveX object requests to local files + // will fail. The code now handles IE separately and catches request.open failures, + // updating the __activeRequestType and trying again with the next in line + try { + // Send the request (asynchronous if handler is valid) + request.open((data==undefined?"GET":"POST"), url, (handler != undefined)); + } catch (e) { + if (EJSC.__isIE) { + request.onreadystatechange = function() { }; + delete request; + + if (this.__activeRequestType == "Native") { + this.__activeRequestIndex = 0; + } + + var i = this.__activeRequestIndex; + for (; i < this.__activeXObjects.length; i++) { + try { + this.__activeRequestType = this.__activeXObjects[i]; + this.__activeRequestIndex = i; + + request = new ActiveXObject(this.__activeXObjects[i]); + request.open((data==undefined?"GET":"POST"), url, (handler != undefined)); + + break; + } catch (e) { + request.onreadystatechange = function() { }; + delete request; + continue; + } + } + + // JHM: 2007-09-25 - Added error checking for non-IE failure on open request + } else if (onError != null) { + onError("Error opening connection: " + e.message, reference); + } + } + + // JHM: 2007-12-07 - Added correct headers when sending a request as POST + if (data != undefined) { + request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + request.setRequestHeader("Content-length", data.length); + request.setRequestHeader("Connection", "close"); + } + + // Build the call back if handler is valid + if(handler) { + var self = this; + request.onreadystatechange = function() { + // Ignore all states except complete + if (request.readyState == 4) { + // Clear state change event + request.onreadystatechange = function() { }; + // JHM: 2007-09-25 - Added checking status code for >= 400, trigger onError (if defined) and don't + // execute callback + var statusValid = true; + var errorCodes = self.fatalErrors; + for (var i = 0; i < errorCodes.length; i++) { + if (request.status == errorCodes[i]) { + statusValid = false; + if (onError != null) { + onError("Error retrieving file: " + request.status, reference); + } + break; + } + } + if (statusValid) { + // Handle response + handler(request, reference); + } + // Return the http object to the pool + self.__returnHttpRequest(request); + } + }; + } else { + // Clear state change event + request.onreadystatechange = function() { }; + } + + try { + if (data == undefined) { data = null; } + + // Open the connection + request.send(data); + } catch (e) { + // Return the request object to the pool + this.__returnHttpRequest(request); + // JHM: 2007-09-25 - Added calling onError (if defined) rather than throwing an exception + if (onError != null) { + onError("Connection Failed: " + e.message, reference); + } + } + + if (handler == undefined) + return request; + } + + }, + // -------------------------------------------------------------------------- + // FUNCTION: __canCoverMouseEvent + // -------------------------------------------------------------------------- + // Checks to see which element the cursor is over and determines if it can + // fire off mouse events on the canvas cover. + // -------------------------------------------------------------------------- + __canCoverMouseEvent: function( e ) { + // Get current element + var el; + if( e.srcElement ) el = e.srcElement; + else el = e.target; + + // Determine if mouse events can fire + try { + while (el.tagName.match(/^body$/i) == null ) { + if( el.className == 'ejsc-legend' ) return false; + el = el.parentNode; + } + } catch(e) { + return false; + } + + return true; + } + }, // End EJSC.utility + + // -------------------------------------------------------------------------- + // CLASS: math + // -------------------------------------------------------------------------- + // This is a utility class used to hold all common calculations + // -------------------------------------------------------------------------- + math: { + // -------------------------------------------------------------------------- + // FUNCTION: __distance + // -------------------------------------------------------------------------- + // Used to calculated the distance between two points + // -------------------------------------------------------------------------- + __distance: function(x1,y1,x2,y2) { + return m_SQRT(m_POW(x2 - x1, 2) + m_POW(y2 - y1, 2)); + } + }, + + // JHM: 2007-10-05 - Added TextLabel and TextLabels classes to better support axis bins for both X and Y + __TextLabel: function(label, index) { + this.__label = label; + this.__index = index + 1; + this.__usage = 1; + this.__manual = false; + }, + + // -------------------------------------------------------------------------- + // CLASS: TextLabels + // -------------------------------------------------------------------------- + // This is a class used to map text labels to integer values + // __add() will add a new element only if it does not already exist and + // return the newly added TextLabel object. If already existing, it will + // return the existing TextLabel object and increment its usage count by 1 + // __remove() will remove an element if its usage count is 1 and update the + // indexes of the remaining TextLabel objects, otherwise it will decrement + // its usage count by 1 + // __find() will return the TextLabel object matching the value parameter or + // null if not found + // -------------------------------------------------------------------------- + // JHM: Modified index to start at 1 rather than 0 so that zero planes function correctly + __TextLabels: function() { + this.__labels = new Array(); + this.__add = function(value) { + var label = this.__find(value); + if (label == null) { + label = new EJSC.__TextLabel(value, this.__labels.length); + this.__labels.push(label); + } else { + label.__usage++; + } + return label; + }; + this.__remove = function(value, manualRemove) { + var label = this.__find(value); + if (label != null) { + if (label.__usage > 1) { + if (manualRemove == true) { return; } + label.__usage--; + } else if (label.__manual != true || (manualRemove == true && label.__usage == 0)) { + var i = label.__index - 1; + this.__labels.splice(i, 1); + for (; i < this.__labels.length; i++) { + this.__labels[i].__index--; + } + } + } + }; + this.__find = function(value) { + for (var i = 0; i < this.__labels.length; i++) { + if (this.__labels[i].__label == value) { + return this.__labels[i]; + } + } + return null; + }; + this.__get = function(index) { + // JHM: 2008-08-08 - Fixed text label support to round number prior to attempting to find label + index = m_ROUND(index); + if (index - 1 >= this.__labels.length || index - 1 < 0) { return ""; } + else { return this.__labels[index - 1].__label; } + }; + this.__count = function() { + return this.__labels.length; + }; + }, + + // -------------------------------------------------------------------------- + // CLASS: Chart + // -------------------------------------------------------------------------- + // Chart class that holds all the generic information for each chart that is + // being created. Constructor expects the id of a DOM object and a set of + // object properties. + // -------------------------------------------------------------------------- + Chart: function( id , options ) { + + // JHM: 2008-05-29 - Added the ability to specify a dom node or id during chart creation + // Validate and save a reference to the chart div + if (id.tagName != undefined) { + this.__id = id.id; + this.__el = id; + } else { + this.__id = id; + this.__el = document.getElementById(id); + } + + if (this.__el == null) { + alert("Unable to create EJSC.Chart\n\nElement \"" + id + "\" does not exist on the current page."); + return false; + } else { + this.__el.innerHTML = ""; + } + + // Private variables + this.__index = EJSC.__Charts.length; + EJSC.__Charts[EJSC.__Charts.length] = this; + + this.__series = new Array(); + this.__el.__chart = this; + + this.__draw_area = undefined; // Current draw area dimensions, calculated in resize() + + this.__resize_width = 0; + this.__resize_height = 0; + // JHM: 2008-06-02 - Added to prevent multiple redraws when simply changing the display property of a parent node + this.__last_draw_width = 0; + this.__last_draw_height = 0; + + this.__mouse_in_chart = false; + this.__zooming = false; + this.__zoom_start = { x: undefined, y: undefined }; + this.__just_zoomed = false; + this.__moving = false; + this.__move_start = { x: undefined, y: undefined }; + this.__key_processing = false; + this.__legend_is_moving = false; + this.__legend_off_x = 0; + this.__legend_off_y = 0; + this.__legend_height = undefined; + this.__draw_series_on_load = true; + this.__colors = EJSC.DefaultColors.slice(); + this.__message_timeout = undefined; + this.__hint_is_sticky = false; +/* JGD - 2011-03-07 - Added */ + this.__canCtxMenu = true; + + + // Element variables + this.__el_container = undefined; + this.__el_chart_container = undefined; + this.__el_axes_canvas = undefined; + this.__el_series_canvas_container = undefined; + this.__el_series_canvas = undefined; + this.__el_series_canvas_div = undefined; + this.__el_titlebar = undefined; + this.__el_mouse_position = undefined; + this.__el_titlebar_text = undefined; + this.__el_hint_canvas = undefined; + this.__el_labels = undefined; + this.__el_hint_labels = undefined; + this.__el_zoombox = undefined; + this.__el_message = undefined; + this.__el_key_grabber = undefined; + this.__el_canvas_cover = undefined; + this.__el_legend = undefined; + this.__el_legend_title = undefined; + this.__el_legend_series = undefined; + this.__el_legend_owner = undefined; + this.__el_legend_owner_title = undefined; + this.__el_legend_minimize = undefined; + this.__el_legend_maximize = undefined; + this.__el_hint = undefined; + this.__el_hint_text = undefined; + this.__el_hint_pointer = undefined; + + // Drawing contexts + this.__axes_context = undefined; + this.__series_context = undefined; + this.__hint_context = undefined; // Not used + + // Public events + this.onBeforeBuild = undefined; + this.onAfterBuild = undefined; + this.onBeforeDraw = undefined; + this.onAfterDraw = undefined; + this.onShowHint = undefined; + this.onBeforeDblClick = undefined; + this.onDblClickPoint = undefined; + this.onBeforeSelectPoint = undefined; + this.onAfterSelectPoint = undefined; + this.onBeforeZoom = undefined; + this.onUserBeginZoom = undefined; + this.onUserEndZoom = undefined; + this.onAfterZoom = undefined; + this.onBeforeUnselectPoint = undefined; + this.onAfterUnselectPoint = undefined; + this.onBeforeMove = undefined; + this.onAfterMove = undefined; + this.onShowMessage = undefined; +/* JGD - 2011-02-28 - Added to support context menu */ + this.onContextMenu = undefined; +/* JGD - 2011-02-28 - Added to support before and after drawing series */ + this.onBeforeDrawSeries = undefined; + this.onAfterDrawSeries = undefined; + + // Public properties + this.axis_left = new EJSC.LinearAxis({ __chart_options: { caption: EJSC.STRINGS["y_axis_caption"] } }); + this.axis_bottom = new EJSC.LinearAxis({ __chart_options: { caption: EJSC.STRINGS["x_axis_caption"] } }); + this.axis_right = new EJSC.LinearAxis({ __chart_options: { caption: EJSC.STRINGS["y_axis_caption"], visible: false } }); + this.axis_top = new EJSC.LinearAxis({ __chart_options: { caption: EJSC.STRINGS["x_axis_caption"], visible: false } }); + + this.building_message = EJSC.STRINGS["building_message"]; + this.max_zoom_message = EJSC.STRINGS["max_zoom_message"]; + this.drawing_message = EJSC.STRINGS["drawing_message"]; + this.title = __title; + this.show_titlebar = true; + this.show_legend = true; + this.legend_position = 'floating'; // Values: 'floating', 'right', 'bottom' + this.legend_width = 104; + this.legend_height = 46; + this.legend_padding = 8; + this.show_messages = true; + this.allow_interactivity = true; + this.allow_zoom = true; + this.auto_zoom = undefined; // Values: "y","x" + this.show_hints = true; + this.proximity_snap = 5; + this.allow_hide_error = false; + this.image_path = EJSC.DefaultImagePath; + this.selected_point = undefined; + this.allow_mouse_wheel_zoom = true; + this.message_timeouts = { progress: 500, nodata: 500, info: 500, error: 2000 }; + this.allow_move = true; + this.auto_resize = true; + this.legend_title = EJSC.STRINGS["chart_legend_title"]; + // JHM: 2008-08-16 - Added legend_state property + this.legend_state = "normal"; // Values: "normal", "minimized" + + this.background = { + color: "rgb(255,255,255)", + opacity: 0, + includeTitle: false + }; + + // TO BE DEPRECATED/REPLACED IN A FUTURE VERSION? + this.auto_find_point_by_x = false; + this.selected_point = undefined; + + // Copy options + this.__init(options); + }, + + // -------------------------------------------------------------------------- + // CLASS: Inheritable + // -------------------------------------------------------------------------- + Inheritable: { + // -------------------------------------------------------------------------- + // FUNCTION: __extendTo + // -------------------------------------------------------------------------- + // Copies all parameters of parent class to [[child]] class. + // -------------------------------------------------------------------------- + __extendTo: function(child, ignorePrototype) { + for (var p in this) { + // JHM: 2008-09-18 - Added check for p in Object.prototype to account for base object extensions + if (p in Object.prototype) continue; + if (child[p] == undefined) { + child[p] = this[p]; + } + } + if (child.prototype && (ignorePrototype == undefined || ignorePrototype == false)) { + this.__extendTo(child.prototype); + } + }, + // -------------------------------------------------------------------------- + // FUNCTION: __copyOptions + // -------------------------------------------------------------------------- + // Copies all user-defined [[options]] into class. + // -------------------------------------------------------------------------- + __copyOptions: function(options) { + for(var p in options) { + // JHM: 2008-09-18 - Added check for p in Object.prototype to account for base object extensions + if (p in Object.prototype) continue; + if ((options[p] instanceof Object) && (typeof options[p] != "string") && + (typeof options[p] != "number") && (typeof options[p] != "boolean") && + !(options[p] instanceof Array) && + ((options[p].constructor.toString().match(/function Object\(\)/) != null) || + (options[p].constructor.toString().match(/\[function\]/) != null && + options[p].toString() == "[object Object]"))) { + if (this[p] == undefined || this[p] == null) { + this[p] = {}; + } + EJSC.Inheritable.__extendTo(this[p]); + this[p].__copyOptions(options[p]); + } else { + this[p] = options[p]; + } + } + } + }, + // -------------------------------------------------------------------------- + // CLASS: __Formatter + // -------------------------------------------------------------------------- + __Formatter: function() { + // Inherit utility methods + EJSC.Inheritable.__extendTo(this); + this.__type = 'generic'; + this.format_string = undefined; + this.format = function( value , roundVal ) { + if (roundVal != undefined && roundVal >= 0) { + // JHM: 2007-11-11 - Modified to use cached math functions + var power = m_POW(10,roundVal); + return m_ROUND( value * power ) / power; + } else { + return value; + } + }; + }, + // -------------------------------------------------------------------------- + // CLASS: __Series + // -------------------------------------------------------------------------- + __Series: { + // Private variables + __owner: null, +/* JGD - 2011-03-07 - Added */ + __canSelectPoints: true, + __index: undefined, + __uniqueIndex: 0, + __hasData: false, + __color: undefined, + __needsXAxis: true, + __needsYAxis: true, + __type: 'undefined', // JHM: 2007-04-16 - Added __type to base class in order to set default 'undefined' + // JHM: 2007-04-16 - Added __legend and __legend_visible to base class in order to facilitate legend + // handling routines + __legend: undefined, + __legend_icon_container: undefined, + __legend_visible: undefined, + __legend_hover: undefined, + __legend_caption: undefined, + __legend_icon: undefined, + __legend_visibility_container: undefined, + __defaultHintString: "[series_title]", + __drawing: false, + __padding: { + x_min: 0, + x_max: 0, + y_min: 0, + y_max: 0 + }, + // JHM: 2007-08-22 - Added __doFree, override in descendants to implement additional cleanup + __doFree: function() { }, + __doDraw: function() { }, + __doUnselectSeries: function() { }, + // JHM - 2007-05-10 - added to support setDataHandler functionality + __doSetDataHandler: function(handler, reload) { }, + __doGetHintString: null, + __getHintString: function(point) { + var result = (this.hint_string == undefined)?this.__defaultHintString:this.hint_string; + if (this.__doGetHintString != undefined) { + result = this.__doGetHintString(point); + } + if (this.onShowHint != undefined) { + result = this.onShowHint(point, this, this.__getChart(), result); + } + return result; + }, + // JHM: 2007-05-18 - Added __doOnDataAvailable and associated handling to base series + __doOnDataAvailable: function() { }, + __dataAvailable: function(data) { + + if (this.__doOnDataAvailable) { + this.__doOnDataAvailable(data); + } + + // JHM: 2007-12-01 - Added support for auto sort + if (this.autosort && this.__doAutoSort != undefined) { + this.__doAutoSort(); + } + // JHM: 2008-01-10 - Modified to call __resetExtremes which will reset and recalculate if necessary + this.__resetExtremes(); + + // JGD: Updated to call reference function + var chart = this.__getChart(); + + // Tell graph to recalc extremes with new series data + // JHM: 2007-05-10 - force extreme complete recalc (reset = true) + chart.__calculateExtremes(true); + + if (this.__getHasData() && this.onAfterDataAvailable) { + if (this.onAfterDataAvailable(chart, this) == false) { + return; + } + } + + // JHM: 2007-05-18 - Added check for parent chart's draw_series_on_load flag + // if false, don't call draw method + if (chart.__draw_series_on_load) { + + // Attempt draw again + chart.__draw(true); + + } + + }, + // JHM: 2007-05-10 - added + __resetExtremes: function() { + if (this.__doResetExtremes != undefined) { + this.__doResetExtremes(); + } + if (this.__doCalculateExtremes != undefined) { + this.__doCalculateExtremes(); + } + }, + // JHM: 2007-04-16 - Moved unique index acquisition to base class constructor, + // removed from individual series + __getUniqueIndex: function() { + if (this.__uniqueIndex == 0) { + this.__uniqueIndex = EJSC.__getUniqueSeriesIndex(); + } + return this.__uniqueIndex; + }, + // JHM: 2007-08-22 - Added __free to base series class and implemented calling __doFree for additional series specific cleanup + __free: function() { + // Remove legend references to prevent leaks + this.__legendRemove(); + if (this.__doFree != undefined) { + this.__doFree(); + } + }, + // JGD: 2007-07-30 + // Added series __legendRemove function + __legendRemove: function() { + // JGD: Added check for __legend + if( this.__legend != undefined ) { + + var e, i; + for (i = 0; i < EJSC.__events.length; i++) { + e = EJSC.__events[i]; + if (e != null) { + if (e[4] == this) { + EJSC.utility.__detachEvent( e[0] , e[1] , e[2] , e[3] ); + delete e; + EJSC.__events[i] = null; + } + } + } + + this.__legend.parentNode.removeChild( this.__legend ); + this.__legend_visible = null; + this.__legend_hover = null; + this.__legend_caption = null; + this.__legend_icon = null; + this.__legend_icon_container = null; + this.__legend_visibility_container = null; + + this.__legend.innerHTML = ""; + this.__legend = null; + } + }, + // JHM: 2007-04-16 - Added __legendCreate in order to facilitate base legend handling routines + __doGetLegendIcon: undefined, + __legendCreate: function() { + + if (this.__legend == undefined) { + + var ui = this.__getUniqueIndex(); + var self = this; + + // JHM: 2007-04-17 - Added support for child series to specify a legend icon via doGetLegendIcon() + if (this.__legend_icon == undefined) { + if (this.__doGetLegendIcon != undefined) { this.__legend_icon = this.__doGetLegendIcon(); } + else { this.__legend_icon = "undefined"; } + } + + this.__legend = EJSC.utility.__createDOMArray( + ["div", { className: "ejsc-legend-series" + (this.legendIsVisible?"":" ejsc-hidden") }, + ["div", { + className: "ejsc-legend-series-div ejsc-legend-series-out", + id: ui, + __ref: self, + __refVar: "__legend_hover", + __events: { + mouseover: function() { self.__legend_hover.className = self.__legend_hover.className.replace(/out/,"over"); }, + mouseout: function() { self.__legend_hover.className = self.__legend_hover.className.replace(/over/,"out"); } + } + }, + ["div", { + className: 'ejsc-legend-series-caption', + innerHTML: this.title, + title: this.title, + __ref: self, + __refVar: "__legend_caption" + }], + ["div", { className: "ejsc-legend-series-icon", __ref: self, __refVar: "__legend_icon_container" }, + ["img", { + src: this.__getChart().image_path + "blank.gif", + // JHM: 2008-08-16 - Updated to correct non-secure warning in https session + className: this.__legend_icon, + __ref: self, + __refVar: "__legend_icon" + }] + ], + ["div", { className: ("ejsc-legend-series-visibility") + (this.__owner.allow_interactivity==true?"":" ejsc-hidden"), __ref: self, __refVar: "__legend_visibility_container" }, + ["div", { + className: (this.visible?"ejsc-legend-series-on":"ejsc-legend-series-off"), + __ref: self, + __refVar: "__legend_visible", + __events: { + click: function() { self.__changeVisibility(); } + } + }] + ] + ] + ] + ); + + } + + if (this.coloredLegend) { + this.__legend_caption.style.color = this.__getColor().hex; + } + + }, + __doSelectNextSeries: undefined, + __selectNextSeries: function(point) { + if (this.__doSelectNextSeries != undefined) { + return this.__doSelectNextSeries(point); + } else { + return true; + } + }, + __doSelectPreviousSeries: undefined, + __selectPreviousSeries: function(point) { + if (this.__doSelectPreviousSeries != undefined) { + return this.__doSelectPreviousSeries(point); + } else { + return true; + } + }, + // JHM: 2007-11-24 - Added __getColor method to handle support for rgb/hex + __getColor: function() { + if (this.__color == undefined) { + this.__color = EJSC.utility.__getColor(this.color); + } + return this.__color; + }, + // JHM: 2007-04-16 - Added __legendInsert in order to facilitate base legend handling routines + __legendInsert: function() { + if (this.__getChart() != null && this.__legend != undefined && this.__legend.inserted == undefined) { + this.__getChart().__el_legend_series.appendChild(this.__legend); + this.__legend.inserted = true; + } + }, + // JHM: 2007-04-16 - Moved __changeVisibility to base series class + __changeVisibility: function() { + // JHM: 2007-04-17 - Added to support more event handling and control by child classes + if (this.__doBeforeVisibilityChange && !this.__doBeforeVisibilityChange()) { return; } + // JHM: 2007-05-20 - Modified to send chart and series objects + if (this.onBeforeVisibilityChange && this.onBeforeVisibilityChange(this, this.__getChart()) == false) { return; } + // Change visibility icon and series' visibility state + if (this.__legend_visible.className == "ejsc-legend-series-on") { +/* JGD - 2011-03-04 - Added */ + if( this.__getChart().__hint_is_sticky == true && this.__getChart().selected_point.__owner == this ) + this.__getChart().__unselectPoint(); + this.__legend_visible.className = "ejsc-legend-series-off"; + this.visible = false; + } else { + this.__legend_visible.className = "ejsc-legend-series-on"; + this.visible = true; + // JHM: 2007-06-13 - Fixed issue with data not loading if series is initially invisible + if (!this.__getHasData()) { + this.reload(); + } + } + // JHM: 2007-04-17 - Added to support more event handling and control by child classes + if (this.__doAfterVisibilityChange) { this.__doAfterVisibilityChange(); } + // JHM: 2007-05-20 - Modified to send chart and series objects and accept boolean return value to cancel redraw + if (this.onAfterVisibilityChange && this.onAfterVisibilityChange(this, this.__getChart(), this.visible) == false) { return; } + + // Redraw chart + this.__getChart().__draw(true); + }, + // JHM: Added __changeLegendVisibility method + __changeLegendVisibility: function(visibility) { + this.legendIsVisible = visibility; + if (this.legendIsVisible) { + this.__legend.className = "ejsc-legend-series"; + } else { + this.__legend.className = "ejsc-legend-series ejsc-hidden"; + } + }, + // JHM: 2007-09-30 - Added __findClosestPoint method which calls (if defined) __doFindClosestPoint + // which should be implemented by all descendant classes. Returns a EJSC.Point descendant or null + // * Parameters: mouse ({x:,y:}) are pixel coordinates which must be converted if necessary, this + // behavior differs from the previous implementation to account for the support for + // multiple axis and the possibility for different scales for each series + __doFindClosestPoint: undefined, + __findClosestPoint: function(mouse, use_proximity) { + if (!this.visible || !this.__getHasData()) { return null; } + if (this.__doFindClosestPoint != undefined) { + return this.__doFindClosestPoint(mouse, (use_proximity==undefined?true:use_proximity)); + } else { + return null; + } + }, + __doSelectPoint: undefined, + __selectPoint: function(point, sticky) { + if (!this.visible || !this.__getHasData()) { return false; } + if (this.__doSelectPoint != undefined) { + return this.__doSelectPoint(point, sticky); + } else { + return null; + } + }, + __doSelectNext: undefined, + __selectNext: function(point) { + if (!this.visible || !this.__getHasData()) { return false; } + if (this.__doSelectNext != undefined) { + return this.__doSelectNext(point); + } else { + return null; + } + }, + __doSelectPrevious: undefined, + __selectPrevious: function(point) { + if (!this.visible || !this.__getHasData()) { return false; } + if (this.__doSelectPrevious != undefined) { + return this.__doSelectPrevious(point); + } else { + return null; + } + }, + __doGetYRange: undefined, + __getYRange: function(screenMinX, screenMaxX) { + if (!this.visible || !this.__getHasData()) { return false; } + if (this.__doGetYRange != undefined) { + return this.__doGetYRange(screenMinX, screenMaxX); + } else { + return null; + } + }, + __doGetXRange: undefined, + __getXRange: function(screenMinY, screenMaxY) { + if (!this.visible || !this.__getHasData()) { return false; } + if (this.__doGetXRange != undefined) { + return this.__doGetXRange(screenMinY, screenMaxY); + } else { + return null; + } + }, + __doPoint2Px: undefined, + __point2px: function(point) { + if (this.__doPoint2Px != undefined) { + return this.__doPoint2Px(point); + } else { + return null; + } + }, + // JHM: 2008-08-12 - Added private get padding methods + __getPadding: function(axis, side) { + if (this.padding[axis + "_axis_" + side] == undefined) { return this.__padding[axis + "_" + side]; } + else { return this.padding[axis + "_axis_" + side]; } + }, + // JGD: 2008-07-18 + // Added to fix errors with trend not knowing if it has data + __getHasData: function() { + return this.__hasData; + }, + // Public variables + // JHM: 2007-12-01 - Added autosort + autosort: true, + title: "", + x_axis_formatter: undefined, + y_axis_formatter: undefined, + // JHM: 2007-06-12 - Added coloredLegend property to allow for the legend text to inherit series color + coloredLegend: true, + // JHM: 2007-05-20 - Modified __color to be public property and default value to be undefined + color: undefined, + // JHM: Added opacity property + opacity: 50, + // JHM: 2007-05-20 - Modified __visible to be public property + visible: true, + // JHM: Moved lineWidth to base series class + lineWidth: 1, + // JHM: Added lineOpacity property + lineOpacity: 100, + // JHM: Added legendIsVisible + legendIsVisible: true, + hint_string: undefined, + x_axis: "bottom", + y_axis: "left", + // JHM: 2007-10-10 - Added delayLoad property + delayLoad: true, + // JHM: 2008-08-12 - Added public padding property + padding: { + x_axis_min: undefined, + x_axis_max: undefined, + y_axis_min: undefined, + y_axis_max: undefined + }, + // Public methods + // JHM: 2008-08-12 - Added get/setPadding methods + getPadding: function() { + return { + x_axis_min: this.__getPadding("x","min"), + x_axis_max: this.__getPadding("x","max"), + y_axis_min: this.__getPadding("y","min"), + y_axis_max: this.__getPadding("y","max") + } + }, + setPadding: function(padding, redraw) { + this.padding = padding; + this.__getChart().__calculateExtremes(true); + if (redraw == undefined || redraw == true) { + this.__getChart().__draw(true); + } + }, + show: function() { + if (!this.visible) { + this.__changeVisibility(); + } + }, + hide: function() { + if (this.visible) { + this.__changeVisibility(); + } + }, + getVisibility: function() { + return this.visible; + }, + // JGD: 2007-04-30 - Added reload function to base series + reload: function() { +/* JGD - 2011-02-28 - Fixed to unselect point before reloading */ + if( this.__getChart().__hint_is_sticky == true && this.__getChart().selected_point.__owner == this ) + this.__getChart().__unselectPoint(); + if( this.__doReload ) { + this.__doReload(); + } + }, + // JHM - 2007-05-10 - added setDataHandler functionality + setDataHandler: function(handler, reload) { + if (this.__doSetDataHandler) { + this.__doSetDataHandler(handler, reload); + } + }, + // JHM: 2007-06-12 - Added getDataHandler method + getDataHandler: function() { + if (this.__dataHandler == undefined) { return null; } + else { return this.__dataHandler; } + }, + // JHM: 2007-05-20 - Added setColor method + setColor: function(color) { + // save the new color + this.color = color; + this.__color = EJSC.utility.__getColor(this.color); + + // JHM: 2007-06-12 - Modified to modify series title text to match series color + if (this.coloredLegend) { + this.__legend_caption.style.color = this.__color.hex; + } else { + this.__legend_caption.style.color = ""; + } + + // redraw the chart + this.__getChart().__draw(true); + }, + // JHM: 2007-06-12 - Added setColoredLegend method + setColoredLegend: function(coloredLegend) { + this.coloredLegend = coloredLegend; + if (this.coloredLegend) { + this.__legend_caption.style.color = this.__getColor().hex; + } else { + this.__legend_caption.style.color = ""; + } + }, + // JHM: added setOpacity method + setOpacity: function(opacity) { + this.opacity = opacity; + this.__getChart().__draw(true); + }, + // JHM: Added setLineOpacity method + setLineOpacity: function(opacity) { + this.lineOpacity = opacity; + this.__getChart().__draw(true); + }, + // JHM: Moved setLineWidth to base series class + setLineWidth: function( width ) { + this.lineWidth = width; + this.__getChart().__draw(true); + }, + // JHM: Added showLegend method + showLegend: function() { + this.__changeLegendVisibility(true); + }, + // JHM: Added hideLegend method + hideLegend: function() { + this.__changeLegendVisibility(false); + }, + // JHM: 2007-06-12 - Added setTitle() + setTitle: function(title) { + // save the new title + this.title = title; + // update the legend + this.__legend_caption.innerHTML = this.title; + this.__legend_caption.title = this.title; + }, + __doFindClosestByPoint: undefined, + findClosestByPoint: function(point) { + if (this.__doFindClosestByPoint != undefined) { + return this.__doFindClosestByPoint(point); + } else { + return null; + } + }, + __doFindClosestByPixel: undefined, + findClosestByPixel: function(point) { + if (this.__doFindClosestByPixel != undefined) { + return this.__doFindClosestByPixel(point); + } else { + return null; + } + }, + // Public Events + // JHM: 2007-04-17 - Added visibility events + onBeforeVisibilityChange: undefined, + onAfterVisibilityChange: undefined, + // JGD: 2007-05-08 - Added base onAfterDataAvailable to base series class + onAfterDataAvailable: undefined, + onShowHint: undefined, + // Protected Methods + __doBeforeVisibilityChange: null, + __doAfterVisibilityChange: null, + // JGD: Added private functions to get references to __owner and chart (switch to this.__owner.__owner etc. for layered classes) + __getOwner: function() { + return this.__owner; + } , + __getChart: function() { + return this.__owner; + }, + __getDrawArea: function() { + return this.__getChart().__getDrawArea(); + } + }, + // -------------------------------------------------------------------------- + // CLASS: __Axis + // -------------------------------------------------------------------------- + __Axis: { + // Private properties + __owner: undefined, + __orientation: undefined, // valid values: "h","v".. used with __side property + __side: undefined, // valid values: "left","right","top","bottom" + __min_extreme: undefined, + __max_extreme: undefined, + __current_min: undefined, + __current_max: undefined, + __forced_min_extreme: undefined, + __forced_max_extreme: undefined, + __scale: undefined, + __increment: undefined, + __series: undefined, + __ticks: undefined, + __written: false, + __drawing: false, + __el: undefined, + __el_caption: undefined, + __el_labels: undefined, + __el_crosshair: undefined, + __el_cursor_position: undefined, + __el_cursor_position_marker: undefined, + __el_cursor_position_label: undefined, + __text_values: undefined, + __force_visible: false, + + // Private methods + __init: function(owner, orientation, side) { + + this.__owner = owner; + this.__orientation = orientation; + this.__side = side; + this.__padding = { + min: 0, + max: 0 + }; + this.__series = []; + this.__ticks = []; + this.__text_values = new EJSC.__TextLabels(); + this.border = { + thickness: 1, + color: undefined, + opacity: 100, + show: undefined + }; + this.major_ticks = { + thickness: 1, + size: 4, + color: undefined, + opacity: 100, + show: true, + count: undefined, + offset: 0, + min_interval: undefined, + max_interval: undefined + }; + this.minor_ticks = { + thickness: 1, + size: 4, + color: "#000", + opacity: 20, + show: false, + count: 7, + offset: 0 + }; + this.grid = { + thickness: 1, + color: "rgb(230,230,230)", + opacity: 100, + show: true + }; + this.zero_plane = { + show: false, + color: "#000", + opacity: 100, + thickness: 1, + coordinate: 0 + }; + this.background = { + color: "#fff", + opacity: 0, + includeTitle: false + }; + this.crosshair = { + show: false, + color: "#F00" + }; + this.cursor_position = { + show: false, + color: "#F00", + textColor: "#FFF", + formatter: undefined, + caption: undefined, + className: undefined + }; + // JHM: 2008-06-30 - Added padding property to axis to override series specific padding + this.padding = undefined; + this.formatter = new EJSC.NumberFormatter( { variable_decimals: 8 } ); + //this.formatter = new EJSC.__Formatter(); + + this.__copyOptions(this.__options.__chart_options); + this.__copyOptions(this.__options); + + // Handle extreme values + if (this.min_extreme != undefined) { this.__forced_min_extreme = this.min_extreme; } + if (this.max_extreme != undefined) { this.__forced_max_extreme = this.max_extreme; } + + // Validate __orientation/__side combination and configure defaults + switch (this.__orientation) { + case "h": + if (this.__side.match(/^(top|bottom)$/) == null) { + console.log("EJSChart: Invalid axis orientation/side combination: " + this.__orientation + "/" + this.__side); + } else { + if (this.size == undefined) this.size = 20; + } + break; + case "v": + if (this.__side.match(/^(left|right)$/) == null) { + console.log("EJSChart: Invalid axis orientation/side combination: " + this.__orientation + "/" + this.__side); + } else { + if (this.size == undefined) this.size = 50; + } + break; + default: + console.log("EJSChart: Invalid axis orientation: " + this.__orientation); + } + }, + // JHM: 2008-06-05 - Added support for drawing axes by setting manual extremes without the requirement of adding series + __hasManualExtremes: function() { + return (this.__forced_min_extreme != undefined && this.__forced_max_extreme != undefined); + }, + __getPhysicalSize: function() { + return parseInt(this.size) + 20; + }, + __getCaption: function() { + function getTransformProperty(element) { + var properties = [ 'transform', 'WebkitTransform', 'MozTransform', 'msTransform', 'OTransform' ]; + var p; + while (p = properties.shift()) { + if (typeof element.style[p] != 'undefined') + return p; + } + return false; + }; + if (this.caption == undefined) { this.caption = "Axis Caption"; } + if (this.__orientation == "h" || EJSC.__isIE || getTransformProperty(document.body) ) { return '' + this.caption + ''; } + else { return '' + this.caption.split("").join("
") + '
'; } + }, + __canDraw: function() { + return ((this.visible == true && this.__series.length > 0) || + (this.__force_visible == true && this.__hasManualExtremes())); + }, + // Private events and event handlers + __doDraw: undefined, + __draw: function(ctx) { + if (this.__owner == undefined) { return false; } + + if (!this.__written) { + this.__write(); + this.__written = true; + } + + if (this.__scale == undefined || isNaN(this.__scale)) { + this.__calculateScale(true, true); + } + + if (this.__doDraw != undefined && this.__drawing == false) { + + try { + this.__drawing = true; + + // Draw background + var c, o, it, da, dr; + c = this.background.color; + o = this.background.opacity; + it = this.background.includeTitle; + if (o > 0 && this.__canDraw()) { + da = this.__owner.__draw_area; + dr = { + left: (this.__orientation=="h"?da.left:(this.__side=="left"?(it?da.left-this.__getPhysicalSize():da.left-this.size):da.right)), + top: (this.__orientation=="v"?da.top:(this.__side=="top"?(it?da.top-this.__getPhysicalSize():da.top-this.size):da.bottom)), + right: (this.__orientation=="h"?da.right:(this.__side=="left"?da.left:(it?da.right+this.__getPhysicalSize():da.right+this.size))), + bottom: (this.__orientation=="v"?da.bottom:(this.__side=="top"?da.top:(it?da.bottom+this.__getPhysicalSize():da.bottom+this.size))) + }; + ctx.lineWidth = 0; + ctx.fillStyle = EJSC.utility.__getColor(c, o / 100).rgba; + ctx.beginPath(); + ctx.moveTo(dr.left, dr.top); + ctx.lineTo(dr.right, dr.top); + ctx.lineTo(dr.right, dr.bottom); + ctx.lineTo(dr.left, dr.bottom); + ctx.lineTo(dr.left, dr.top); + ctx.fill(); + } + + this.__doDraw(ctx); + } catch (e) { + } finally { + this.__drawing = false; + } + } + }, + __doDrawZeroPlane: undefined, + __drawZeroPlane: function(ctx) { + if (this.__owner == undefined) { return false; } + if (this.__owner.__draw_area == undefined) { return false; } + if (this.zero_plane.show == false) { return false; } + + if (!this.__written) { + this.__write(); + this.__written = true; + } + + if (this.__doDrawZeroPlane != undefined && this.__drawing == false) { + + // Skip draw if there are no valid series and __force_visible != true + if (!this.__canDraw()) { return; } + + try { + this.__drawing = true; + this.__doDrawZeroPlane(ctx); + } catch (e) { + } finally { + this.__drawing = false; + } + + } else { + + var coord = this.__getZeroPlaneCoordinate(); + var area = this.__owner.__draw_area; + + if (coord >= this.__current_min && coord <= this.__current_max) { + + ctx.lineWidth = this.zero_plane.thickness; + ctx.strokeStyle = EJSC.utility.__getColor(this.zero_plane.color, this.zero_plane.opacity / 100).rgba; + + ctx.beginPath(); + + if (this.__orientation == "v") { + ctx.moveTo(area.left, this.__pt2px(coord)); + ctx.lineTo(area.right, this.__pt2px(coord)); + } else { + ctx.moveTo(this.__pt2px(coord), area.top); + ctx.lineTo(this.__pt2px(coord), area.bottom); + } + + ctx.stroke(); + + } + + } + + }, + __doGetZeroPlaneCoordinate: undefined, + __getZeroPlaneCoordinate: function() { + if (this.__doGetZeroPlaneCoordinate != undefined) { + return this.__doGetZeroPlaneCoordinate(); + } else { + return (this.zero_plane.coordinate == undefined)?0:this.zero_plane.coordinate; + } + }, + __doWrite: undefined, + __write: function() { + if (this.__doWrite != undefined) { + if (this.__doWrite() == false) return false; + } + var self = this; + this.__el = EJSC.utility.__createDOMArray( + ["span", { + className: "ejsc-" + this.__orientation + "-axis-labels ejsc-" + this.__side + "-axis-labels ejsc-hidden", + __ref: self, + __refVar: "__el" + }, + ["span", { + className: "ejsc-" + this.__orientation + "-axis-caption ejsc-" + this.__side + "-axis-caption" + (this.caption_class!=undefined?" " + this.caption_class:""), + innerHTML: this.__getCaption(), + __ref: self, + __refVar: "__el_caption", + __styles: { + filter: ( EJSC.__isIE ? (this.__orientation=="v"?(this.__side=="left"?"flipV flipH":"flipV flipV"):"") : "" ) , + writingMode: ( EJSC.__isIE ? (this.__orientation=="v"?'tb-rl':'') : '' ) + } + }], + ["span", { + className: "ejsc-" + this.__orientation + "-axis-labels", + __ref: self, + __refVar: "__el_labels" + }, + ["span", { className: "ejsc-" + this.__orientation + "-label ejsc-invisible" + (this.label_class!=undefined?" " + this.label_class:"") }], + ["span", { className: "ejsc-" + this.__orientation + "-label ejsc-invisible" + (this.label_class!=undefined?" " + this.label_class:"") }], + ["span", { className: "ejsc-" + this.__orientation + "-label ejsc-invisible" + (this.label_class!=undefined?" " + this.label_class:"") }], + ["span", { className: "ejsc-" + this.__orientation + "-label ejsc-invisible" + (this.label_class!=undefined?" " + this.label_class:"") }], + ["span", { className: "ejsc-" + this.__orientation + "-label ejsc-invisible" + (this.label_class!=undefined?" " + this.label_class:"") }], + ["span", { className: "ejsc-" + this.__orientation + "-label ejsc-invisible" + (this.label_class!=undefined?" " + this.label_class:"") }], + ["span", { className: "ejsc-" + this.__orientation + "-label ejsc-invisible" + (this.label_class!=undefined?" " + this.label_class:"") }], + ["span", { className: "ejsc-" + this.__orientation + "-label ejsc-invisible" + (this.label_class!=undefined?" " + this.label_class:"") }], + ["span", { className: "ejsc-" + this.__orientation + "-label ejsc-invisible" + (this.label_class!=undefined?" " + this.label_class:"") }], + ["span", { className: "ejsc-" + this.__orientation + "-label ejsc-invisible" + (this.label_class!=undefined?" " + this.label_class:"") }], + ["span", { className: "ejsc-" + this.__orientation + "-label ejsc-invisible" + (this.label_class!=undefined?" " + this.label_class:"") }], + ["span", { className: "ejsc-" + this.__orientation + "-label ejsc-invisible" + (this.label_class!=undefined?" " + this.label_class:"") }] + ], + ["div", { + className: "ejsc-cursor-position ejsc-cursor-position-" + this.__orientation + " ejsc-hidden", + __ref: self, + __refVar: "__el_cursor_position" + }, + ["div", { + className: "ejsc-cursor-position-marker ejsc-cursor-position-marker-" + this.__side, + __ref: self, + __refVar: "__el_cursor_position_marker", + __styles: { + backgroundColor: EJSC.utility.__getColor(this.cursor_position.color).hex + } + }], + ["span", { + className: "ejsc-cursor-position-label ejsc-cursor-position-label-" + this.__side + (this.cursor_position.className!=undefined?" " + this.cursor_position.className:""), + __ref: self, + __refVar: "__el_cursor_position_label", + __styles: { + backgroundColor: EJSC.utility.__getColor(this.cursor_position.color).hex, + color: EJSC.utility.__getColor(this.cursor_position.textColor).hex + } + }] + ] + ] + ); + this.__owner.__el_labels.appendChild(this.__el); + + this.__owner.__el_series_canvas_container.appendChild( + EJSC.utility.__createDOMArray( + ["div", { + className: "ejsc-" + this.__orientation + "-crosshair", + __ref: self, + __refVar: "__el_crosshair", + __styles: { + backgroundColor: EJSC.utility.__getColor(this.crosshair.color).hex + } + }] + ) + ); + }, + // JHM: 2008-09-23 - Added __free method to remove memory leaks + __free: function() { + this.__el_crosshair = null; + this.__el_cursor_position_label = null; + this.__el_cursor_position_marker = null; + this.__el_cursor_position = null; + this.__el_labels = null; + this.__el_caption = null; + this.__el = null; + + var e, i; + for (i = 0; i < EJSC.__events.length; i++) { + e = EJSC.__events[i]; + if (e != null) { + if (e[4] == this) { + EJSC.utility.__detachEvent( e[0] , e[1] , e[2] , e[3] ); + delete e; + EJSC.__events[i] = null; + } + } + } + }, + __adjustLabelContainer_v: function(area) { + // JHM: 2008-05-21 - Validate element this.__el + if (this.__el == null) { return; } + + // Adjust label container size + this.__el.style.top = area.top + "px"; + this.__el.style[this.__side] = "0px"; + this.__el.style.width = this.__getPhysicalSize() + "px"; + this.__el.style.height = area.height + "px"; + this.__el.style.display = (this.visible?"block":"none"); + }, + __adjustLabelContainer_h: function(area) { + // JHM: 2008-05-21 - Validate element this.__el + if (this.__el == null) { return; } + + // Adjust label container size + if (this.__side == "top") { + this.__el.style.top = area.top - this.__getPhysicalSize() + "px"; + } else { + this.__el.style.top = area.bottom + "px"; + } + this.__el.style.left = area.left + "px"; + this.__el.style.width = area.width + "px"; + this.__el.style.height = this.__getPhysicalSize() + "px"; + this.__el.style.display = (this.visible?"block":"none"); + }, + __adjustCaptionPosition_v: function(area) { + // Adjust caption position + this.__el_caption.style[this.__side] = "0px"; + var w = this.__el_caption.firstChild.offsetWidth; + var h = this.__el_caption.firstChild.offsetHeight; + var s = EJSC.__isIE ? h : w-16; + if( EJSC.__isIE ) + this.__el_caption.style.top = m_FLOOR((area.height - s) / 2) + "px"; + else + this.__el_caption.style[this.__side] = m_FLOOR((-s) / 2) + "px"; + }, + __adjustCaptionPosition_h: function(area) { + // Adjust caption position + this.__el_caption.style.left = m_FLOOR((area.width - this.__el_caption.offsetWidth) / 2) + "px"; + this.__el_caption.style[this.__side] = "0px"; + }, + __drawBorder_v: function(area, ctx) { + // Draw border + if (this.border.show == true || (this.visible == true && this.border.show == undefined)) { + + ctx.beginPath(); + + ctx.lineWidth = this.border.thickness; + ctx.strokeStyle = EJSC.utility.__getColor((this.border.color==undefined?this.color:this.border.color), this.border.opacity / 100).rgba; + + ctx.moveTo(area[this.__side], area.top); + ctx.lineTo(area[this.__side], area.top + area.height); + ctx.stroke(); + + } + }, + __drawBorder_h: function(area, ctx) { + // Draw border + if (this.border.show == true || (this.visible == true && this.border.show == undefined)) { + + // Setup canvas + ctx.beginPath(); + + ctx.lineWidth = this.border.thickness; + ctx.strokeStyle = EJSC.utility.__getColor((this.border.color==undefined?this.color:this.border.color), this.border.opacity / 100).rgba; + + ctx.moveTo(area.left, area[this.__side]); + ctx.lineTo(area.left + area.width, area[this.__side]); + ctx.stroke(); + + } + }, + __doGetRound: undefined, + __getLabel: function(value, round, formatter) { + if (round == undefined && this.__doGetRound != undefined) { + round = this.__doGetRound(value); + } + if (this.__text_values.__count() > 0) { return this.__text_values.__get(value); } + else if (formatter != undefined) { return formatter.format(value, round); } + else { return this.formatter.format(value, round); } + }, + __doAddSeries: undefined, + __addSeries: function(series) { + if (this.__doAddSeries != undefined) { + if (this.__doAddSeries(series) == false) { return false; } + } + this.__series.push(series); + }, + __doRemoveSeries: undefined, + __removeSeries: function(series) { + if (this.__doRemoveSeries != undefined) { + if (this.__doRemoveSeries(series) == false) { return false; } + } + + // Unregister text labels if necessary + // JGD : 2008-07-30 - Fixed to allow for container series types + if (this.__text_values && series.__points != undefined) { + for (i = 0; i < series.__points.length; i++) { + if (this.__orientation == "h" && series.__points[i].__x_label != undefined) { + this.__text_values.__remove(series.__points[i].__x_label.__label); + } else if (series.__points[i].__y_label != undefined) { + this.__text_values.__remove(series.__points[i].__y_label.__label); + } + } + } + + for (var i = 0; i < this.__series.length; i++) { + if (this.__series[i] == series) { + this.__series.splice(i,1); + } + } + + this.__calculateExtremes(true); + }, + __doCalculateExtremes: undefined, + __calculateExtremes: function(reset) { + + if (this.__owner == undefined) { return false; } + if (this.__doCalculateExtremes != undefined) { + if (this.__doCalculateExtremes(reset) == false) { return false; } + } + if (reset) { + this.__min_extreme = undefined; + this.__max_extreme = undefined; + this.__current_min = undefined; + this.__current_max = undefined; + } + var min = isNaN(this.__min_extreme)?undefined:this.__min_extreme; + var max = isNaN(this.__max_extreme)?undefined:this.__max_extreme; + var i = 0; + var series = this.__series; + var padding = { + min: 0, + max: 0 + }; + for(; i < series.length; i++ ) { + if (!series[i].__getHasData() || !series[i].visible) continue; + if (this.__orientation == "h") { + if (min == undefined || series[i].__minX < min) min = series[i].__minX; + if (max == undefined || series[i].__maxX > max) max = series[i].__maxX; + if (series[i].__getPadding("x","min") > padding.min) padding.min = series[i].__getPadding("x","min"); + if (series[i].__getPadding("x","max") > padding.max) padding.max = series[i].__getPadding("x","max"); + } else { + if (min == undefined || series[i].__minY < min) min = series[i].__minY; + if (max == undefined || series[i].__maxY > max) max = series[i].__maxY; + if (series[i].__getPadding("y","min") > padding.min) padding.min = series[i].__getPadding("y","min"); + if (series[i].__getPadding("y","max") > padding.max) padding.max = series[i].__getPadding("y","max"); + } + } + + if (min == undefined || max == undefined) { + if (this.__hasManualExtremes()) { + min = this.__forced_min_extreme; + max = this.__forced_max_extreme; + } else { + return; + } + } + + if (this.__text_values.__count() > 0) { + if (min > 1) { min = 1; } + if (max < (this.__text_values.__count())) { + max = this.__text_values.__count(); + } + } + + if (min == max) { + if (min == 0) { + min = -0.01; + max = 0.01; + } else { + min -= (m_ABS(min) * 0.01); + max += (m_ABS(max) * 0.01); + } + } + + this.__data_extremes = { min_extreme: min, max_extreme: max }; + + this.__padding = { min: (this.padding == undefined || this.padding.min == undefined?padding.min:this.padding.min), max: (this.padding == undefined || this.padding.max == undefined?padding.max:this.padding.max) }; + + if (this.__forced_min_extreme != undefined) min = this.__forced_min_extreme; + if (this.__forced_max_extreme != undefined) max = this.__forced_max_extreme; + + // If extremes have changed, record new extremes, recalculate scale + // and apply padding + if (this.__min_extreme != min || this.__max_extreme != max) { + + // Save new extremes + this.__min_extreme = min; + this.__max_extreme = max; + + if (this.__current_min == undefined || isNaN(this.__current_min)) { this.__current_min = min; } + if (this.__current_max == undefined || isNaN(this.__current_max)) { this.__current_max = max; } + + this.__calculateScale(true, true); + } + + }, + __doCalculateScale: undefined, + __calculateScale: function(calculatePadding, generateTicks) { + + if (this.__owner == undefined) { return false; } + if (this.__owner.__draw_area == undefined) { return false; } + + if (this.__doCalculateScale != undefined) { + if (this.__doCalculateScale(calculatePadding, generateTicks) == false) { return false; } + } + + if (generateTicks == undefined || generateTicks == true) { + this.__generateTicks(); + } + + }, + __doGetZoomBoxCoordinates: undefined, + __getZoomBoxCoordinates: function() { + var result = { min: null, max: null }; + if (this.__owner != undefined) { + var zoombox = this.__owner.__el_zoombox; + result.min = this.__px2pt(this.__orientation=="h"?zoombox.offsetLeft:zoombox.offsetTop + zoombox.offsetHeight); + result.max = this.__px2pt(this.__orientation=="h"?zoombox.offsetLeft + zoombox.offsetWidth:zoombox.offsetTop); + } + return result; + }, + __doGenerateTicks: undefined, + __generateTicks: function() { + if (!this.__written) { + this.__write(); + this.__written = true; + } + if (this.__doGenerateTicks != undefined) { + if (this.__doGenerateTicks() == false) { return false; } + } + }, + __doPt2Px: undefined, + __pt2px: function(point, ignoreBounds) { + if (this.__doPt2Px != undefined) { + return this.__doPt2Px(point); + } + }, + __doPx2Pt: undefined, + __px2pt: function(point, ignoreBounds) { + if (this.__doPx2Pt != undefined) { + return this.__doPx2Pt(point); + } + }, + __doShowCursorPosition: undefined, + __showCursorPosition: function(point) { + if (!this.__written || !this.visible) { return false; } + if (this.__owner.__draw_area == undefined) { return false; } + + if (this.__doShowCursorPosition != undefined) { + if (this.__doShowCursorPosition(point) == false) { return false; } + } + + if (this.cursor_position.show && this.__owner.allow_interactivity) { + if (this.onShowCursorPosition != undefined) { + if (this.onShowCursorPosition(this.__px2pt(point[(this.__orientation=="v"?"y":"x")] + this.__owner.__draw_area[(this.__orientation=="v"?"top":"left")]), this, this.__owner) == false) { return false; } + } + + var da = this.__owner.__draw_area; + var value = this.__px2pt(this.__orientation=="h"?point.x+da.left:point.y+da.top); + if (isNaN(value)) { return false; } + + var label = this.__getLabel(value, (this.__doGetRound != undefined?this.__doGetRound(value, 3):undefined), this.cursor_position.formatter); + if (this.cursor_position.caption != undefined) { + label = this.cursor_position.caption + " " + label; + } + this.__el_cursor_position.style.display = "block"; + this.__el_cursor_position_label.innerHTML = label; + if (this.__orientation == "h") { + this.__el_cursor_position_marker.style.left = point.x - 1 + "px"; + this.__el_cursor_position_label.style.left = point.x - (m_ROUND(this.__el_cursor_position_label.offsetWidth / 2)) + "px"; + } else { + this.__el_cursor_position_marker.style.top = point.y - 1 + "px"; + this.__el_cursor_position_label.style.top = point.y - (m_ROUND(this.__el_cursor_position_label.offsetHeight / 2)) + "px"; + } + } + }, + __doHideCursorPosition: undefined, + __hideCursorPosition: function() { + if (!this.__written) { return false; } + + if (this.__doHideCursorPosition != undefined) { + if (this.__doHideCursorPosition() == false) { return false; } + } + + if (this.cursor_position.show && this.__owner.allow_interactivity) { + if (this.onHideCursorPosition != undefined) { + if (this.onHideCursorPosition(this, this.__owner) == false) { return false; } + } + this.__el_cursor_position.style.display = "none"; + } + }, + __doShowCrosshair: undefined, + // JHM: Updated to include fireEvent property + __showCrosshair: function(point, fireEvent) { + if (!this.__written) { return false; } + + if (this.__doShowCrosshair != undefined) { + if (this.__doShowCrosshair(point) == false) { return false; } + } + + if (this.crosshair.show && this.__owner.allow_interactivity) { + if (this.onShowCrosshair != undefined && (fireEvent == undefined || fireEvent == true)) { + if (this.onShowCrosshair(this.__px2pt(point[(this.__orientation=="v"?"y":"x")] + this.__owner.__draw_area[(this.__orientation=="v"?"top":"left")]), this, this.__owner) == false) { return false; } + } + + // JHM: 2008-08-17 - Added adjustment to correctly position crosshairs exactly under the cursor + if (this.__orientation == "v") { + this.__el_crosshair.style.top = point.y + (EJSC.__isIE?-1:1) + "px"; + } else { + this.__el_crosshair.style.left = point.x + (EJSC.__isIE?-1:1) + "px"; + } + this.__el_crosshair.style.display = "block"; + } + }, + __doHideCrosshair: undefined, + // JHM: Updated to include fireEvent property + __hideCrosshair: function(fireEvent) { + if (!this.__written) { return; } + + if (this.__doHideCrosshair != undefined) { + if (this.__doHideCrosshair() == false) { return false; } + } + + if (this.crosshair.show) { + if (this.onHideCrosshair != undefined && (fireEvent == undefined || fireEvent == true)) { + if (this.onHideCrosshair(this, this.__owner) == false) { return false; } + } + this.__el_crosshair.style.display = "none"; + } + }, + __doMouseDown: undefined, + __mouseDown: function(point, right_click) { + if (this.__doMouseDown != undefined) { + if (this.__doMouseDown(point, right_click) == false) { return false; } + } + }, + __doMouseMove: undefined, + __mouseMove: function(point) { + if (this.__doMouseMove != undefined) { + if (this.__doMouseMove(point) == false) { return false; } + } + if (this.crosshair.show) { + this.__showCrosshair(point); + } + if (this.cursor_position.show) { + this.__showCursorPosition(point); + } + }, + __doMouseUp: undefined, + __mouseUp: function(point, right_click) { + if (this.__doMouseUp != undefined) { + if (this.__doMouseUp(point, right_click) == false) { return false; } + } + }, + __doMouseEnter: undefined, + __mouseEnter: function(point) { + if (this.__doMouseEnter != undefined) { + if (this.__doMouseEnter(point) == false) { return false; } + } + if (this.crosshair.show) { + this.__showCrosshair(point); + } + if (this.cursor_position.show) { + this.__showCursorPosition(point); + } + }, + __doMouseLeave: undefined, + __mouseLeave: function() { + if (this.__doMouseLeave != undefined) { + if (this.__doMouseLeave() == false) { return false; } + } + if (this.crosshair.show) { + this.__hideCrosshair(); + } + if (this.cursor_position.show) { + this.__hideCursorPosition(); + } + }, + __doMove: undefined, + __move: function(start, end) { + // JHM: 2008-06-05 - Fixed moving when manual extremes are set but there are no series + if (this.__series.length == 0 && !this.__hasManualExtremes) { return true; } + if (this.__doMove != undefined) { + if (this.__doMove(start, end) == false) { return false; } + } + }, + __doZoom: undefined, + __zoom: function(start, end) { + var result = false; + // JHM: 2008-06-05 - Fixed zooming when manual extremes are set but there are no series + if (this.__series.length > 0 || this.__hasManualExtremes() && this.__doZoom != undefined) { + result = this.__doZoom(start, end); + } + return result; + }, + __doResetZoom: undefined, + __resetZoom: function() { + if (this.__doResetZoom != undefined) { + this.__doResetZoom(); + } + }, + __getChart: function() { + return this.__owner; + }, + __getHintCaption: function() { + if (this.hint_caption == undefined) { + return (this.caption==""?"":this.caption + ":"); + } else { + return this.hint_caption; + } + }, + // Public properties + color: "#ccc", + caption: undefined, + formatter: undefined, + hint_caption: undefined, + visible: true, + size: undefined, // h = 20, v = 50 + caption_class: undefined, + label_class: undefined, + stagger_ticks: true, + extremes_ticks: false, + force_static_points: false, + // Public methods + addBin: function(label, redraw) { + var bin = this.__text_values.__add(label, true); + if (bin.__usage == 1) { + bin.__usage = 0; + } + bin.__manual = true; + if (redraw == undefined || redraw == true) { + for (var i = 0; i < this.__series.length; i++) { + this.__series[i].__resetExtremes(); + } + this.__owner.__draw(true); + } + }, + removeBin: function(label, redraw) { + var bin = this.__text_values.__find(label); + if (bin != null) { + this.__text_values.__remove(label, true); + if (redraw == undefined || redraw == true) { + for (var i = 0; i < this.__series.length; i++) { + this.__series[i].__resetExtremes(); + } + this.__owner.__draw(true); + } + } + }, + getExtremes: function() { + return { + min: (this.__forced_min_extreme!=undefined?this.__forced_min_extreme:this.__min_extreme), + max: (this.__forced_max_extreme!=undefined?this.__forced_max_extreme:this.__max_extreme) + }; + }, + // JHM: 2008-06-30 - Added redraw flag to setExtremes + setExtremes: function(min, max, redraw) { + this.__forced_min_extreme = min; + this.__forced_max_extreme = max; + if (redraw == undefined || redraw == true) { + this.__owner.__calculateExtremes(true); + } + }, + // JHM: 2008-08-17 - Added ignoreBounds parameter, outsideBounds property to result + pointToPixel: function(point, ignoreBounds) { + + var outside = false; + var p; + + // JHM: Fixed pointToPixel support for bins + // JHM: 2008-06-05 - Added support for bins + if (this.__text_values.__count() > 0 && (typeof point == "string")) { + point = this.__text_values.__find(point).__index; + } + + if (point > this.__current_max || point < this.__current_min) { + if (ignoreBounds == undefined || ignoreBounds == false) { + return undefined; + } else { + outside = true; + } + } + + p = this.__pt2px(point); + p = this.__owner.__chartPt2ScreenPt({ x: p, y: p }); + if (this.__orientation == "h") { + if (ignoreBounds != undefined) { + return { point: p.x, outsideBounds: outside }; + } else { + return p.x; + } + } else { + if (ignoreBounds != undefined) { + return { point: p.y - this.__owner.__draw_area.top, outsideBounds: outside }; + } else { + return p.y - this.__owner.__draw_area.top; + } + } + }, + pixelToPoint: function(point) { + point = this.__owner.__screenPt2ChartPt({ x: point, y: point }); + if (this.__orientation == "h") { + // JHM: 2008-06-05 - Added support for bins + point = this.__px2pt(point.x + this.__owner.__draw_area.left); + if (this.__text_values.__count() > 0) { + point = this.__text_values.__get(m_ROUND(point)).__label; + } + return point + } else { + point = this.__px2pt(point.y + this.__owner.__draw_area.top); + if (this.__text_values.__count() > 0) { + point = this.__text_values.__get(m_FLOOR(point)); + } + return point + } + }, + setCaption: function(caption) { + this.caption = caption; + if( this.__el_caption == undefined ) return false; // JGD: Returns if axis has not been drawn + this.__el_caption.innerHTML = this.__getCaption(); + if (this.__owner.__draw_area == undefined) { return false; } + this["__adjustCaptionPosition_" + this.__orientation](this.__owner.__draw_area); + }, + showGrid: function(redraw) { + this.grid.show = true; + if (redraw == undefined || redraw == true) { + this.__owner.__draw(true); + } + }, + hideGrid: function(redraw) { + this.grid.show = false; + if (redraw == undefined || redraw == true) { + this.__owner.__draw(true); + } + }, + show: function(redraw) { + if (!this.visible) { + this.visible = true; + this.__force_visible = true; + if (redraw == undefined || redraw == true) { + this.__owner.__resize(true, true, true); + } + } + }, + hide: function(redraw) { + if (this.visible) { + this.visible = false; + this.__force_visible = false; + if (redraw == undefined || redraw == true) { + // JHM: 2008-05-21 + this.__owner.__resize(true, true, true); + } + } + }, + // JHM: Updated to include fireEvent property + setCrosshair: function(visible, coordinate, fireEvent) { + if (visible) { + this.__showCrosshair({ y: this.__pt2px(coordinate) - this.__owner.__draw_area.top, x: this.__pt2px(coordinate) - this.__owner.__draw_area.left }, fireEvent); + } else { + this.__hideCrosshair(fireEvent); + } + }, + resetZoom: function(redraw) { + this.__resetZoom(); + if (redraw == undefined || redraw == true) { + this.__owner.__draw(true); + } + }, + getZoomBoxCoordinates: function() { + return this.__getZoomBoxCoordinates(); + }, + getZoom: function() { + return { min: this.__current_min, max: this.__current_max }; + }, + setZoom: function(min, max, redraw, reselectPoint) { + this.__current_min = min; + this.__current_max = max; + this.__calculateScale(false); + if (redraw == undefined || redraw == true) { + var chart = this.__owner; + window.setTimeout(function() { chart.__draw(reselectPoint); },0); + } + }, + // Public events + onNeedsTicks: undefined, + onShowCrosshair: undefined, + onHideCrosshair: undefined, + onShowCursorPosition: undefined, + onHideCursorPosition: undefined + }, + // -------------------------------------------------------------------------- + // CLASS: __Point + // -------------------------------------------------------------------------- + __Point: { + // Public properties + x: null, + y: null, + label: null, + userdata: null + }, + // -------------------------------------------------------------------------- + // CLASS: __DataHandler + // -------------------------------------------------------------------------- + __DataHandler: { + __autoclear: true, + __owner: undefined, + __loading: false, + __loaded: false, + __data: null, + __dataAvailable: function() { + if (this.onDataAvailable != null) { + // JHM: Added call to onDataAvailable event + if (this.onDataAvailable(this.__data, this, this.__owner, this.__owner.__getChart()) == false) { + return; + } + } + this.__owner.__dataAvailable(this.__data); + if (this.__autoclear == true) { + this.__data = []; + } + }, + loadData: function() { + this.__loadData(); + }, + __doLoadData: null, // Defined by descendant + __loadData: function() { + if (this.__loading == true || this.__loaded == true) { return; } + if (this.__doLoadData != null) { + var result = this.__doLoadData(); + if (result != null) { + this.__loaded = result; + } + } + }, + __showError: function(message) { + try { this.__owner.__owner.__doShowMessage(message, "error"); + } catch (e) { + } + }, + __init: function(series, template) { + this.__data = new Array(); + this.__owner = series; + this.__template = template; + }, + onDataAvailable: null + } + }).__init(); + + EJSC.Inheritable.__extendTo(EJSC.Chart); + var ___chart = EJSC.Chart.prototype; + +/* JGD - 2011-03-04 - Added */ + ___chart.getSeries = function() { + return this.__series; + }; + + // CHART PRIVATE METHODS + ___chart.__initializeAxes = function() { + + if (this.axis_bottom != undefined) { this.axis_bottom.__init(this, "h", "bottom"); } + if (this.axis_left != undefined) { this.axis_left.__init(this, "v", "left"); } + if (this.axis_top != undefined) { this.axis_top.__init(this, "h", "top"); } + if (this.axis_right != undefined) { this.axis_right.__init(this, "v", "right"); }; + + }; + + // Initializes the chart object + ___chart.__init = function(options) { + + function applyAxisOptions(axis, chart) { + if (options["axis_" + axis] == undefined) { return options; } + if (options["axis_" + axis].__type != undefined) { + chart["axis_" + axis] = options["axis_" + axis]; + if (chart["axis_" + axis].__options.__chart_options == undefined) { + chart["axis_" + axis].__options.__chart_options = {}; + } + } else { + for (var i in options["axis_" + axis]) { + chart["axis_" + axis].__options[i] = options["axis_" + axis][i]; + } + } + if (options["axis_" + axis].visible == true) { chart["axis_" + axis].__options.__force_visible = true; } + delete options["axis_" + axis]; + }; + + if (options != undefined) { + + applyAxisOptions("left", this); + applyAxisOptions("bottom", this); + applyAxisOptions("right", this); + applyAxisOptions("top", this); + + this.__copyOptions(options); + + } + + this.__initializeAxes(); + + if (options != undefined) { + + if (options.axis_left && options.axis_left.visible == true) { this.axis_left.__force_visible = true; } + if (options.axis_bottom && options.axis_bottom.visible == true) { this.axis_bottom.__force_visible = true; } + if (options.axis_right && options.axis_right.visible == true) { this.axis_right.__force_visible = true; } + if (options.axis_top && options.axis_top.visible == true) { this.axis_top.__force_visible = true; } + + } + + // Added event for building + if (this.onBeforeBuild != undefined) { + this.onBeforeBuild(this); + } + + // Create and append the chart objects + this.__write(); + + // Resize the chart objects + this.__resize(false, true); + + // Show the chart + this.__el_container.className = this.__el_container.className.replace(/ ejsc-invisible/,""); + + // Added event for building + if (this.onAfterBuild != undefined) { + this.onAfterBuild(this); + } + +/* JGD - 2011-03-04 - Added */ + this.__addAdditionalParameters( this.__el_axes_canvas ); + this.__addAdditionalParameters( this.__el_series_canvas ); + this.__addAdditionalParameters( this.__el_hint_canvas ); + + var self = this; + window.setTimeout(function() { self.__draw(false); },1); + + // Added check for auto_resize + if (this.auto_resize) { + EJSC.__addChartResize(this); + } + + }; + +/* JGD - 2011-03-04 - Added */ + ___chart.__addAdditionalParameters = function( el ) { + + // if( EJSC.__isIE ) return; + var ctx = el.getContext("2d"); + + ctx.__el = el; + + ctx.clearText = function() { + var el = this.__el.parentNode; + var divs = el.getElementsByTagName('DIV'); + for( var i=0 ; i axis.__max_extreme ) max = axis.__max_extreme; + axis.setZoom( min , max , false ); + + /*--- Drag ---*/ + + if( v ) { + min -= max_d * axis.__scale; + max -= min_d * axis.__scale; + } else { + min += min_d * axis.__scale; + max += max_d * axis.__scale; + } + + if( min < axis.__min_extreme ) { + max = max + ( axis.__min_extreme - min ); + min = axis.__min_extreme; + } + if( max > axis.__max_extreme ) { + min = min + ( axis.__max_extreme - max ); + max = axis.__max_extreme; + } + if( min < axis.__min_extreme ) min = axis.__min_extreme; + if( max > axis.__max_extreme ) max = axis.__max_extreme; + axis.setZoom( min , max , false ); + + /* */ + + } + + var self = this; + this.__gestureTimeout = setTimeout( function() { self.redraw(); } , 50 ); + + } else if( e.targetTouches.length == 1 && (navigator.userAgent.toLowerCase()).search('android') !== -1 ) { + + this.__gestering = e.targetTouches.length; + clearTimeout( this.__gestureTimeout ); + + var dif, dif2, mid, min, max, axis, x1, y1, x2, y2, difx, dify, dx1, dy1, dx2, dy2, scale; + + var v, s1, e1, d, min, max; + + for( var i in this.__gestureExtremes ) { + + axis = this['axis_' + i]; + v = axis.__orientation == 'v'; + if( !axis.visible ) continue; + + s1 = this.__touchCoords[ v ? 'y1' : 'x1' ]; + e1 = e.targetTouches[0][ v ? 'pageY' : 'pageX' ]; + + d = s1 - e1; + + min = this.__gestureExtremes[i][0]; + max = this.__gestureExtremes[i][1]; + + var n = min, x = max; + + /*--- Drag ---*/ + + if( v ) { + min -= d * axis.__scale; + max -= d * axis.__scale; + } else { + min += d * axis.__scale; + max += d * axis.__scale; + } + + if( min < axis.__min_extreme ) { + max = max + ( axis.__min_extreme - min ); + min = axis.__min_extreme; + } + if( max > axis.__max_extreme ) { + min = min + ( axis.__max_extreme - max ); + max = axis.__max_extreme; + } + if( min < axis.__min_extreme ) min = axis.__min_extreme; + if( max > axis.__max_extreme ) max = axis.__max_extreme; + axis.setZoom( min , max , false ); + + } + + var self = this; + this.__gestureTimeout = setTimeout( function() { self.redraw(); } , 50 ); + + } + + break; + + case 'touchend': + + clearTimeout( this.__gestureTimeout ); + clearTimeout( this.__contextTimeout ); + + if( this.__gestering > 0 ) { + + this.redraw(); + + } else if( this.__gestering === false ) { + + var evt = {}; + evt.button = 1; + evt.x = evt.clientX = this.__touchCoords.x1; + evt.y = evt.clientY = this.__touchCoords.y1; + evt.target = e.target; + + this.__mouse_in_chart = true; + this.proximity_snap *= 5; + + this.__doClickCanvasCover(evt); + this.__doMouseUpCanvasCover(evt); + + this.proximity_snap *= 1/5; + + var touch = new Date().getTime(); + if( touch - this.__lastTouch > 0 && touch - this.__lastTouch <= 500 ) { + if( this.selected_point == undefined ) + this.__doDblClickCanvasCover(e); + else + this.__unselectPoint(true); + } + this.__lastTouch = touch; + + } + + this.__gestering = false; + + break; + + } + + }; + + // Create html objects and insert into document + ___chart.__write = function() { + + // JHM: 2008-05-21 - Validate element this.__el + if (this.__el == null) { return; } + + var self = this; + + this.__el_container = EJSC.utility.__createDOMArray( + ["div", { + className: "ejschart ejsc-invisible", + unselectable: "on", + tabIndex: 0, + __ref: self, + __events: { + selectstart: doDragOrSelect, +/* JGD - 2011-02-28 - Fixed to support context menu */ + contextmenu: function(e) { self.__doRightClickCanvasCover(e); return cancelEvent(e); }, + dblclick: function(e) { self.__doDblClickCanvasCover(e); }, + click: function(e) { self.__doClickCanvasCover(e); }, + mousemove: function(e) { self.__doMouseMoveCanvasCover(e); }, + mousedown: function(e) { self.__doMouseDownCanvasCover(e); return cancelEvent(e); }, + mouseup: function(e) { self.__doMouseUpCanvasCover(e); if (!EJSC.__isIE) { self.__el_key_grabber.focus(); } }, + mouseout: function(e) { self.__doMouseOutCanvasCover(e); }, + keydown: function(e) { self.__doKeyDownCanvasCover(e); return false; }, + mousewheel: function(e) { self.__doMouseWheelCanvasCover(e) }, + DOMMouseScroll: function(e) { self.__doMouseWheelCanvasCover(e) } + }, + ontouchstart: function(e) { self.__doTouch(e); return cancelEvent(e); } , + ontouchmove: function(e) { self.__doTouch(e); return cancelEvent(e); } , + ontouchend: function(e) { self.__doTouch(e); return cancelEvent(e); } + }, + ["div", { + className: "ejsc-chart", + __ref: self, + __refVar: "__el_chart_container" + }, + ["div", { className: "ejsc-canvas-container ejsc-axes-canvas-container", __ref: self, __refVar: "__el_axes_canvas_container" }, + ["canvas", { + className: "ejsc-axes-canvas", + __ref: self, + __refVar: "__el_axes_canvas" + + }] + ], + ["div", { className: "ejsc-canvas-container ejsc-series-canvas-container", __ref: self, __refVar: "__el_series_canvas_container" }, + ["div", { + className: "ejsc-series-canvas-div", + __ref: self, + __refVar: "__el_series_canvas_div" + }, + ["canvas", { + className: "ejsc-series-canvas", + __ref: self, + __refVar: "__el_series_canvas" + }] + ] + ], + ["div", { + className: "ejsc-titlebar" + (this.show_titlebar==true?"":" ejsc-hidden"), + __ref: self, + __refVar: "__el_titlebar" + }, + ["span", { + className: "ejsc-titlebar-text", + innerHTML: this.title, + __ref: self, + __refVar: "__el_titlebar_text" + }] + ], + ["div", { + className: "ejsc-labels", + __ref: self, + __refVar: "__el_labels" + }], + ["div", { className: "ejsc-canvas-container ejsc-hint-canvas-container" }, + ["canvas", { + className: "ejsc-hint-canvas", + __ref: self, + __refVar: "__el_hint_canvas" + }] + ], + ["div", { + className: "ejsc-hint-labels", + __ref: self, + __refVar: "__el_hint_labels" + }], + ["div", { + className: "ejsc-zoombox ejsc-invisible", + __ref: self, + __refVar: "__el_zoombox" + }], + ["span", { + className: "ejsc-message ejsc-message-progress" + (this.show_messages==false?" ejsc-hidden":""), + innerHTML: this.building_message, + __ref: self, + __refVar: "__el_message" + }] + /*["input", { + className: "ejsc-key-grabber", + tabIndex: 1, + __ref: self, + __refVar: "__el_key_grabber", + __events: { + keydown: function(e) { return self.__doKeyDownCanvasCover(e); } + } + }],*/ +/* JHM - 2009-01-08 - Moved up a container so that z-index works properly + ["span", { + className: "ejsc-hint", + __ref: self, + __refVar: "__el_hint" + }, + ["span", { + __ref: self, + __refVar: "__el_hint_text" + }] + ], +*/ + ], + /*["div", { + className: "ejsc-canvas-cover", + __ref: self, + __refVar: "__el_canvas_cover", + __events: { +/* JGD - 2011-02-28 - Fixed to support context menu * + contextmenu: function(e) { self.__doRightClickCanvasCover(e); return cancelEvent(e); }, + dblclick: function(e) { self.__doDblClickCanvasCover(e); }, + click: function(e) { self.__doClickCanvasCover(e); }, + mousemove: function(e) { self.__doMouseMoveCanvasCover(e); }, + mousedown: function(e) { self.__doMouseDownCanvasCover(e); }, + mouseup: function(e) { self.__doMouseUpCanvasCover(e); if (!EJSC.__isIE) { self.__el_key_grabber.focus(); } }, + mouseout: function(e) { self.__doMouseOutCanvasCover(e); }, + keydown: function() { if (EJSC.__isIE) { self.__doKeyDownCanvasCover(event); return false; } else { return true; } }, + mousewheel: function(e) { self.__doMouseWheelCanvasCover(e) }, + DOMMouseScroll: function(e) { self.__doMouseWheelCanvasCover(e) } + } + }],*/ + ["span", { + className: "ejsc-hint", + __ref: self, + __refVar: "__el_hint" + }, + ["span", { + __ref: self, + __refVar: "__el_hint_text" + }] + ], + ["div", { + className: "ejsc-hint-pointer", + __ref: self, + __refVar: "__el_hint_pointer" + }, + ["div", { + className: "ejsc-hint-tl" + }] + ], + ["div", { + className: "ejsc-legend-right" + (this.show_legend==true&&this.legend_position=="right"?"":" ejsc-hidden"), + __ref: self, + __refVar: "__el_legend_right" + }], + ["div", { + className: "ejsc-legend-bottom" + (this.show_legend==true&&this.legend_position=="bottom"?"":" ejsc-hidden"), + __ref: self, + __refVar: "__el_legend_bottom" + }], + ["div", { + className: "ejsc-legend ejsc-legend-" + (this.legend_state=="normal"?"maximized":"minimized") + (this.show_legend==true&&this.legend_position=="floating"?"":" ejsc-hidden"), + __ref: self, + __refVar: "__el_legend" + }, + ["div", { + className: "ejsc-legend-caption", + __ref: self, + __events: { + mousedown: function(e) { self.__doStartMoveLegend(e); }, + mouseup: function(e) { self.__doEndMoveLegend(e); }, + mousemove: function(e) { self.__doMoveLegend(e); } + } + }, + ["div", { + className: "ejsc-legend-minimize", + __ref: self, + __events: { + click: function() { self.__doMinimizeLegend(); } + } + }, + ["div", { + className: "ejsc-legend-minimize-mouseout", + __ref: self, + __refVar: "__el_legend_minimize", + __events: { + mouseover: function() { self.__el_legend_minimize.className = "ejsc-legend-minimize-mouseover"; }, + mouseout: function() { self.__el_legend_minimize.className = "ejsc-legend-minimize-mouseout"; } + } + }] + ], + ["div", { + className: "ejsc-legend-maximize", + __ref: self, + __events: { + click: function() { self.__doMaximizeLegend(); } + } + }, + ["div", { + className: "ejsc-legend-maximize-mouseout", + __ref: self, + __refVar: "__el_legend_maximize", + __events: { + mouseover: function() { self.__el_legend_maximize.className = "ejsc-legend-maximize-mouseover"; }, + mouseout: function() { self.__el_legend_maximize.className = "ejsc-legend-maximize-mouseout"; } + } + }] + ], + ["div", { + className: "ejsc-legend-grabber" + }], + ["span", { + className: "ejsc-legend-title", + innerHTML: this.legend_title, + __ref: self, + __refVar: "__el_legend_title" + }] + ], + ["div", { + className: "ejsc-legend-series-container", + __ref: self, + __refVar: "__el_legend_series", + __events: { + mousemove: function() { return false; } + } + }], + ["div", { + className: "ejsc-legend-owner", + __ref: self, + __refVar: "__el_legend_owner" + }, + ["div", { + className: "ejsc-legend-owner-icon" + }], + ["span", { + className: "ejsc-legend-owner-title", + innerHTML: this.title, + __ref: self, + __refVar: "__el_legend_owner_title" + }] + ] + ] + ] + ); + + this.__el_canvas_cover = this.__el_key_grabber = this.__el_container; + + // Transform canvas tags if IE + if (EJSC.__isIE){ + this.__el_axes_canvas = G_vmlCanvasManager.initElement(this.__el_axes_canvas, this); + this.__el_series_canvas = G_vmlCanvasManager.initElement(this.__el_series_canvas, this); + this.__el_hint_canvas = G_vmlCanvasManager.initElement(this.__el_hint_canvas, this); + } + + // Save references to canvas drawing contexts + + this.__axes_context = this.__el_axes_canvas.getContext("2d"); + this.__axes_context.globalCompositeOperation = 'source-over'; + this.__series_context = this.__el_series_canvas.getContext("2d"); + this.__series_context.globalCompositeOperation = 'source-over'; + this.__hint_context = this.__el_hint_canvas.getContext("2d"); + this.__hint_context.globalCompositeOperation = 'source-over'; + + this.__el.appendChild(this.__el_container); + + if( (navigator.userAgent.toLowerCase()).search('android') !== -1 ) { + this.__el_container.appendChild( EJSC.utility.__createDOMArray( + ["div", { + className: "ejsc-zoom-buttons" + }, + ["button", { + className: "zoom-in", + ontouchend: function() { var e = { wheelDelta: 1 }; self.__doMouseWheelCanvasCover(e); return cancelEvent(e); }, + onclick: function(e) { var e = { wheelDelta: 1 }; self.__doMouseWheelCanvasCover(e); return cancelEvent(e); }, + ondblclick: function(e) { return cancelEvent(e); } + }], + ["button", { + className: "zoom-out", + ontouchend: function() { var e = { wheelDelta: -1 }; self.__doMouseWheelCanvasCover(e); return cancelEvent(e); }, + onclick: function(e) { var e = { wheelDelta: -1 }; self.__doMouseWheelCanvasCover(e); return cancelEvent(e); }, + ondblclick: function(e) { return cancelEvent(e); } + }] + ] + ) ); + } + + }; + + // Position message window in the center of the chart draw area + ___chart.__positionMessage = function() { + + var da = this.__draw_area; + if (da == undefined) { return false; } + + // Reposition message window + if (this.__el_message.className.match(/ejsc-invisible/) == null) { + this.__el_message.style.left = da.left + m_FLOOR((da.width - this.__el_message.offsetWidth) / 2) + "px"; + this.__el_message.style.top = da.top + m_FLOOR((da.height - this.__el_message.offsetHeight) / 2) + "px"; + } + + }; + + // Show the message window + ___chart.__doShowMessage = function(message, messageType) { + + if (this.show_messages == false) { return false; } + + // Current Classes: + // progress, nodata, error, info + if (this.onShowMessage != undefined) { + if (this.onShowMessage(message, messageType) == false ) return false; + } + + var el = this.__el_message; + if (el == null || el == undefined) { return; } + + // JHM: 2007-09-25 - Added the ability to cancel hiding error messages, see Chart.allow_hide_error property + if (el.className.match(/error/) == null || messageType == "error" || this.allow_hide_error === true) { + el.className = el.className.replace(/ ejsc-invisible/g, ""); + el.className = el.className.replace(/(progress|nodata|error|info)/, messageType); + el.innerHTML = message; + this.__positionMessage(); + + if (this.__message_timeout != undefined) { + window.clearTimeout(this.__message_timeout); + this.__message_timeout = undefined; + } + + var self = this; + var timeout = this.message_timeouts[messageType]; + this.__message_timeout = window.setTimeout(function() { self.__doHideMessage(); }, timeout); + + } + + }; + + // Hide the message window + ___chart.__doHideMessage = function() { + + this.__message_timeout = undefined; + + // JHM: 2007-09-25 - Added the ability to cancel hiding error messages, see Chart.allow_hide_error property + if (this.__el_message.className.match(/error/) == null || this.allow_hide_error === true) { + this.__el_message.clasName = this.__el_message.className.replace(/ ejsc-invisible/g, ""); + this.__el_message.className += " ejsc-invisible"; + } + + }; + + ___chart.setLegendPosition = function(legend_position) { + this.legend_position = legend_position; + this.__resize(true,true,true); + // this.redraw(); + }; + + // Resize components + ___chart.__resize = function(redraw, force_recalc, update_scale) { + + // JHM: 2008-05-21 - Validate element this.__el + if (this.__el == null) { return; } + + function resizeCanvas(el, width, height) { + el.width = width; + el.height = height; + el.style.width = width + "px"; + el.style.height = height + "px"; + el.setAttribute("width", width); + el.setAttribute("height", height); + // Fire IE event, won't fire automatically + if (EJSC.__isIE) el.fireEvent("onresize"); + } + + // Get outer container's dimensions + var c_width = this.__el.offsetWidth - EJSC.utility.__borderSize(this.__el, "left") - EJSC.utility.__borderSize(this.__el, "right"); + var c_height = this.__el.offsetHeight - EJSC.utility.__borderSize(this.__el, "top") - EJSC.utility.__borderSize(this.__el, "bottom"); + + // Validate the size change, exit if its the same + if (force_recalc == undefined || force_recalc == false) { + if (c_width == this.__resize_width && c_height == this.__resize_height) { return false; } + } + + if (c_width <= 0 || c_height <= 0) { // JGD - 2010-02-25 - v2.1 - Fixed to check if <=0 for IE7 and IE8 divs with borders + // this.__draw_area = undefined; + return false; + } + + // JHM: 2008-05-21 - Moved to set resize height/width to size even when zero so that the change is recognized when they change to not zero + this.__resize_width = c_width; + this.__resize_height = c_height; + + // JHM: 2008-06-02 - Implemented last drawn dimensions to reduce unnecessary redraws + var needs_redraw = false; + if (this.__last_draw_width != this.__resize_width || this.__last_draw_height != this.__resize_height) { + this.__last_draw_width = this.__resize_width; + this.__last_draw_height = this.__resize_width; + needs_redraw = true; + } + + if( this.show_legend == true ) { + if( this.legend_position == 'right' ) + c_width -= this.legend_width + this.legend_padding; + else if( this.legend_position == 'bottom' ) + c_height -= this.legend_height + this.legend_padding; + } + + if (needs_redraw) { + + // Resize canvas objects + resizeCanvas(this.__el_chart_container, c_width, c_height); + resizeCanvas(this.__el_axes_canvas, c_width, c_height); + resizeCanvas(this.__el_series_canvas, c_width, c_height); + resizeCanvas(this.__el_hint_canvas, c_width, c_height); + + resizeCanvas(this.__el_legend_right, this.legend_width-2, c_height-2); + resizeCanvas(this.__el_legend_bottom, c_width-2, this.legend_height-2); + + if( this.show_legend ) { + switch( this.legend_position ) { + case 'floating': this.__el_legend.insertBefore( this.__el_legend_series , this.__el_legend_owner ); break; + case 'right': this.__el_legend_right.appendChild( this.__el_legend_series ); break; + case 'bottom': this.__el_legend_bottom.appendChild( this.__el_legend_series ); break; + } + } + + this.__el_legend.className = this.__el_legend.className.replace( / ejsc-hidden/g , '' ) + ( this.legend_position == 'floating' && this.show_legend ? '' : ' ejsc-hidden' ); + this.__el_legend_right.className = this.__el_legend_right.className.replace( / ejsc-hidden/g , '' ) + ( this.legend_position == 'right' && this.show_legend ? '' : ' ejsc-hidden' ); + this.__el_legend_bottom.className = this.__el_legend_bottom.className.replace( / ejsc-hidden/g , '' ) + ( this.legend_position == 'bottom' && this.show_legend ? '' : ' ejsc-hidden' ); + + } + + var offsetTop, offsetLeft, offsetBottom, offsetRight; + offsetTop = (this.show_titlebar==true?this.__el_titlebar.offsetHeight:0); + offsetTop = (this.axis_top.visible==true&&this.__hasXAxisSeries()?this.axis_top.__getPhysicalSize()+offsetTop:offsetTop); + offsetLeft = (this.axis_left.visible==true&&this.__hasYAxisSeries()?this.axis_left.__getPhysicalSize():0); + offsetBottom = (this.axis_bottom.visible==true&&this.__hasXAxisSeries()?this.axis_bottom.__getPhysicalSize():0); + offsetRight = (this.axis_right.visible==true&&this.__hasYAxisSeries()?this.axis_right.__getPhysicalSize():0); + + // Calculate draw area + this.__draw_area = { + left: offsetLeft, + top: offsetTop, + right: offsetLeft + c_width - offsetLeft - offsetRight, + bottom: offsetTop + c_height - offsetBottom - offsetTop, + width: c_width - offsetLeft - offsetRight, + height: c_height - offsetBottom - offsetTop + }; + + if (needs_redraw) { + // Reposition series canvas container and series canvas + this.__el_series_canvas_container.style.left = this.__draw_area.left + "px"; + this.__el_series_canvas_container.style.top = this.__draw_area.top + "px"; + this.__el_series_canvas_container.style.width = this.__draw_area.width + "px"; + this.__el_series_canvas_container.style.height = this.__draw_area.height + "px"; + + this.__el_series_canvas_div.style.left = -(this.__draw_area.left) + "px"; + this.__el_series_canvas_div.style.top = -(this.__draw_area.top) + "px"; + + this.__positionMessage(); + + if (update_scale === true) { + // JHM: 2008-05-30 - Modified to remove padding recalc + this.axis_bottom.__calculateScale(false, true); + this.axis_left.__calculateScale(false, true); + this.axis_top.__calculateScale(false, true); + this.axis_right.__calculateScale(false, true); + } + + if (redraw == undefined || redraw != false) { + this.__draw(true); + } + } + + }; + + // Determine if the chart has series which require a x axis + ___chart.__hasXAxisSeries = function() { + + var i = 0; + var result = false; + + for (; i < this.__series.length; i++) { + if (this.__series[i].__needsXAxis) { + result = true; + break; + } + } + + return result; + + }; + + // Determine if the chart has series which require a y axis + ___chart.__hasYAxisSeries = function() { + + var i = 0; + var result = false; + + for (; i < this.__series.length; i++) { + if (this.__series[i].__needsYAxis) { + result = true; + break; + } + } + + return result; + + }; + + // Draw the axes + ___chart.__draw_axes = function(ctx, clearCanvas) { + + // Clear axes canvas + if (clearCanvas == undefined || clearCanvas == true) { + ctx.clearRect(0, 0, this.__el_axes_canvas.offsetWidth, this.__el_axes_canvas.offsetHeight); + ctx.clearText(); + } + + // Draw background + if (this.background.opacity > 0) { + + var da = this.__draw_area; + ctx.lineWidth = 0; + ctx.fillStyle = EJSC.utility.__getColor(this.background.color, this.background.opacity / 100).rgba; + ctx.beginPath(); + ctx.moveTo(da.left, da.top); + ctx.lineTo(da.right, da.top); + ctx.lineTo(da.right, da.bottom); + ctx.lineTo(da.left, da.bottom); + ctx.lineTo(da.left, da.top); + ctx.fill(); + + if (this.background.includeTitle && this.show_titlebar) { + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(this.__el.offsetWidth, 0); + ctx.lineTo(this.__el.offsetWidth, this.__el_titlebar.offsetHeight); + ctx.lineTo(0, this.__el_titlebar.offsetHeight); + ctx.lineTo(0, 0); + ctx.fill(); + } + } + + // Redraw axes + if (this.axis_left != undefined && (this.__hasYAxisSeries() || this.axis_left.__hasManualExtremes())) { this.axis_left.__draw(ctx); } + if (this.axis_bottom != undefined && (this.__hasXAxisSeries() || this.axis_bottom.__hasManualExtremes())) { this.axis_bottom.__draw(ctx); } + if (this.axis_top != undefined && (this.__hasXAxisSeries() || this.axis_top.__hasManualExtremes())) { this.axis_top.__draw(ctx); } + if (this.axis_right != undefined && (this.__hasYAxisSeries() || this.axis_right.__hasManualExtremes())) { this.axis_right.__draw(ctx); } + + }; + + // Draw the series +/* JGD - 2011-02-04 - Fixed */ + ___chart.__draw_series = function(ctx, clearCanvas) { + + // Clear series canvas + if (clearCanvas == undefined || clearCanvas == true) { + ctx.clearRect(0, 0, this.__el_series_canvas.offsetWidth, this.__el_series_canvas.offsetHeight); + ctx.clearText(); + } + + if (ctx.beginUpdate) ctx.beginUpdate(); + + if( this.onBeforeDrawSeries !== undefined ) + this.onBeforeDrawSeries(this,ctx); + + for (var i = 0; i < this.__series.length; i++) { + this.__series[i].__doDraw(ctx); + } + + if( this.onAfterDrawSeries !== undefined ) + this.onAfterDrawSeries(this,ctx); + + if (ctx.endUpdate) ctx.endUpdate(); + + }; + + // Draw zero planes + ___chart.__draw_zero_planes = function(ctx) { + + if (this.axis_left != undefined) { this.axis_left.__drawZeroPlane(ctx); } + if (this.axis_bottom != undefined) { this.axis_bottom.__drawZeroPlane(ctx); } + if (this.axis_top != undefined) { this.axis_top.__drawZeroPlane(ctx); } + if (this.axis_right != undefined) { this.axis_right.__drawZeroPlane(ctx); } + + }; + + // Cleanup after draw and reselect point if necessary + ___chart.__draw_cleanup = function(ctx, reselectPoint) { + + // IE hack to force last object to show + ctx.beginPath(); + ctx.moveTo(-1,-1); + ctx.lineTo(0,0); + ctx.stroke(); + + // Select point + if (this.selected_point != undefined && reselectPoint == true) { + this.__selectPoint(this.selected_point, this.__hint_is_sticky); + } + + }; + + ___chart.__draw = function(reselectPoint, showMessage) { + +/* JGD - 2011-03-02 - Added */ + if( EJSC.__ready == false && document.documentMode == 8 ) + return; + + // JHM: 2008-01-10 - Updated to enhance charts with a large number of series + if (this.__canDraw === false) { return; } + if (this.__axes_context == null || this.__series_context == null) { return; } + + if (this.__draw_area == undefined) { return false; } + + // Call before draw event handler if defined + if (this.onBeforeDraw != undefined) { + if (this.onBeforeDraw(this) == false) { return false; } + } + + if (showMessage == undefined || showMessage == true) { + this.__doShowMessage(this.drawing_message, "progress"); + } + + this.__draw_axes(this.__axes_context, true); + this.__draw_series(this.__series_context, true); + this.__draw_zero_planes(this.__series_context); + + // Call after draw event handler if defined + if (this.onAfterDraw != undefined) { + this.onAfterDraw(this); + } + + this.__draw_cleanup(this.__series_context, reselectPoint); + + }; + + // Tell all axes to calculate their extremes + ___chart.__calculateExtremes = function(reset) { + this.axis_left.__calculateExtremes(reset); + this.axis_bottom.__calculateExtremes(reset); + this.axis_right.__calculateExtremes(reset); + this.axis_top.__calculateExtremes(reset); + }; + + // Return the first available color from the __colors array + ___chart.__getNewSeriesColor = function() { + if (this.__colors.length == 0) { + this.__colors = EJSC.DefaultColors.slice(); + } + return this.__colors.pop(); + }; + + // Start legend move, record starting coordinates + ___chart.__doStartMoveLegend = function(e) { + if (!e) var e = window.event; + + var xy = EJSC.utility.__realXY(e); + + if (e.srcElement) { var el = e.srcElement; } + else { var el = e.target; } + + if (el.className.match(/mouseover/) == null) { + this.__legend_off_x = ( xy.x - this.__el_legend.offsetLeft ); + this.__legend_off_y = ( xy.y - this.__el_legend.offsetTop ); + this.__legend_is_moving = true; + } + }; + + // Clear legend moving flag + ___chart.__doEndMoveLegend = function(e) { + this.__legend_is_moving = false; + }; + + // Move legend window + ___chart.__doMoveLegend = function(e) { + if (this.__legend_is_moving) { + + // JHM: 2008-01-11 - Modified to use cached container reference + var cont = this.__el_container; + + if (!e) var e = window.event; + var xy = EJSC.utility.__realXY(e); + + // JHM: 2008-10-02 - Modified to account for scrolling containers + var ol = EJSC.utility.__documentOffsetLeft(cont, true); + var ot = EJSC.utility.__documentOffsetTop(cont, true); + var mw = (document.body.offsetWidth > (ol + cont.offsetWidth))?document.body.offsetWidth:cont.offsetWidth + ol; + var mh = (document.body.offsetHeight > (ot + cont.offsetHeight))?document.body.offsetHeight:cont.offsetHeight + ot; + + var tmp_left = xy.x - this.__legend_off_x; + if (tmp_left < (0 - ol)) { + tmp_left = (0 - ol); + } + if (tmp_left > (mw - ol - this.__el_legend.offsetWidth)) { + tmp_left = (mw - ol - this.__el_legend.offsetWidth); + } + + var tmp_top = xy.y - this.__legend_off_y; + if (tmp_top < (0 - ot)) { + tmp_top = (0 - ot); + } + if (tmp_top > (mh - ot - this.__el_legend.offsetHeight)) { + tmp_top = (mh - ot - this.__el_legend.offsetHeight); + } + + this.__el_legend.style.left = tmp_left + "px"; + this.__el_legend.style.top = tmp_top + "px"; + }; + }; + + // Hide the content area and footer of the legend window + ___chart.__doMinimizeLegend = function() { + this.__legend_height = this.__el_legend.style.height; + this.__el_legend.className = this.__el_legend.className.replace(/maximized/, "minimized"); + }; + + // Show the content area and footer of the legend window + ___chart.__doMaximizeLegend = function() { + + this.__el_legend.className = this.__el_legend.className.replace(/minimized/, "maximized"); + if (this.__legend_height != undefined) { + this.__el_legend.style.height = this.__legend_height; + } + }; + + // Added __changeLegendVisibility() + ___chart.__changeLegendVisibility = function(visible) { + + this.__el_legend.className = this.__el_legend.className.replace(/ ejsc-hidden/, ""); + if (!visible) { + this.__el_legend.className = this.__el_legend.className.replace(/ ejsc-hidden/, "") + " ejsc-hidden"; + } + this.show_legend = visible; + + }; + + ___chart.__doHideZoomBox = function() { + this.__el_zoombox.className = this.__el_zoombox.className.replace(/ ejsc-visible/, " ejsc-invisible"); + }; + + ___chart.__doShowZoomBox = function(min_x, max_x, min_y, max_y) { + this.__el_zoombox.className = this.__el_zoombox.className.replace(/ ejsc-invisible/, " ejsc-visible"); + this.__el_zoombox.style.left = (min_x) + "px"; + this.__el_zoombox.style.width = (max_x - min_x) + "px"; + this.__el_zoombox.style.top = (max_y) + "px"; + this.__el_zoombox.style.height = (min_y - max_y) + "px"; + }; + +/* JGD - 2011-02-28 - Added to support context menu */ + ___chart.__doRightClickCanvasCover = function(e) { + + if (!e) var e = window.event; + + if (this.__canCtxMenu && this.onContextMenu != undefined) { + this.onContextMenu(this, e); + } + + }; + + ___chart.__doClickCanvasCover = function( e ) { + + if (!e) var e = window.event; + +/* JGD - 2011-03-02 - Added */ + if (e.srcElement) var target = e.srcElement; + else var target = e.target; + while (target !== this.__el_container) { + if (target == this.__el_legend) return; + target = target.parentNode; + } + + if (this.allow_interactivity == false) { return true; } + + if (this.__just_zoomed) { + this.__just_zoomed = false; + return; + } + + // Get mouse coordinates, based on top left of the chart container + var point = this.__screenPt2ChartPt(EJSC.utility.__realXY(e)); + + // Determine if mouse is in draw area + if (this.__mouse_in_chart && !point.outside) { + + // Get Which Button Was Pressed + var rightclick = (e.which?(e.which==3):(e.button?(e.button==2):false)); + if (EJSC.utility.__canCoverMouseEvent(e) == false) return true; + + if (!rightclick && this.__zooming != true) { + this.__findPointAt(point, true, true); + } + + } + + }; + + ___chart.__doKeyDownCanvasCover = function( e ) { + + if (!e) var e = window.event; + + if (this.allow_interactivity == false) { return true; } + + // Fall out if currently processing a key + if (this.__key_processing == true) { return true; } + + // Process key + this.__key_processing = true; + + var key = (e.keyCode?e.keyCode:e.which); + var result = true; + + // Fix IE problem with not recognizing lower-case letters + if (EJSC.__isIE && (!(e.shiftKey)) && key >= 65 && key <= 90 ) { + key += 32; + } + + // Trigger key command + switch (key) { + case 13: // Enter + if (this.selected_point != undefined) { + if (this.onDblClickPoint != undefined) { + this.onDblClickPoint(this.selected_point, this.selected_point.__owner, this); + } + } + result = false; + break; + case 27: // ESC + case 99: // [C]lear key (Camino fix) + this.__unselectPoint(true); + result = false; + break; + case 37: // Left-Key + case 63234: // Left-Key (Unicode) + if (this.selected_point != undefined && this.__hint_is_sticky == true) { + this.selected_point.__owner.__selectPrevious(this.selected_point); + } + result = false; + break; + case 39: // Right-Key + case 63235: // Right-Key (Unicode) + if (this.selected_point != undefined && this.__hint_is_sticky == true) { + this.selected_point.__owner.__selectNext(this.selected_point); + } + result = false; + break; + case 38: // Up-Key + case 63232: // Up-Key (Unicode) + this.__selectPreviousSeries(); + result = false; + break; + case 40: // Down-Key + case 63233: // Down-Key (Unicode) + this.__selectNextSeries(); + result = false; + break; + } + + // Clear key processing and exit + this.__key_processing = false; + if (!result) { return cancelEvent(e); } + else return true; + }; + + ___chart.__doDblClickCanvasCover = function( e ) { + + if (this.allow_interactivity == false) { return true; } + + if (this.onBeforeDblClick) { + if (this.onBeforeDblClick(this) == false) { + return false; + } + } + + if (!e) var e = window.event; + if (EJSC.utility.__canCoverMouseEvent(e) == false) { return true; } + + if (this.onDblClickPoint != undefined && this.selected_point != undefined) { + if (this.onDblClickPoint(this.selected_point, this.selected_point.__owner, this) == false) { + return false; + } + } + + // Get Which Button Was Pressed + if (e.which) { var rightclick = (e.which == 3); } // Sets if the right button was pressed (true or false) if e.which + else if (e.button) { var rightclick = (e.button == 2); } // Sets if the right button was pressed (true or false) if e.button + + if (!rightclick) { + + if (this.allow_zoom) { + + try { + if (this.onBeforeZoom != undefined) { + if (this.onBeforeZoom(this) == false) { return false; } + } + + this.__zooming = true; + this.__el_zoombox.style.left = "0px"; + this.__el_zoombox.style.top = "0px"; + this.__el_zoombox.style.width = "0px"; + this.__el_zoombox.style.height = "0px"; + + this.axis_left.__resetZoom(); + this.axis_bottom.__resetZoom(); + this.axis_right.__resetZoom(); + this.axis_top.__resetZoom(); + + this.__draw(true); + this.__just_zoomed = true; + + if (this.onAfterZoom != undefined) { + this.onAfterZoom(this); + } + } catch (e) { + } finally { + this.__zooming = false; + } + } + } + }; + + ___chart.__doMouseDownCanvasCover = function( e ) { + + if (!e) var e = window.event; + +/* JGD - 2011-03-02 - Added */ + if (e.srcElement) var target = e.srcElement; + else var target = e.target; + while (target !== this.__el_container) { + if (target == this.__el_legend) return; + target = target.parentNode; + } + + if (this.allow_interactivity == false) { return true; } + if (!EJSC.utility.__canCoverMouseEvent(e)) { return true; } + + // Get mouse coordinates, based on top left of the chart container + var point = this.__screenPt2ChartPt(EJSC.utility.__realXY(e)); + var right_click = false; + + if (this.__mouse_in_chart) { + + // Get Which Button Was Pressed + if (e.which) { right_click = (e.which == 3); } // Sets if the right button was pressed (true or false) if e.which + else if (e.button) { right_click = (e.button == 2); } // Sets if the right button was pressed (true or false) if e.button + + if (!right_click && e.altKey) { right_click = true; } + + this.axis_left.__mouseDown(point, right_click); + this.axis_bottom.__mouseDown(point, right_click); + this.axis_right.__mouseDown(point, right_click); + this.axis_top.__mouseDown(point, right_click); + + if (!right_click) { + + if (this.allow_zoom) { + + if (this.onBeforeZoom != undefined) { + if (this.onBeforeZoom(this) == false) { return false; } + } + + this.__zooming = true; + this.__zoom_start = point; + + this.__el_zoombox.style.left = this.__zoom_start.x + "px"; + this.__el_zoombox.style.width = "0px"; + this.__el_zoombox.style.top = this.__zoom_start.y + "px"; + this.__el_zoombox.style.height = "0px"; + + if (this.onUserBeginZoom != undefined) { + if (this.onUserBeginZoom(this) == false) { + this.__zooming = false; + return false; + } + } + } + + } else { + +/* JGD - 2011-03-02 - Added */ + this.__canCtxMenu = true; + + if (this.allow_move) { + + if (this.onBeforeMove != undefined) { + if (this.onBeforeMove(this) == false) { return false; } + } + + this.__moving = true; + this.__move_start = point; + + } + + } + + } + + return false; + + }; + + ___chart.__doMouseMoveCanvasCover = function(e) { + + if (!e) var e = window.event; + + if (this.allow_interactivity == false) { return true; } + + // Get mouse coordinates, based on top left of the chart container + var point = this.__screenPt2ChartPt(EJSC.utility.__realXY(e)); + var newMIC = (point.outside == false); + + // Determine if mouse is in draw area and fire appropriate events when status changes + if (newMIC != this.__mouse_in_chart) { + this.__mouse_in_chart = newMIC; + if (newMIC == true) { + this.axis_left.__mouseEnter(point); + this.axis_bottom.__mouseEnter(point); + this.axis_right.__mouseEnter(point); + this.axis_top.__mouseEnter(point); + } else { + this.axis_left.__mouseLeave(); + this.axis_bottom.__mouseLeave(); + this.axis_right.__mouseLeave(); + this.axis_top.__mouseLeave(); + } + } + + if (this.__mouse_in_chart) { + + this.axis_left.__mouseMove(point); + this.axis_bottom.__mouseMove(point); + this.axis_right.__mouseMove(point); + this.axis_top.__mouseMove(point); + + } + + if (this.__zooming == true) { + + // Hide hint if not sticky to zooming + if (!this.__hint_is_sticky && this.selected_point != undefined) this.__unselectPoint(false); + + var zs = this.__zoom_start; + var ze = point; + + var min_x = (zs.xze.x?zs.x:ze.x); + var min_y = (zs.yze.y?zs.y:ze.y); + + if ((max_x - min_x) > 4 || (max_y - min_y) > 4) { + + if (this.auto_zoom == "y") { + min_y = 0; + max_y = this.__draw_area.height; + } else if (this.auto_zoom == "x") { + min_x = 0; + max_x = this.__draw_area.width; + } + + min_x += this.__draw_area.left; + max_x += this.__draw_area.left; + min_y += this.__draw_area.top; + max_y += this.__draw_area.top; + this.__doShowZoomBox(min_x, max_x, (min_y 0) { zoomDirection = 1; } + else if (zoomDirection < 0) { zoomDirection = -1; } + + if (this.onBeforeZoom != undefined) { + if (this.onBeforeZoom(this) == false) { + return cancelEvent(e); + } + } + + this.__zooming = true; + + try { + // Determine new zoom coordinates + var da = this.__draw_area; + // JHM: 2008-05-21 - Added additional checks for draw area undefined + if (da == undefined) { return cancelEvent(e); } + var zs = {}; + var ze = {}; + + if (zoomDirection == 1) { // 1 == zoom in + zs.x = da.left + (da.width * 0.1); + zs.y = da.top + (da.height * 0.1); + ze.x = da.right - (da.width * 0.1); + ze.y = da.bottom - (da.height * 0.1); + } else { + zs.x = da.left - (da.width * 0.5); + zs.y = da.top - (da.height * 0.5); + ze.x = da.right + (da.width * 0.5); + ze.y = da.bottom + (da.height * 0.5); + } + + this.__el_zoombox.style.left = zs.x + "px"; + this.__el_zoombox.style.top = zs.y + "px"; + this.__el_zoombox.style.width = (ze.x - zs.x) + "px"; + this.__el_zoombox.style.height = (ze.y - zs.y) + "px"; + +/* JGD - 2011-04-11 - Fixed */ + zs.x = zs.x - da.left; + zs.y = zs.y - da.top; + + // Trigger zoom in axes + + var al = this.axis_left.__zoom(zs, ze); + var ab = this.axis_bottom.__zoom(zs, ze); + var ar = this.axis_right.__zoom(zs, ze); + var at = this.axis_top.__zoom(zs, ze); + var skipMessage = al || ab || ar || at; + + if (!skipMessage && this.show_messages && (this.__hasYAxisSeries() || this.__hasXAxisSeries())) { + this.__doShowMessage(this.max_zoom_message, "info"); + return cancelEvent(e); + } + + // Redraw + this.__draw(true); + this.__just_zoomed = true; + + if (this.onAfterZoom != undefined) { + this.onAfterZoom(this); + } + + } catch (e) { + } finally { + this.__zooming = false; + } + + return cancelEvent(e); + }; + + ___chart.__doMouseUpCanvasCover = function( e ) { + + if (!e) var e = window.event; + + if (this.allow_interactivity == false) { return true; } + // JHM: 2008-05-21 - Added additional checks for draw area undefined + if (this.__draw_area == undefined) { return true; } + + try { + // Get mouse coordinates, based on top left of the chart container + var point = this.__screenPt2ChartPt(EJSC.utility.__realXY(e)); + var right_click = false; + + // Get Which Button Was Pressed + if (e.which) { right_click = (e.which == 3); } // Sets if the right button was pressed (true or false) if e.which + else if (e.button) { right_click = (e.button == 2); } // Sets if the right button was pressed (true or false) if e.button + + if (!right_click && e.altKey) { right_click = true; } + + this.axis_left.__mouseUp(point, right_click); + this.axis_bottom.__mouseUp(point, right_click); + this.axis_right.__mouseUp(point, right_click); + this.axis_top.__mouseUp(point, right_click); + + if (!right_click && this.__zooming) { + + var da = this.__draw_area; + var zs = this.__zoom_start; + var ze = {}; + + // Determine if down and right (zoom in), else update + // coords so that all axes will see it as a zoom out + if (this.auto_zoom == undefined) { + if (point.x > zs.x && point.y > zs.y) { + ze.x = this.__el_zoombox.offsetWidth + this.__el_zoombox.offsetLeft; + ze.y = this.__el_zoombox.offsetHeight + this.__el_zoombox.offsetTop; + } else { + zs.x = this.__el_zoombox.offsetWidth + this.__el_zoombox.offsetLeft; + zs.y = this.__el_zoombox.offsetHeight + this.__el_zoombox.offsetTop; + ze.x = this.__el_zoombox.offsetLeft; + ze.y = this.__el_zoombox.offsetTop; + } + } else if (this.auto_zoom == "y") { + if (point.x > zs.x) { + ze.x = this.__el_zoombox.offsetWidth + this.__el_zoombox.offsetLeft; + ze.y = this.__el_zoombox.offsetHeight + this.__el_zoombox.offsetTop; + } else { + zs.x = this.__el_zoombox.offsetWidth + this.__el_zoombox.offsetLeft; + zs.y = this.__el_zoombox.offsetHeight + this.__el_zoombox.offsetTop; + ze.x = this.__el_zoombox.offsetLeft; + ze.y = this.__el_zoombox.offsetTop; + } + } else if (this.auto_zoom == "x") { + if (point.y > zs.y) { + ze.x = this.__el_zoombox.offsetWidth + this.__el_zoombox.offsetLeft; + ze.y = this.__el_zoombox.offsetHeight + this.__el_zoombox.offsetTop; + } else { + zs.x = this.__el_zoombox.offsetWidth + this.__el_zoombox.offsetLeft; + zs.y = this.__el_zoombox.offsetHeight + this.__el_zoombox.offsetTop; + ze.x = this.__el_zoombox.offsetLeft; + ze.y = this.__el_zoombox.offsetTop; + } + } + + var far_enough_x = m_ABS(ze.x - zs.x) > 3; + var far_enough_y = m_ABS(ze.y - zs.y) > 3; + + if ((this.auto_zoom == "y" && far_enough_x) || (this.auto_zoom == "x" && far_enough_y) || far_enough_x && far_enough_y) { + + if (this.auto_zoom == "y") { + + if (zs.x < ze.x) { + var y_min, y_max, i, range; + for (i = 0; i < this.__series.length; i++) { + range = this.__series[i].__getYRange(zs.x, ze.x); + if (range != null) { + if (y_min == undefined || range.min < y_min) y_min = range.min; + if (y_max == undefined || range.max > y_max) y_max = range.max; + } + } + + if (y_min != undefined && y_max != undefined) { + zs.y = y_min; + ze.y = y_max; + } else { + this.__doShowMessage(this.max_zoom_message, "info"); + return false; + } + + } else { + zs.y = 0; + ze.y = -1; + } + + } else if (this.auto_zoom == "x") { + + if (ze.y > zs.y) { + var x_min, x_max, i, range; + for (i = 0; i < this.__series.length; i++) { + range = this.__series[i].__getXRange(ze.y, zs.y); + if (range != null) { + if (x_min == undefined || range.min < x_min) x_min = range.min; + if (x_max == undefined || range.max > x_max) x_max = range.max; + } + } + + if (x_min != undefined && x_max != undefined) { + zs.x = x_min; + ze.x = x_max; + } else { + this.__doShowMessage(this.max_zoom_message, "info"); + return false; + } + + } else { + zs.x = 0; + ze.x = -1; + } + + } + + if (this.onUserEndZoom != undefined) { + var zoom_start = {x: zs.x + da.left, y: zs.y + da.top}; + var zoom_end = {x: ze.x, y: ze.y}; + if (this.onUserEndZoom(this, zoom_start, zoom_end, (ze.x>zs.x&&ze.y>zs.y?'in':'out')) == false) { + return false; + } + } + + // Trigger zoom in axes + var al = this.axis_left.__zoom(zs, ze); + var ab = this.axis_bottom.__zoom(zs, ze); + var ar = this.axis_right.__zoom(zs, ze); + var at = this.axis_top.__zoom(zs, ze); + var skipMessage = al || ab || ar || at; + + if (!skipMessage && this.show_messages && (this.__hasYAxisSeries() || this.__hasXAxisSeries())) { + this.__doShowMessage(this.max_zoom_message, "info"); + return false; + } + + // Redraw + this.__draw(true); + this.__just_zoomed = true; + + if (this.onAfterZoom != undefined) { + this.onAfterZoom(this); + } + + } + + } + + } catch (e) { + } finally { + // Hide zoom box + this.__doHideZoomBox(); + + this.__moving = false; + this.__zooming = false; + + return false; + } + + }; + + ___chart.__selectPreviousSeries = function() { + + // Fail out if no selected point, hint isn't sticky, or has only one series + if (this.selected_point == undefined) return; + if (this.__hint_is_sticky == false) return; + + // Find previous series + var point = this.selected_point; + var series = point.__owner; + var i; + + // Check the current series to see if we can select next + if (!series.__selectNextSeries(point)) { + return; + } + + if (point.__owner.__owner != point.__owner.__getChart()) { + series = point.__owner.__owner; + } + + // Find current series + for (i = 0; i < this.__series.length; i++) { + if (this.__series[i] == series) { + break; + } + } + + i--; + + if (i < 0) { + i = this.__series.length - 1; + } + + // JGD: 2007-04-26 + // Updated to make sure new series is visible + while (this.__series[i].visible == false || this.__series[i].__canSelectPoints == false) { + i--; + if (i < 0) i = this.__series.length - 1; + } + + // Find point in previous series closest to the screen coordinates of the current point + var mouse = point.__owner.__point2px(point); + this.__selectPoint(this.__series[i].__findClosestPoint(mouse, false).point, true); + + }; + + ___chart.__selectNextSeries = function() { + + // Fail out if no selected point, hint isn't sticky, or has only one series + if (this.selected_point == undefined) return; + if (this.__hint_is_sticky == false) return; + + var point = this.selected_point; + var series = point.__owner; + var i; + + // Check the current series to see if we can select next + if (!series.__selectNextSeries(point)) { + return; + } + + if (point.__owner.__owner != point.__owner.__getChart()) { + series = point.__owner.__owner; + } + + // Find current series + for (i = 0; i < this.__series.length; i++) { + if (this.__series[i] == series) { + break; + } + } + + i++; + + if (i >= this.__series.length) { + i = 0; + } + + // JGD: 2007-04-26 + // Updated to make sure new series is visible + while (this.__series[i].visible == false || this.__series[i].__canSelectPoints == false) { + i++; + if (i == this.__series.length ) i = 0; + } + + // Find point in previous series closest to the screen coordinates of the current point + var mouse = point.__owner.__point2px(point); + var point = this.__series[i].__findClosestPoint(mouse, false); + if (point != null) { + this.__selectPoint(point.point, true); + } + + }; + + ___chart.__chartPt2ScreenPt = function(point) { + + var result = { x: undefined, y: undefined}; + var offset = undefined; + var area = this.__draw_area; + + if (this.__draw_area == undefined) { return false; } + + if (point.x != undefined) { + offset = EJSC.utility.__documentOffsetLeft(this.__el_canvas_cover); + result.x = (point.x + offset); + } else { + result.outside = true; + } + + if (point.y != undefined) { + offset = EJSC.utility.__documentOffsetTop(this.__el_canvas_cover); + result.y = (point.y + offset + area.top); + } else { + result.outside = true; + } + + return result; + + }; + + ___chart.__screenPt2ChartPt = function(point) { + + var result = { x: undefined, y: undefined, outside: false }; + var offset = undefined; + var area = this.__draw_area; + + if (area == undefined) { return false; } + + if (point.x != undefined) { + offset = EJSC.utility.__documentOffsetLeft(this.__el_canvas_cover); + + if (point.x < (offset + area.left)) { + result.x = 0; + result.outside = true; + } else if (point.x > (offset + area.right)) { + result.x = area.width; + result.outside = true; + } else { + result.x = (point.x - offset - area.left); + } + } else { + result.outside = true; + } + + if (point.y != undefined) { + offset = EJSC.utility.__documentOffsetTop(this.__el_canvas_cover); + + if (point.y < (offset + area.top)) { + result.y = 0; + result.outside = true; + } else if (point.y > (offset + area.bottom)) { + result.y = area.height; + result.outside = true; + } else { + result.y = (point.y - offset - area.top); + } + } else { + result.outside = true; + } + + return result; + + }; + + ___chart.__findPointAt = function(mouse, forceSticky, select, use_proximity) { + + // Create empty found points array + var points = []; + var series = this.__series; + var seriesLen = series.length; + var foundPoint, closestPoint, distance_from, dist; + var i, j; + var md = EJSC.math.__distance; + + use_proximity = (use_proximity == undefined?true:use_proximity); + + // Loop through all series and call their find point function + j = 0; + for (i = 0; i < seriesLen; i++) { + if (series[i].visible) { + foundPoint = series[i].__findClosestPoint(mouse, use_proximity); + if (foundPoint != null) { + points.push(foundPoint); + } + } + j++; + } + + // Loop through found points to find the closest or unselect point if not sticky + if (points.length > 0) { + + for (i = 0; i < points.length; i++) { + + // JHM: 2007-11-11 - Modified to use distance method + if (distance_from == undefined || points[i].distance < distance_from) { + distance_from = points[i].distance; + closestPoint = points[i].point; + } + + } + + if (closestPoint == undefined) { + closestPoint = points[0]; + } + + if (select == undefined || select == true) { + this.__selectPoint(closestPoint, forceSticky); + } + + } else { + + if ((select == undefined || select == true) && forceSticky != true) { + this.__unselectPoint(); + } + + } + + return closestPoint; + + }; + + + ___chart.__selectPoint = function(point, forceSticky, fireAfterEvent) { + + if (point == undefined || point.__owner == undefined) return; + // JHM: 2008-09-09 - Updated to define fireAfterEvent if not already set so that the published events fire properly + if (fireAfterEvent == undefined) fireAfterEvent = true; + + // Call before select function if defined + var hint = undefined; + + // JGD: 2007-05-09 + // Updated onBeforeSelectPoint to send point, series, chart, hint element, and select/hover + if (this.onBeforeSelectPoint && fireAfterEvent != false) { + // JHM: 2008-08-22 - Updated to account for initial point selection click when determining hoverOrSelect parameter + if (this.onBeforeSelectPoint(point, point.__owner, this, this.__el_hint, (this.__hint_is_sticky==true||forceSticky?"select":"hover")) == false) { + return false; + } + } + + // JHM: 2007-12-03 - Added check of show_hints before triggering onShowHint event + if (this.onShowHint && this.show_hints) { + // JHM: 2008-08-22 - Updated to account for initial point selection click when determining hoverOrSelect parameter + hint = this.onShowHint(point, point.__owner, this, this.__el_hint, (this.__hint_is_sticky==true||forceSticky?"select":"hover")); + } + + // JGD: 2007-08-06 + // Fixed to actually catch if this.selected_point.__owner is undefined + if (this.selected_point != undefined && this.selected_point.__owner != undefined && this.selected_point.__owner != point.__owner) { + this.selected_point.__owner.__doUnselectSeries(); + } + + // Store selected point + this.selected_point = point; + + // Hide if series is not visible + if (point.__owner.visible == false) { + this.__unselectPoint(true); + } + + // Select point + // JGD: 2007-05-09 + // Updated __selectPoint to transfer user text to series __selectPoint + // JHM: 2007-12-03 - Added check for show_hints + if (this.show_hints == true && hint != false) { + + // Retrieve hint details and coordinates from series + var hint_details = point.__owner.__selectPoint(point, this.__hint_is_sticky || forceSticky); + if (hint_details == null) { return; } + hint_details.chart_title = ""; + + // Configure hint text + if (hint == undefined) { + // Set hint text to series-defined default string + hint = hint_details.__defaultHintString; + // JHM: 2008-06-05 - Added support for cancelling hint from series.onShowHint + if (hint == false) { + return false; + } + } + + // Replace variables + for (var i in hint_details) { + if (i.match(/\_\_/) == null) { + hint = hint.replace(new RegExp("\\[" + i + "\\]","gi"), hint_details[i]); + } + } + + this.__el_hint_text.innerHTML = hint; + + // Show and position the hint + this.__el_hint.style.display = "block"; + + if (hint_details.__center) { + this.__el_hint_pointer.style.display = "none"; + this.__el_hint_pointer.firstChild.className = ""; + this.__el_hint.style.left = (hint_details.__position.x - (this.__el_hint.offsetWidth / 2) + "px"); + this.__el_hint.style.top = (hint_details.__position.y - (this.__el_hint.offsetHeight / 2) + "px"); + } else { + this.__el_hint_pointer.style.display = "block"; + + var x_mid = this.__draw_area.width / 2 + this.__draw_area.left; + var y_mid = this.__draw_area.height / 2 + this.__draw_area.top; + + var x_placement, x_offset, x_hint_offset; + if (hint_details.__position.x > x_mid) { + x_placement = "r"; + x_offset = 12; + x_hint_offset = this.__el_hint.offsetWidth + 5; + } else { + x_placement = "l"; + x_offset = 0; + x_hint_offset = -5; + } + + var y_placement, y_offset, y_hint_offset; + if (hint_details.__position.y > y_mid) { + y_placement = 'b'; + y_offset = 12; + y_hint_offset = this.__el_hint.offsetHeight + 5; + } else { + y_placement = 't'; + y_offset = 0; + y_hint_offset = -5; + } + + // Move hint to defined place + this.__el_hint_pointer.firstChild.className = "ejsc-hint-" + y_placement + x_placement; + this.__el_hint_pointer.style.left = (hint_details.__position.x - x_offset + "px"); + this.__el_hint_pointer.style.top = (hint_details.__position.y - y_offset + "px"); + this.__el_hint.style.left = (hint_details.__position.x - x_hint_offset + "px"); + this.__el_hint.style.top = (hint_details.__position.y - y_hint_offset + "px"); + } + + // Force hint_is_sticky if needed + if (forceSticky) { + this.__hint_is_sticky = true; + } + } + + // Call after select function if defined + // JGD: 2007-08-06 + // Fixed to check if this.selected_point actually exists + if (this.selected_point != undefined && this.onAfterSelectPoint && fireAfterEvent != false) { + // JGD: 2007-05-09 + // Updated onAfterSelectPoint to send point, series, chart, hint element, and select/hover + this.onAfterSelectPoint(point, point.__owner, this, this.__el_hint, (this.__hint_is_sticky==true?"select":"hover")); + } + +/* + if (point == undefined || point.__owner == undefined) return; + if (fireAfterEvent == undefined) fireAfterEvent = true; + + var hint = undefined; + + if (this.onBeforeSelectPoint && fireAfterEvent != false) { + if (this.onBeforeSelectPoint(point, point.__owner, this, this.__el_hint, (this.__hint_is_sticky==true||forceSticky?"select":"hover")) == false) { + return false; + } + } + + if (this.onShowHint && this.show_hints) { + hint = this.onShowHint(point, point.__owner, this, this.__el_hint, (this.__hint_is_sticky==true||forceSticky?"select":"hover")); + } + + if (this.selected_point != undefined && this.selected_point.__owner != undefined && this.selected_point.__owner != point.__owner) { + this.selected_point.__owner.__doUnselectSeries(); + } + + this.selected_point = point; + + if (point.__owner.visible == false) { + this.__unselectPoint(true); + } + + if (this.show_hints == true && hint != false) { + + // Retrieve hint details and coordinates from series + var hint_details = point.__owner.__selectPoint(point, this.__hint_is_sticky || forceSticky); + if (hint_details == null) { return; } + hint_details.chart_title = ""; + + if (hint == undefined) { + // Set hint text to series-defined default string + hint = hint_details.__defaultHintString; + if (hint == false) { + return false; + } + } + + for (var i in hint_details) { + if (i.match(/\_\_/) == null) { + hint = hint.replace(new RegExp("\\[" + i + "\\]","gi"), hint_details[i]); + } + } + + this.__el_hint_text.innerHTML = hint; + + this.__el_hint.style.display = "block"; + + if( ctx == undefined ) + var ctx = this.__hint_context; + + if( ctx == this.__hint_context ) { + this.__hint_context.clearRect(0, 0, this.__el_hint_canvas.offsetWidth, this.__el_hint_canvas.offsetHeight); + this.__hint_context.clearText(); + } + + if (hint_details.__center) { + + this.__el_hint_pointer.style.display = "none"; + this.__el_hint_pointer.firstChild.className = ""; + this.__el_hint.style.left = (hint_details.__position.x - (this.__el_hint.offsetWidth / 2) + "px"); + this.__el_hint.style.top = (hint_details.__position.y - (this.__el_hint.offsetHeight / 2) + "px"); + + } else { + + // this.__el_hint_pointer.style.display = "block"; + + var x_mid = this.__draw_area.width / 2 + this.__draw_area.left; + var y_mid = this.__draw_area.height / 2 + this.__draw_area.top; + + var x_placement, x_offset, x_hint_offset; + if (hint_details.__position.x > x_mid) { + x_placement = "r"; + x_offset = 12; + x_hint_offset = this.__el_hint.offsetWidth + 5; + } else { + x_placement = "l"; + x_offset = 0; + x_hint_offset = -5; + } + + var y_placement, y_offset, y_hint_offset; + if (hint_details.__position.y > y_mid) { + y_placement = 'b'; + y_offset = 12; + y_hint_offset = this.__el_hint.offsetHeight + 5; + } else { + y_placement = 't'; + y_offset = 0; + y_hint_offset = -5; + } + + this.__el_hint_pointer.firstChild.className = "ejsc-hint-" + y_placement + x_placement; + this.__el_hint_pointer.style.left = (hint_details.__position.x - x_offset + "px"); + this.__el_hint_pointer.style.top = (hint_details.__position.y - y_offset + "px"); + this.__el_hint.style.left = (hint_details.__position.x - x_hint_offset + "px"); + this.__el_hint.style.top = (hint_details.__position.y - y_hint_offset + "px"); + + ctx.strokeStyle = '#999999'; + ctx.fillStyle = '#ffffff'; + ctx.lineWidth = 1; + + var x = hint_details.__position.x; + var y = hint_details.__position.y; + var w = this.__el_hint.offsetWidth; + var h = this.__el_hint.offsetHeight; + var l = x; + var t = y; + + this.__el_hint.style.display = "none"; + + switch( y_placement ) { + case 't': + switch( x_placement ) { + case 'l': + ctx.beginPath(); + ctx.moveTo( x , y ); + ctx.lineTo( x+4 , y+11 ); + ctx.lineTo( x+4 , y+4+h ); + ctx.lineTo( x+4+w , y+4+h ); + ctx.lineTo( x+4+w , y+4 ); + ctx.lineTo( x+11 , y+4 ); + ctx.lineTo( x , y ); + ctx.fill(); + ctx.beginPath(); + ctx.moveTo( x , y ); + ctx.lineTo( x+4 , y+11 ); + ctx.lineTo( x+4 , y+4+h ); + ctx.lineTo( x+4+w , y+4+h ); + ctx.lineTo( x+4+w , y+4 ); + ctx.lineTo( x+11 , y+4 ); + ctx.lineTo( x , y ); + ctx.stroke(); + t = t + 4; + l = l + 4; + break; + case 'r': + ctx.beginPath(); + ctx.moveTo( x , y ); + ctx.lineTo( x-4 , y+11 ); + ctx.lineTo( x-4 , y+4+h ); + ctx.lineTo( x-4-w , y+4+h ); + ctx.lineTo( x-4-w , y+4 ); + ctx.lineTo( x-11 , y+4 ); + ctx.lineTo( x , y ); + ctx.fill(); + ctx.beginPath(); + ctx.moveTo( x , y ); + ctx.lineTo( x-4 , y+11 ); + ctx.lineTo( x-4 , y+4+h ); + ctx.lineTo( x-4-w , y+4+h ); + ctx.lineTo( x-4-w , y+4 ); + ctx.lineTo( x-11 , y+4 ); + ctx.lineTo( x , y ); + ctx.stroke(); + t = t + 4; + l = l - 4 - w; + break; + } + break; + case 'b': + switch( x_placement ) { + case 'l': + ctx.beginPath(); + ctx.moveTo( x , y ); + ctx.lineTo( x+4 , y-11 ); + ctx.lineTo( x+4 , y-4-h ); + ctx.lineTo( x+4+w , y-4-h ); + ctx.lineTo( x+4+w , y-4 ); + ctx.lineTo( x+11 , y-4 ); + ctx.lineTo( x , y ); + ctx.fill(); + ctx.beginPath(); + ctx.moveTo( x , y ); + ctx.lineTo( x+4 , y-11 ); + ctx.lineTo( x+4 , y-4-h ); + ctx.lineTo( x+4+w , y-4-h ); + ctx.lineTo( x+4+w , y-4 ); + ctx.lineTo( x+11 , y-4 ); + ctx.lineTo( x , y ); + ctx.stroke(); + t = t - 4 - h; + l = l + 4; + break; + case 'r': + ctx.beginPath(); + ctx.moveTo( x , y ); + ctx.lineTo( x-4 , y-11 ); + ctx.lineTo( x-4 , y-4-h ); + ctx.lineTo( x-4-w , y-4-h ); + ctx.lineTo( x-4-w , y-4 ); + ctx.lineTo( x-11 , y-4 ); + ctx.lineTo( x , y ); + ctx.fill(); + ctx.beginPath(); + ctx.moveTo( x , y ); + ctx.lineTo( x-4 , y-11 ); + ctx.lineTo( x-4 , y-4-h ); + ctx.lineTo( x-4-w , y-4-h ); + ctx.lineTo( x-4-w , y-4 ); + ctx.lineTo( x-11 , y-4 ); + ctx.lineTo( x , y ); + ctx.stroke(); + t = t - 4 - h; + l = l - 4 - w; + break; + } + break; + } + ctx.fontFamily = 'Verdana'; + ctx.fontSize = '10px'; + ctx.fillStyle = '#000000'; + ctx.fontWeight = 'normal'; + var hints = hint.split('
'); + for( var i=0 ; i') !== -1 || hints[i].indexOf('