Files
line_chart_demo/fusioncharts.charts.js
T

11130 lines
490 KiB
JavaScript

(function (factory) {
if (typeof module === 'object' && typeof module.exports !== "undefined") {
module.exports = factory;
} else {
factory(FusionCharts);
}
}(function (FusionCharts) {
/**!
* @license FusionCharts JavaScript Library
* Copyright FusionCharts Technologies LLP
* License Information at <http://www.fusioncharts.com/license>
*/
/**
* @private
* @module fusioncharts.renderer.javascript.charts.common
*/
/* jshint ignore: start */
FusionCharts.register ('module', ['private', 'modules.renderer.js-charts', function () {
FusionCharts.register ('module', ['private', 'modules.renderer.js-column2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('column2d', {
standaloneInit: true,
friendlyName: 'Column Chart',
creditLabel: creditLabel,
defaultDatasetType : 'column',
applicableDSList: { 'column': true },
singleseries: true
},chartAPI.sscartesian, {
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-column3d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('column3d', {
friendlyName: '3D Column Chart',
defaultDatasetType: 'column3d',
applicableDSList: { 'column3d': true },
defaultPlotShadow: 1,
creditLabel: creditLabel,
is3D: true,
standaloneInit: true,
hasLegend: false,
singleseries: true,
fireGroupEvent: true,
defaultZeroPlaneHighlighted: false
}, chartAPI.sscartesian3d, {
showplotborder: 0,
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-bar2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname);
chartAPI ('bar2d', {
friendlyName: 'Bar Chart',
isBar: true,
standaloneInit: true,
defaultDatasetType: 'bar2d',
creditLabel: creditLabel,
applicableDSList: { 'bar2d': true },
singleseries: true,
spaceManager: chartAPI.barbase
}, chartAPI.ssbarcartesian, {
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-bar3d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('bar3d', {
friendlyName: '3D Bar Chart',
defaultDatasetType: 'bar3d',
applicableDSList: { 'bar3d': true },
defaultPlotShadow: 1,
fireGroupEvent: true,
standaloneInit: true,
creditLabel: creditLabel,
is3D: true,
isBar: true,
singleseries: true,
defaultZeroPlaneHighlighted: false
}, chartAPI.ssbarcartesian3d, {
showplotborder: 0,
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-area2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('area2d', {
friendlyName: 'Area Chart',
standaloneInit: true,
creditLabel: creditLabel,
defaultDatasetType: 'area',
singleseries: true,
defaultPlotShadow: 0
}, chartAPI.sscartesian, {
enablemousetracking: true
}, chartAPI.areabase);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-line', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname);
chartAPI ('line', {
friendlyName: 'Line Chart',
standaloneInit: true,
creditLabel: creditLabel,
defaultPlotShadow: 1,
singleseries: true,
axisPaddingLeft: 0,
axisPaddingRight: 0,
defaultDatasetType: 'line'
}, chartAPI.sscartesian, {
zeroplanethickness: 1,
enablemousetracking: true,
zeroplanealpha: 40,
showzeroplaneontop: 0
}, chartAPI.areabase);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-pareto2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI,
pluck = lib.pluck,
pluckNumber = lib.pluckNumber,
componentDispose = lib.componentDispose,
preDefStr = lib.preDefStr,
NINETYSTRING = preDefStr.NINETYSTRING;
chartAPI ('pareto2d', {
defaultDatasetType: 'column2d',
singleseries : true,
creditLabel: creditLabel,
_createDatasets: function () {
var iapi = this,
components = iapi.components,
dataObj = iapi.jsonData,
is3d = iapi.is3D,
numberFormatter = components.numberFormatter,
dataset = dataObj.data || (dataObj.dataset && dataObj.dataset[0] && dataObj.dataset[0].data),
length = dataset && dataset.length,
chartAttr = dataObj.chart,
yAxis,
i,
datasetStore,
datasetObj,
defaultDatasetType = iapi.defaultDatasetType,
DsGroupClass,
Pareto = FusionCharts.get('component', ['dataset', 'Pareto']),
dsType,
DsClass,
GroupManager,
datasetJSON,
pareto = new Pareto (),
showCumulativeLine = pluckNumber (chartAttr.showcumulativeline, 1),
groupManagerName,
dataOnlyArr = [],
newDataLen,
oldDataLen,
datasetComponent,
TRUE_STRING = 'true',
ONE_STRING = '1',
setObj,
value;
if (!dataset) {
iapi.setChartMessage();
return;
}
for (i = 0; i< length; i++) {
setObj = dataset[i];
value = numberFormatter.getCleanValue (setObj.value);
if (value !== null && !(setObj.vline === TRUE_STRING || setObj.vline === true ||
setObj.vline === 1 || setObj.vline === ONE_STRING)) {
dataOnlyArr.push (setObj);
}
}
iapi.config.categories = dataOnlyArr;
datasetStore = components.dataset || (components.dataset = []);
datasetJSON = dataOnlyArr;
dsType = pluck (defaultDatasetType);
dsType = dsType && dsType.toLowerCase ();
DsGroupClass = FusionCharts.register('component', ['datasetGroup', 'column']);
GroupManager = components[groupManagerName] = new DsGroupClass ();
GroupManager.chart = iapi;
GroupManager.init ();
/// get the DsClass
if (is3d) {
DsClass = FusionCharts.get('component', ['dataset', 'Column3d']);
}
else {
DsClass = FusionCharts.get('component', ['dataset', 'Column']);
}
if (DsClass) {
datasetComponent = datasetStore[0];
if (!datasetComponent) {
// create the dataset Object
datasetObj = new DsClass ();
datasetStore.push (datasetObj);
datasetObj.chart = iapi;
datasetObj.index = i;
// add to group manager
GroupManager && GroupManager.addDataSet (datasetObj, 0, 0);
pareto.init (datasetObj, datasetJSON, defaultDatasetType);
}
else {
newDataLen = datasetJSON.length;
oldDataLen = datasetComponent.components.data.length;
if (newDataLen < oldDataLen) {
datasetComponent.removeData(newDataLen, oldDataLen - newDataLen);
}
datasetComponent.JSONData = {
data: datasetJSON
};
pareto.configure.call(datasetComponent);
}
}
yAxis = components.yAxis[1];
if (showCumulativeLine) {
yAxis && yAxis.setAxisConfig({
drawLabels: true,
drawPlotLines: true,
drawAxisName: true,
drawAxisLine: true,
drawPlotBands: true,
drawTrendLines: true,
drawTrendLabels: true
});
yAxis.show();
DsClass = FusionCharts.get('component', ['dataset', 'line']);
datasetComponent = datasetStore[1];
if (!datasetComponent) {
datasetObj = new DsClass ();
datasetStore.push (datasetObj);
datasetObj.chart = iapi;
datasetObj.index = i;
pareto.init (datasetObj, datasetJSON, 'line');
}
else {
newDataLen = datasetJSON.length;
oldDataLen = datasetComponent.components.data.length;
if (newDataLen < oldDataLen) {
datasetComponent.removeData(newDataLen, oldDataLen - newDataLen);
}
datasetComponent.JSONData = {
data: datasetJSON
};
pareto.configure.call(datasetComponent);
}
}
else {
datasetComponent = datasetStore[1];
if (datasetComponent) {
componentDispose.call(datasetComponent);
datasetStore.pop();
}
if (yAxis) {
yAxis.setAxisConfig({
drawLabels: false,
drawPlotLines: false,
drawAxisName: false,
drawAxisLine: false,
drawPlotBands: false,
drawTrendLines: false,
drawTrendLabels: false
});
yAxis.hide();
}
}
},
_setCategories: function () {
var iapi = this,
components = iapi.components,
dataObj = iapi.jsonData,
dataset = dataObj.dataset,
numberFormatter = components.numberFormatter,
xAxis = components.xAxis,
categories = dataObj.data || (dataset && dataset[0].data) || [],
catArr = [],
catObj,
catLen = categories.length,
i,
vLine = {},
count = 0,
value;
for (i = 0; i < catLen; i++) {
catObj = categories[i];
value = numberFormatter.getCleanValue (catObj.value, true);
if (catObj.vline === 'true' || catObj.vline === '1' || catObj.vline === 1 ||
catObj.vline === true) {
vLine[count] = catObj;
}
else {
if (value === null) {
continue;
}
else {
catObj.value = value;
catArr.push (catObj);
}
}
count++;
}
catArr.sort (function (a, b) {
return b.value - a.value;
});
for (i in vLine) {
catArr.splice(i, 0, vLine[i]);
}
xAxis[0].setCategory (catArr);
},
standaloneInit: true,
hasLegend: false,
isPercentage: true
}, chartAPI.msdybasecartesian, {
enablemousetracking: true,
plotfillalpha: NINETYSTRING
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-pareto3d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI,
preDefStr = lib.preDefStr,
NINETYSTRING = preDefStr.NINETYSTRING;
chartAPI ('pareto3d', {
standaloneInit: true,
is3D: true,
friendlyName: '3D Pareto Chart',
creditLabel: creditLabel,
fireGroupEvent: true,
defaultPlotShadow: 1,
singleseries : true,
hasLegend: false,
defaultDatasetType: 'column3d',
_createDatasets: chartAPI.pareto2d,
_setCategories: chartAPI.pareto2d,
isPercentage: true
}, chartAPI.msdybasecartesian3d, {
plotfillalpha: NINETYSTRING,
use3dlineshift: 1,
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-pie2d', function () {
var global = this,
lib = global.hcLib,
hasTouch = lib.hasTouch,
win = global.window,
doc = win.document,
//strings
BLANKSTRING = lib.BLANKSTRING,
//add the tools thats are requared
pluck = lib.pluck,
pluckNumber = lib.pluckNumber,
toRaphaelColor = lib.toRaphaelColor,
supportsTouch = 'createTouch' in doc,
// The devices which both touch and pointer.
supportsOnlyTouch = (supportsTouch &&
! (win.navigator.maxTouchPoints ||
win.navigator.msMaxTouchPoints)),
ROLLOVER = 'DataPlotRollOver',
ROLLOUT = 'DataPlotRollOut',
EVENTARGS = 'eventArgs',
each = lib.each,
TRANSFORMSTR = 't',
plotEventHandler = lib.plotEventHandler,
schedular = lib.schedular,
priorityList = lib.priorityList,
/*
* Function to get the exact visual position (top, left) of a element
*/
getContainerPosition = function (el, noscroll) {
var p = {
left: 0,
top: 0
},
clientRect;
// for newer browsers
if (el.getBoundingClientRect) {
clientRect = el.getBoundingClientRect ();
p.top = clientRect.top + (win.pageYOffset || doc.scrollTop || 0) - (doc.clientTop || 0);
p.left = clientRect.left + (win.pageXOffset || doc.scrollLeft || 0) - (doc.clientLeft || 0);
}
// for very older browsers
else {
// iterate till the root element of the DOM to get the exact position of the element
while (el) {
p.left += (el.offsetLeft || 0);
p.top += (el.offsetTop || 0);
if (el !== doc.body && el !== doc.documentElement && !noscroll) {
p.left -= (el.scrollLeft || 0);
p.top -= (el.scrollTop || 0);
}
el = el.offsetParent;
}
}
return p;
},
docMode8 = win.document.documentMode === 8,
UNDEFINED,
HIDDEN = 'hidden',
VISIBLE = docMode8 ? 'visible' : '',
M = 'M',
L = 'L',
math = Math,
mathSin = math.sin,
mathCos = math.cos,
mathRound = math.round,
mathATan2 = math.atan2,
mathMin = math.min,
mathMax = math.max,
mathAbs = math.abs,
mathPI = math.PI,
mathCeil = math.ceil,
mathFloor = math.floor,
deg2rad = mathPI / 180,
rad2deg = 180 / mathPI,
pi = Math.PI,
piBy2 = pi / 2,
pi2 = 2 * pi,
pi3By2 = pi + piBy2,
convertColor = lib.graphics.convertColor,
// COLOR_TRANSPARENT = lib.COLOR_TRANSPARENT,
POSITION_BOTTOM = lib.POSITION_BOTTOM,
POSITION_RIGHT = lib.POSITION_RIGHT,
chartAPI = lib.chartAPI,
COMMASTRING = lib.COMMASTRING,
ZEROSTRING = lib.ZEROSTRING,
ONESTRING = lib.ONESTRING,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
getClickArcTangent = function (x, y, center, ref, pieYScale) {
return mathATan2 ((y - center[1] - ref.top) / pieYScale, x - center[0] - ref.left);
};
chartAPI ('pie2d', {
friendlyName: 'Pie Chart',
standaloneInit: true,
defaultSeriesType: 'pie',
defaultPlotShadow: 1,
reverseLegend: 1,
alignCaptionWithCanvas: 0,
sliceOnLegendClick: true,
isSingleSeries: true,
dontShowLegendByDefault: true,
defaultDatasetType : 'Pie2D',
applicableDSList: {
'Pie2D': true
},
defaultZeroPlaneHighlighted: false,
creditLabel: creditLabel,
_plotDragMove: function (dx, dy, x, y, evt) {
var o = this,
plotItem = o.data ('plotItem'),
chart = plotItem.chart,
seriesData = plotItem.seriesData,
datasets = chart.components.dataset,
datasetConf = datasets[0].config,
angle;
if (isNaN(dx) || isNaN(dy) || (!datasetConf.enableRotation || seriesData.singletonCase) ||
seriesData.isRightClicked) {
return;
}
angle = getClickArcTangent.call (evt, x, y, seriesData.pieCenter,
seriesData.chartPosition, 1);
if (!seriesData.isRotating) {
seriesData.dragStartAngle !== angle && (seriesData.isRotating = true);
/**
* This event is fired on drag rotation start of pie chart.
*
* @event FusionCharts#rotationStart
* @group chart:pie
*
* @param { number} startingAngle - Gives the value of the
* startingAngle of the chart, when the chart
* starts rotating
*/
global.raiseEvent ('RotationStart', {
startingAngle: (seriesData._rotationalStartAngle = chart._startingAngle())
},
chart.chartInstance
);
}
datasetConf.startAngle += (angle - seriesData.dragStartAngle);
seriesData.dragStartAngle = angle;
seriesData.moveDuration = 0;
// Check if already there is an initiation for visual updates.
if (!datasetConf.updateInited) {
// set the flag to truth value.
datasetConf.updateInited = true;
setTimeout(chart._batchRotate || (chart._batchRotate = function () {
// visually update the chart
chart._rotate ();
// turn the flag to falsy after an interval
datasetConf.updateInited = false;
}), 50);
}
},
_plotDragStart: function (x, y, evt) {
var o = this,
plotItem = o.data ('plotItem'),
chart = plotItem.chart,
seriesData = plotItem.seriesData,
datasets = chart.components.dataset,
datasetConf = datasets[0].config,
startingAng = -datasetConf.startAngle,
angle;
seriesData.isRightClicked = !hasTouch && (evt.button !== 0 && evt.button !== 1) ? true : false;
if (!datasetConf.enableRotation || seriesData.isRightClicked) {
return;
}
seriesData.isRotating = false;
//Calculate chart's current position. Chart cotainer may be moved mean while
seriesData.chartPosition = getContainerPosition (chart.linkedItems.container);
angle = getClickArcTangent.call (evt, x, y, seriesData.pieCenter,
seriesData.chartPosition, 1);
seriesData.dragStartAngle = angle;
seriesData.startingAngleOnDragStart = startingAng;
},
_plotDragEnd: function (e) {
var o = this,
rotaionalEndAngle,
plotItem = o.data ('plotItem'),
chart = plotItem.chart,
chartConfig = chart.config,
seriesData = plotItem.seriesData;
if (seriesData.isRightClicked) {
return;
}
chartConfig.clicked = true;
if (!chart.disposed) {
/*extend2 (chart.logic.chartInstance.jsVars._reflowData,
reflowUpdate, true);*/
chart._rotate ();
}
!seriesData.isRotating && chart._plotGraphicClick.call (plotItem.graphic, e);
delete chartConfig.clicked;
if (seriesData.isRotating) {
/* The events mouseup, dragend and click are raised in order. In order
* to update the flag isRotating to false post click event, setTimeout
* called, to take immediate effect, is programmed to update the flag.
* Thus, the flag gets updated post the series of events, in effect.
* NB: Click event is subscribed conditionally.
*/
setTimeout (function () {
seriesData.isRotating = false;
}, 0);
/**
* This event is fired on drag rotation end of pie chart.
*
* @event FusionCharts#rotationEnd
* @group chart:pie
*
* @param { number} changeInAngle - Gives the value by how much
* the chart was rotated
* @param { number} startingAngle - Gives the value of the
* startingAngle of the chart on rotation end.
*/
global.raiseEvent ('RotationEnd', {
startingAngle: (rotaionalEndAngle = chart._startingAngle()),
changeInAngle: (rotaionalEndAngle - seriesData._rotationalStartAngle)
}, chart.chartInstance);
}
// !chart.isHovered && chart.onPlotHover (o, false);
},
_plotRollOver: function (e) {
var ele = this,
plotItem = ele.plotItem || ele.data ('plotItem'),
chart = plotItem.chart,
conf = chart.components.dataset[0].config,
innerDiameter,
centerLabelConfig,
centerLabelText;
if (!conf.isRotating) {
plotEventHandler.call (ele, chart, e, ROLLOVER);
chart.onPlotHover (ele, true);
}
conf.isHovered = true;
if (innerDiameter = plotItem.innerDiameter) {
(centerLabelConfig = plotItem.centerLabelConfig) &&
(centerLabelText = centerLabelConfig.label) &&
chart.drawDoughnutCenterLabel (centerLabelText, plotItem.center[0],
plotItem.center[1], innerDiameter, innerDiameter, centerLabelConfig, false);
}
},
onPlotHover: function (plot, hover) {
var plotItem = plot.data ('plotItem'),
center = plotItem.center,
rolloverProps = plotItem.rolloverProperties || {},
color = hover ? rolloverProps.color : plotItem.color,
borderWidth = hover ? rolloverProps.borderWidth : plotItem.borderWidth,
borderColor = hover ? rolloverProps.borderColor : plotItem.borderColor;
if (color) {
if (hover) {
color.cx = center[0];
color.cy = center[1];
color.r = plotItem.radius;
}
rolloverProps.enabled && plotItem.graphic.attr ( {
fill: toRaphaelColor(color),
'stroke-width': borderWidth,
'stroke': borderColor
});
}
},
_plotRollOut: function (e) {
var ele = this,
plotItem = ele.plotItem || ele.data ('plotItem'),
chart = plotItem.chart,
seriesData = chart.components.dataset[0],
conf = seriesData.config,
/** @todo: enable doughnut center label feature related to enableMultiSlicing */
enableMultiSlicing = true, //seriesData.enableMultiSlicing,
innerDiameter,
centerLabelConfig,
centerLabelText;
if (!conf.isRotating) {
plotEventHandler.call (ele, chart, e, ROLLOUT);
chart.onPlotHover (ele, false);
}
conf.isHovered = false;
if (innerDiameter = plotItem.innerDiameter) {
(centerLabelConfig = !enableMultiSlicing ?
conf.lastCenterLabelConfig : conf.centerLabelConfig) &&
((centerLabelText = centerLabelConfig.label) || !centerLabelText) &&
chart.drawDoughnutCenterLabel (centerLabelText, plotItem.center[0],
plotItem.center[1], innerDiameter, innerDiameter, centerLabelConfig, false);
}
},
_rotate: function () {
var _ringPath,
_rotateAttrs,
chart = this,
dataOptions = chart.components.dataset[0],
conf = dataOptions.config,
plotData = dataOptions.components.data,
chartConfig = chart.config,
piePlotOptions = conf.piePlotOptions,
startAngle = (conf.startAngle || 0) % pi2,
slicedOffset = conf.slicingDistance,
seriesConf = dataOptions.config,
valueTotal = seriesConf.valueTotal,
factor = pi2 / valueTotal,
cx = chartConfig.canvasLeft + chartConfig.canvasWidth * 0.5,
cy = chartConfig.canvasTop + chartConfig.canvasHeight * 0.5,
r = conf.pieMinRadius,
r2 = (piePlotOptions.innerSize || 0) * 0.5,
val,
angle,
angle1,
angle2,
setConfig,
setGraphics,
i;
angle1 = angle2 = startAngle;
for (i = 0; i < plotData.length; i += 1) {
setConfig = plotData[i].config;
setGraphics = plotData[i].graphics;
val = setConfig.y;
if (val === null || val === undefined) {
continue;
}
// plotItem = plotItems[i];
angle2 = angle1;
// This conditional assignment of value 2 * pi is to by-pass a
// computational error inherent to any computer system, which
// happens here for certain values in singleton cases.
angle1 -= !seriesConf.singletonCase ? val * factor : pi2;
angle = (angle1 + angle2) * 0.5;
setConfig.angle = angle;
setConfig.transX = mathCos (angle) * slicedOffset;
setConfig.transY = mathSin (angle) * slicedOffset;
setConfig.slicedTranslation = TRANSFORMSTR +
(mathCos (angle) * slicedOffset) + COMMASTRING +
(mathSin (angle) * slicedOffset);
if (!(_rotateAttrs = setConfig._rotateAttrs)) {
_rotateAttrs = setConfig._rotateAttrs = {
ringpath: [],
transform: BLANKSTRING
};
}
_ringPath = _rotateAttrs.ringpath;
_ringPath[0] = cx;
_ringPath[1] = cy;
_ringPath[2] = r;
_ringPath[3] = r2;
_ringPath[4] = angle1;
_ringPath[5] = angle2;
_rotateAttrs.transform = setConfig.sliced ? setConfig.slicedTranslation : BLANKSTRING;
setGraphics.element.attr (_rotateAttrs);
}
chart.placeDataLabels (true, plotData, conf);
},
getPlotData: function (id, slicedState) {
var chart = this,
dataset = chart.components.dataset[0],
data = dataset.components.data[id].config,
userData = dataset.config.userData || (dataset.config.userData = []),
plotData,
value,
prop;
if (!userData[id]) {
plotData = userData[id] = {};
for (prop in data) {
typeof (value = data[prop]) !== 'object' &&
typeof (value) !== 'function' &&
prop.indexOf('_') !== 0 && (plotData[prop] = value);
}
plotData.value = plotData.y;
plotData.categoryLabel = plotData.label = plotData.seriesName;
delete plotData.y;
delete plotData.total;
delete plotData.doNotSlice;
delete plotData.name;
delete plotData.seriesName;
delete plotData.centerAngle;
delete plotData.showInLegend;
delete plotData.angle;
delete plotData.endAngle;
delete plotData.isVisible;
delete plotData.setColor;
delete plotData.slicedTranslation;
delete plotData.startAngle;
delete plotData.transX;
delete plotData.transY;
delete plotData.pValue;
}
else {
plotData = userData[id];
}
plotData.sliced = slicedState;
return plotData;
},
_plotGraphicClick: function (evt) {
var prevTextPos,
o = this.element || this,
plotItem = o.plotItem || o.data ('plotItem'),
elData = o.data('eventArgs') || {},
chart = plotItem.chart,
index = plotItem.index,
// elData = o.data ('eventArgs') || { },
chartSeriesData = chart.components.dataset[0],
seriesData = chartSeriesData.config,
enableMultiSlicing = seriesData.enableMultiSlicing,
set = chartSeriesData.components.data[index],
setGraphics = set.graphics,
setConfig = set.config,
doNotSlice = setConfig.doNotSlice,
atleastOneOtherSliced,
graphic,
dataLabel,
sliced,
slicedTranslation = setConfig.slicedTranslation,
transX,
transY,
connector,
connectorPath,
lastSliceTimeStamp,
minTimeForNextSlice = 400,
animationObj = chart.get('config', 'animationObj'),
animationDuration = animationObj.duration || 200,
mainElm = animationObj.dummyObj,
animObj = animationObj.animObj,
animType = animationObj.animType,
eventArgs,
path;
!seriesData.isRotating && plotEventHandler.call (o, chart, evt);
if (seriesData.isRotating || seriesData.singletonCase || doNotSlice) {
return;
}
// If 'enableMultiSlicing' is false, sliceInOtherPies is called only once
// for the clicked pie. From within sliceInOtherPies, 'enableMultiSlicing'
// is set to true, method plotGraphicClick is called once for each
// sliced pie and finally, value of 'enableMultiSlicing' is reset to false.
// Value of multiSliced is always false for plotGraphicClick call from
// sliceInOtherPies. However, its value may be true or false, by the
// return value of the sliceInOtherPies.
atleastOneOtherSliced = !enableMultiSlicing && chart.sliceInOtherPies (index);
if ((sliced = setConfig.sliced) && atleastOneOtherSliced) {
return;
}
//
// In chrome of hybrid touch devices (touch + mouse), slice is getting fired twice for single touch
// To prevent this cancle slice event within very sort period
//
if (supportsTouch && !supportsOnlyTouch) {
lastSliceTimeStamp = new Date ().getTime ();
if (plotItem.lastSliceTimeStamp &&
(lastSliceTimeStamp - plotItem.lastSliceTimeStamp) < minTimeForNextSlice) {
return;
}
// store current time stamp
plotItem.lastSliceTimeStamp = lastSliceTimeStamp;
}
graphic = setGraphics.element;
connector = setGraphics.connector;
dataLabel = setGraphics.label || setGraphics.dataLabel;
slicedTranslation = typeof slicedTranslation === 'object' ? 't' + slicedTranslation : slicedTranslation;
connectorPath = setConfig.connectorPath;
transX = (sliced ? -1 : 1) * setConfig.transX;
transY = (sliced ? -1 : 1) * setConfig.transY;
eventArgs = graphic.data(EVENTARGS) || graphic.data(EVENTARGS, {});
/**
* This event is fired when a pieSlice in pie-chart starts slicing transition.
*
* @event FusionCharts#slicingStart
* @group chart:pie-slice
*
* @param { object} data - Contains the values for the following
* attributes borderColor, borderWidth,
* categoryLabel, dashStyle, displayValue,
* hoverEffects (boolean), label, link,
* rolloverProperties (boolean), sliced (boolean),
* toolText, value (value of the sliced object)
* @param { boolean} slicedState - Tells the state of the slice before
* transition begins. The value is true for
* sliced-out state and false for sliced-in state.
*/
global.raiseEvent ('slicingStart', {
slicedState: sliced,
dataIndex: ('index' in elData) && elData.index,
data: chart.getPlotData (index, sliced)
}, chart.chartInstance);
graphic.animateWith(mainElm, animObj, {
transform: sliced ? 't0,0' : slicedTranslation
}, animationDuration, animType, function () {
/**
* This event is fired when a pieSlice in pie-chart ends slicing transition.
*
* @event FusionCharts#slicingEnd
* @group chart:pie-slice
*
* @param { object} data - Contains the values for the following
* attributes borderColor, borderWidth,
* categoryLabel, dashStyle, displayValue,
* hoverEffects (boolean), label, link,
* rolloverProperties (boolean), sliced (boolean),
* toolText, value (value of the sliced object)
* @param { boolean} slicedState - Tells the state of the slice
* before transition begins. The value is true
* for sliced-out state and false for
* sliced-in state.
*/
global.raiseEvent ('slicingEnd', {
slicedState: sliced,
dataIndex: ('index' in elData) && elData.index,
data: chart.getPlotData (index, sliced)
}, chart.chartInstance);
});
if (dataLabel && dataLabel.x) {
if (!(prevTextPos = dataLabel.data('textPos'))) {
prevTextPos = dataLabel.data('textPos', {
x: dataLabel.x,
y: dataLabel.y
});
}
dataLabel.animateWith(mainElm, animObj, {
x: dataLabel.x + (sliced ? 0 : transX)
}, animationDuration, animType);
prevTextPos.x = dataLabel.x + (sliced ? 0 : transX);
}
if (connectorPath) {
path = connectorPath.slice(0);
path[1] += transX;
path[2] += transY;
path[4] += transX;
path[6] += transX;
connector.animateWith(mainElm, animObj, {
path: path
}, animationDuration, animType);
setConfig.connectorPath = path;
}
// updating the slicing information.
eventArgs.isSliced = (sliced = setConfig.sliced = !sliced);
/** @todo: Enable center label feature related to enableMultiSlicing */
//if ((innerDiameter = plotItem.innerDiameter) && !enableMultiSlicing) {
// (centerLabelConfig = sliced && plotItem.centerLabelConfig.label ?
// plotItem.centerLabelConfig : chartSeriesData.centerLabelConfig) &&
// (centerLabelText = centerLabelConfig.label) &&
// chart.drawDoughnutCenterLabel (centerLabelText, plotItem.center[0], plotItem.center[1],
// innerDiameter, innerDiameter, centerLabelConfig, true);
//}
return sliced;
},
sliceInOtherPies: function (mainPieId) {
var chart = this,
series = chart.components.dataset[0],
plotItems = series.components.data,
i = plotItems.length,
numSliced = 0,
plot;
series.enableMultiSlicing = true;
while (i--) {
i !== mainPieId && (plot = plotItems[i]).config.sliced && ++numSliced &&
chart._plotGraphicClick.call (plot.graphics);
}
series.enableMultiSlicing = false;
// Returns true when at least one other pie is sliced
return !!numSliced;
},
placeDataLabels: (function () {
/*
* Pie Helper Functions.
*/
var sortArrayByPoint = function (a, b) {
return a.point.value - b.point.value;
},
sortArrayByAngle = function (a, b) {
return a.angle - b.angle;
},
alignments = ['start', 'start', 'end', 'end'],
alignCenter = 'middle',
ySign = [-1, 1, 1, -1],
xSign = [1, 1, -1, -1];
return function (isRotating, plotItems, plot, seriesData) {
var chart = this,
chartConfig = chart.config,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
legendConfig = chartComponents.legend.config,
legendWidth = legendConfig.symbolWidth,
legendAlign = legendConfig.align,
legendBorderwidth = legendConfig.borderWidth,
datasetGraphics = dataSet.graphics,
plotOptions = dataSet.config,
canvasLeft = chartConfig.canvasLeft,
canvasTop = chartConfig.canvasTop,
canvasWidth = chartConfig.canvasWidth,
cx = canvasLeft + chartConfig.canvasWidth * 0.5,
cy = canvasTop + chartConfig.canvasHeight * 0.5,
smartLabel = chart.linkedItems.smartLabel,
dataLabelsOptions = plotOptions.dataLabelOptions,
style = dataLabelsOptions.style,
lineHeight = pluckNumber (mathCeil (parseFloat (style.lineHeight)), 12),
placeInside = (plotItems.length === 1 ? chartConfig.singletonPlaceValue : false),
skipOverlapLabels = dataLabelsOptions.skipOverlapLabels,
manageLabelOverflow = dataLabelsOptions.manageLabelOverflow,
connectorPadding = dataLabelsOptions.connectorPadding,
distanceOption = dataLabelsOptions.distance,
remainingHeight,
seriesCenter = seriesData && seriesData.metrics || [
cx,
cy,
2 * plotOptions.pieMinRadius,
(plotOptions.innerSize || 0)
],
centerY = seriesCenter[1],
centerX = seriesCenter[0],
radius = seriesCenter[2] * 0.5,
// divide the points into quarters for anti collision
quarters = [
[], // top right
[], // bottom right
[], // bottom left
[] // top left
],
plotLeft = canvasLeft,
plotTop = canvasTop,
plotWidth = canvasWidth,
dataLabelsRadius = (plot.labelsRadius = radius + distanceOption),
labelFontSize = parseInt (style.fontSize, 10),
labelHeight = labelFontSize,
halfLabelHeight = labelHeight / 2,
xDisplacement = [connectorPadding,
connectorPadding, -connectorPadding, -connectorPadding
],
maxLabels = (plot.labelsMaxInQuadrant = mathFloor (
dataLabelsRadius / labelHeight)),
isSmartLineSlanted = dataLabelsOptions.isSmartLineSlanted,
innerRadius = seriesCenter[3] / 2,
align,
i,
labelWidth,
j,
oriY,
maxYmayHave,
spaceRequired,
length,
k,
sliced,
x1,
x2,
x3,
x4,
y1,
y2,
y3,
points,
point,
angle,
connector,
connectorPath,
excess,
excessArr,
labelQuadrantHeight,
maxQuadrantLabel,
dataLabel,
quarter,
transX,
transY,
smartLabelObj,
slicedTranslation,
centerDistance,
sameSideQuadrentLastY = Number.POSITIVE_INFINITY,
extraLineSpaceComp,
lPoint,
pointConfig,
pointGraphics,
leftClubbedQuadrent = [],
rightClubbedQuadrent = [],
prevTextPos,
prevConnectorPos,
animationObj = chart.get('config', 'animationObj'),
// animating on every rotations slows the performance. @revisit.
animationDuration = isRotating ? 0 : (animationObj.duration || 0),
mainElm = animationObj.dummyObj,
animObj = animationObj.animObj,
animType = animationObj.animType,
plotDragMove = chart._plotDragMove,
plotDragStart = chart._plotDragStart,
plotDragEnd = chart._plotDragEnd,
plotRollOver = chart._plotRollOver,
plotRollOut = chart._plotRollOut,
paper = chartComponents.paper,
dataLabelContainer = datasetGraphics.dataLabelContainer,
_textAttrs,
_textCss;
// run parent method
/**^
* Introduced the isRotating argument too to detect
* whether this is called during rotation.
*/
smartLabel.useEllipsesOnOverflow(chartConfig.useEllipsesWhenOverflow);
if (!isRotating) {
//do not set the style every time
// Do it for first time
smartLabel.setStyle (style);
}
// arrange points for detection collision
// Creates an array of quarter containing labels of each
// quarter if there has only one label the draw it inside
if (plotItems.length == 1 && !innerRadius && placeInside) {
point = plotItems[0];
if ((_textAttrs = point.config._textAttrs).text) {
pointGraphics = point.graphics;
pointConfig = point.config;
dataLabel = pointGraphics.label;
point.slicedTranslation = [plotLeft, plotTop];
_textAttrs['text-anchor'] = alignCenter;
_textAttrs.x = 0;
_textAttrs.y = 0;
_textAttrs.transform = ['t', centerX, centerY];
if (!dataLabel) {
dataLabel = pointGraphics.label = paper
.text(_textAttrs, _textCss, dataLabelContainer)
.drag(plotDragMove, plotDragStart, plotDragEnd)
.hover(plotRollOver, plotRollOut);
}
else {
dataLabel.animateWith (mainElm, animObj, _textAttrs, animationDuration, animType);
}
dataLabel.x = centerX;
dataLabel.data('textPos', {
x: centerX,
y: centerY
})
.data('plotItem', _textAttrs.plotItem)
.data(EVENTARGS, _textAttrs.eventArgs);
if (pointConfig.y !== null && pointConfig.y !== undefined) {
dataLabel.show ();
}
if (pointGraphics.connector) {
pointGraphics.connector.attr({
path: []
});
}
}
}
else {
if (placeInside) {
centerDistance = innerRadius + ((radius - innerRadius) / 2);
each (plotItems, function (point) {
pointConfig = point.config;
if ((_textAttrs = pointConfig._textAttrs).text) {
pointGraphics = point.graphics;
dataLabel = pointGraphics.label;
if (pointConfig.y !== null && pointConfig.y !== undefined) {
connector = pointGraphics.connector;
connector && connector.show();
dataLabel && dataLabel.show ();
}
_textAttrs.transform = 't0,0';
angle = pointConfig.angle;
y3 = centerY + (centerDistance *
mathSin (angle));
//previous implementation adds halfLebelHeight - 2 which makes the
//label aligned to top instead middle [RED - 1717]
/*y3 = centerY + (centerDistance *
mathSin (angle)) + halfLabelHeight - 2;*/
x3 = centerX + (centerDistance * mathCos (angle));
_textAttrs._x = x3;
_textAttrs._y = y3;
if (point.sliced) {
slicedTranslation = point.slicedTranslation;
transX = slicedTranslation[0] - plotLeft;
transY = slicedTranslation[1] - plotTop;
x3 = x3 + transX;
y3 = y3 + transY;
}
_textAttrs['text-anchor'] = alignCenter;
_textAttrs.x = 0;
_textAttrs.y = 0;
_textAttrs.transform = ['t', x3, y3];
if (!dataLabel) {
dataLabel = pointGraphics.label = paper
.text(_textAttrs, _textCss, dataLabelContainer)
.drag(plotDragMove, plotDragStart, plotDragEnd)
.hover(plotRollOver, plotRollOut);
}
else {
dataLabel.animateWith (mainElm, animObj, _textAttrs, animationDuration,
animType);
}
dataLabel.x = _textAttrs._x;
// storing original x value
// to use while slicing in (IE Issue original
// x get changed form animate)
dataLabel.x = _textAttrs._x;
dataLabel.y = _textAttrs._y;
dataLabel
.data('plotItem', _textAttrs.plotItem)
.data(EVENTARGS, _textAttrs.eventArgs);
if (_textAttrs.visibility === VISIBLE) {
dataLabel.show();
}
}
});
}
else { //outside
for (i = plotItems.length - 1; i >= 0; i --) {
point = plotItems[i];
pointConfig = point.config;
_textAttrs = pointConfig._textAttrs;
if (!(_textAttrs.text = pointConfig.displayValue)) {
continue;
}
pointGraphics = point.graphics;
if (pointConfig.y !== null && pointConfig.y !== undefined) {
dataLabel = pointGraphics.label;
connector = pointGraphics.connector;
connector && connector.show();
dataLabel && dataLabel.show ();
}
_textAttrs.text = pointConfig.displayValue;
_textAttrs.transform = 't0,0';
angle = pointConfig.angle % pi2;
if (angle < 0) {
angle = pi2 + angle;
}
// Calculate bottom right quarter labels
if (angle >= 0 && angle < piBy2) {
quarter = 1;
} else
// Calculate bottom left quarter labels
if (angle < pi) {
quarter = 2;
} else
// Calculate top left quarter labels
if (angle < pi3By2) {
quarter = 3;
}
// Calculate top right quarter labels
else {
quarter = 0;
}
// Now put labels according to each quarter
quarters[quarter].push ( {
point: point,
angle: angle
});
}
i = k = 4;
//if excess then remove the low value slice first
while (i--) {
if (skipOverlapLabels) {
// Find labels can fit into the quarters or not
excess = quarters[i].length - maxLabels;
if (excess > 0) {
// sort by point.value
quarters[i].sort (sortArrayByPoint);
// remove extra data form the array
// which labels can not be fitted into
// the quarters
excessArr = quarters[i].splice (0, excess);
//hide all removed labels
for (j = 0, length = excessArr.length; j < length; j += 1) {
point = excessArr[j].point;
_textAttrs = point.config._textAttrs;
pointGraphics = point.graphics;
if (pointGraphics.label) {
pointGraphics.label.attr('visibility', HIDDEN);
}
if (pointGraphics.connector) {
pointGraphics.connector.attr ( {
visibility: HIDDEN
});
}
}
}
}
// now we sort the data labels by its label angle
quarters[i].sort (sortArrayByAngle);
}
maxQuadrantLabel = mathMax (
quarters[0].length,
quarters[1].length,
quarters[2].length,
quarters[3].length
);
labelQuadrantHeight = mathMax (
mathMin (maxQuadrantLabel, maxLabels) * labelHeight,
dataLabelsRadius + labelHeight
);
// Club the first and second quadrent as they appear in the right side of the
// chart and club the other two. Clubbing here means merge all the points together
// so that marginal quadrent case are handled like one point at first quadrent and
// second point at another quadrent but placed closely.
rightClubbedQuadrent = quarters[0].concat (quarters[1]);
leftClubbedQuadrent = quarters[2].concat (quarters[3]);
for (i = rightClubbedQuadrent.length - 1; i >= 0; i--) {
lPoint = rightClubbedQuadrent[i].point.config;
// Clear if any previous values are stored
delete lPoint.clearance;
delete lPoint.clearanceShift;
oriY = mathAbs (labelQuadrantHeight * mathSin (lPoint.angle));
if (Math.abs (sameSideQuadrentLastY - oriY) < lineHeight * 2) {
lPoint.clearance = 0;
rightClubbedQuadrent[i+1].point.clearanceShift = lineHeight / 2;
}
sameSideQuadrentLastY = oriY;
}
sameSideQuadrentLastY = Number.POSITIVE_INFINITY;
for (i = 0, length = leftClubbedQuadrent.length; i < length; i++) {
lPoint = leftClubbedQuadrent[i].point.config;
// Clear if any previous values are stored
delete lPoint.clearance;
delete lPoint.clearanceShift;
oriY = mathAbs (labelQuadrantHeight * mathSin (lPoint.angle));
if (Math.abs (sameSideQuadrentLastY - oriY) < lineHeight * 2) {
lPoint.clearance = 0;
leftClubbedQuadrent[i-1].point.clearanceShift = lineHeight / 2;
}
sameSideQuadrentLastY = oriY;
}
// reverse 1st and 3rd quardent points
quarters[1].reverse ();
quarters[3].reverse ();
while (k--) {
points = quarters[k];
length = points.length;
if (!skipOverlapLabels) {
if (length > maxLabels) {
labelHeight = labelQuadrantHeight / length;
}
else {
labelHeight = labelFontSize;
}
halfLabelHeight = labelHeight / 2;
}
//1st pass
//place all labels at 1st quarter
// calculate the total available space to put labels
spaceRequired = length * labelHeight;
// calculate the remaining height
remainingHeight = labelQuadrantHeight;
//place all child point
for (i = 0; i < length; i += 1, spaceRequired -= labelHeight) {
// Get the y position of the label (radius
// where data label is to draw)
oriY = mathAbs (labelQuadrantHeight * mathSin (points[i].angle));
if (remainingHeight < oriY) {
oriY = remainingHeight;
}
else if (oriY < spaceRequired) {
oriY = spaceRequired;
}
remainingHeight = (points[i].oriY = oriY) - labelHeight;
}
//2nd pass (reverse)
align = alignments[k];
//place all labels at 1st quarter
maxYmayHave = labelQuadrantHeight - ((length - 1) * labelHeight);
remainingHeight = 0;
//place all child point
for (i = points.length - 1; i >= 0; i -= 1, maxYmayHave += labelHeight) {
point = points[i].point;
angle = points[i].angle;
pointConfig = point.config;
_textAttrs = pointConfig._textAttrs;
if (!_textAttrs.text) {
continue;
}
_textCss = pointConfig._textCss;
pointGraphics = point.graphics;
sliced = pointConfig.sliced;
dataLabel = pointGraphics.label;
oriY = mathAbs (labelQuadrantHeight * mathSin (angle));
if (oriY < remainingHeight) {
oriY = remainingHeight;
}
else if (oriY > maxYmayHave) {
oriY = maxYmayHave;
}
remainingHeight = oriY + labelHeight;
// If the there is a label down underneath or
// bordered put elipses else wrap the word.
extraLineSpaceComp = pointConfig.clearance === undefined?
mathCeil (pluckNumber (parseFloat (pointConfig.style.border), 12), 12) * 2:
mathCeil (pluckNumber (parseFloat (pointConfig.style.border),
pointConfig.clearance)) * 2;
y1 = ((oriY + points[i].oriY) / 2);
x1 = centerX + xSign[k] * dataLabelsRadius *
mathCos (math.asin (y1 / labelQuadrantHeight));
y1 *= ySign[k];
y1 += centerY;
y2 = centerY + (radius * mathSin (angle));
x2 = centerX + (radius * mathCos (angle));
// Relation: centerX <= connectorStartX <= connectorEndX
// (for right half and vice versa for left half)
(k < 2 && x1 < x2 || k > 1 && x1 > x2) && (x1 = x2);
x3 = x1 + xDisplacement[k];
y3 = y1 - halfLabelHeight - 2;
x4 = x3 + xDisplacement[k];
_textAttrs._x = x4;
if (manageLabelOverflow) {
labelWidth = k > 1 ? x4 - chartConfig.canvasLeft : chartConfig.canvasLeft +
plotWidth - x4;
smartLabel.setStyle (pointConfig.style);
lineHeight = pluckNumber (mathCeil (parseFloat (pointConfig.style.lineHeight)),
12) + extraLineSpaceComp;
if (legendAlign === 'right' && pointConfig.transX > 0) {
labelWidth -= legendWidth + (legendBorderwidth * 2);
}
smartLabelObj =
smartLabel.getSmartText (pointConfig.displayValue, labelWidth, lineHeight);
if (pointConfig.clearance === undefined && smartLabelObj.height > labelHeight) {
y1 += labelHeight;
}
if (legendAlign !== 'right' && pointConfig.transY > 0) {
y1 -= labelHeight;
}
_textAttrs.text = smartLabelObj.text;
// _textAttrs.title = smartLabelObj.tooltext || '';
_textAttrs.tooltip = smartLabelObj.tooltext;
/*dataLabel.attr ( {
text: smartLabelObj.text
//title: (smartLabelObj.tooltext || '')
})
.tooltip (smartLabelObj.tooltext);*/
}
_textAttrs._y = y3;
if (sliced) {
transX = pointConfig.transX;
transY = pointConfig.transY;
x3 = x3 + transX;
x1 = x1 + transX;
x2 = x2 + transX;
y2 = y2 + transY;
x4 = x4 + transX;
}
_textAttrs['text-anchor'] = align;
_textAttrs.vAlign = 'middle';
_textAttrs.x = x4;
_textAttrs.y = y1;
prevTextPos = dataLabel && dataLabel.data('textPos');
if (prevTextPos) {
dataLabel.attr({
x: prevTextPos.x,
y: prevTextPos.y
}). animateWith(mainElm, animObj, _textAttrs, animationDuration);
}
else {
// for the initial rendering.
dataLabel = pointGraphics.label = paper
.text(_textAttrs, _textCss, dataLabelContainer)
.drag(plotDragMove, plotDragStart, plotDragEnd)
.hover(plotRollOver, plotRollOut);
}
dataLabel.x = _textAttrs._x;
dataLabel._x = _textAttrs._x;
dataLabel.y = _textAttrs._y;
if (_textAttrs.tooltip) {
dataLabel.tooltip(_textAttrs.tooltip);
delete _textAttrs.tooltip;
}
if (_textAttrs.visibility === VISIBLE) {
dataLabel.show();
}
dataLabel.data('textPos', {
x: x4,
y: y1
})
.data('plotItem', _textAttrs.plotItem)
.data(EVENTARGS, _textAttrs.eventArgs);
// draw the connector
if ((connector = pointGraphics.connector)) {
pointConfig.connectorPath = connectorPath = [
M,
x2, y2, // base
L,
// first break, next to the label
isSmartLineSlanted ? x1 : x2, y1,
x3, y1 // end of the string at the label
];
prevConnectorPos = connector.data('connectorPath');
if (prevConnectorPos) {
if (!chartConfig.clicked) {
connector.attr({
path: prevConnectorPos.path
}). animateWith(mainElm, animObj, {
path: connectorPath
}, animationDuration);
}
}
else {
connector.attr ( {
path: connectorPath
});
}
connector.data('connectorPath', {
path: connectorPath
});
}
}
}
}
}
};
} ()),
_spaceManager: function () {
var chart = this,
chartConfig = chart.config,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
data = dataSet.components.data,
conf = dataSet.config,
legend = chartComponents.legend,
colorM = chartComponents.colorManager,
SmartLabel = chart.linkedItems.smartLabel,
textWidthArr = [],
length = conf.dataLabelCounter,
labelMaxW = 0,
fcJSONChart = chart.jsonData.chart,
manageLabelOverflow = pluckNumber (fcJSONChart.managelabeloverflow, 0),
userGivenSlicingDist = pluckNumber (fcJSONChart.slicingdistance),
slicingDistance = !conf.preSliced && (chartConfig.allPlotSliceEnabled === ZEROSTRING) &&
(fcJSONChart.showlegend !== ONESTRING || fcJSONChart.interactivelegend === ZEROSTRING) ?
0 : mathAbs (pluckNumber (userGivenSlicingDist, 20)),
pieRadius = pluckNumber (fcJSONChart.pieradius, 0),
enableSmartLabels = pluckNumber (fcJSONChart.enablesmartlabels, fcJSONChart.enablesmartlabel, 1),
skipOverlapLabels = enableSmartLabels ? pluckNumber (fcJSONChart.skipoverlaplabels,
fcJSONChart.skipoverlaplabel, 1) : 0,
isSmartLineSlanted = pluckNumber (fcJSONChart.issmartlineslanted, 1),
labelDistance = length ? pluckNumber (fcJSONChart.labeldistance, fcJSONChart.nametbdistance, 5) :
slicingDistance,
smartLabelClearance = pluckNumber (fcJSONChart.smartlabelclearance, 5),
width = chartConfig.width,
height = chartConfig.height,
actionBarHeight = (chart._manageActionBarSpace(height * 0.225) || {}).bottom,
chartWorkingWidth = width - (chartConfig.marginRight + chartConfig.marginLeft),
chartWorkingHeight = height - (chartConfig.marginTop + chartConfig.marginBottom) -
(actionBarHeight ? actionBarHeight + chartConfig.marginBottom : 0),
minOfWH = mathMin (chartWorkingHeight, chartWorkingWidth),
smartLineColor = pluck (fcJSONChart.smartlinecolor, colorM.getColor ('plotFillColor')),
smartLineAlpha = pluckNumber (fcJSONChart.smartlinealpha, 100),
smartLineThickness = pluckNumber (fcJSONChart.smartlinethickness, 0.7),
dataLabelOptions = (conf.dataLabelOptions = dataSet._parseDataLabelOptions ()),
style = dataLabelOptions.style,
lineHeight = length ? pluckNumber (parseInt (style.lineHeight, 10), 12) : 0, //2px padding
pieMinRadius = pieRadius === 0 ? minOfWH * 0.15 : pieRadius,
pieMinDia = (2 * pieMinRadius),
legendSpace = {
bottom: 0,
right: 0
},
captionSpace,
pieYScale = conf.pieYScale,
pieSliceDepth = conf.pieSliceDepth,
textObj,
avaiableMaxpieSliceDepth,
totalSpaceReq;
// Old code for placeValuesInside
// placeLabelsInside = pluckNumber ((FCchartName === 'doughnut2d') ? 0 : fcJSONChart.placevaluesinside),
dataLabelOptions.connectorWidth = smartLineThickness;
dataLabelOptions.connectorPadding = pluckNumber (fcJSONChart.connectorpadding, 5);
dataLabelOptions.connectorColor = convertColor (smartLineColor, smartLineAlpha);
// If smart label is on and there is a label defined only then modify the label distance
if (length) {
if (enableSmartLabels) {
labelDistance = smartLabelClearance;
}
labelDistance += slicingDistance;
}
// Include label
totalSpaceReq = pieMinDia + ((lineHeight + labelDistance) * 2);
// Provide at least single line height space for caption.
// a space manager that manages the space for the tools as well as the captions.
captionSpace = chart._manageChartMenuBar(totalSpaceReq < chartWorkingHeight ?
chartWorkingHeight - totalSpaceReq : chartWorkingHeight / 2);
chartWorkingHeight -= ((captionSpace.top || 0) + (captionSpace.bottom || 0));
if (conf.showLegend) {
chart.hasLegend = true;
if (pluck (fcJSONChart.legendposition, POSITION_BOTTOM).toLowerCase () !== POSITION_RIGHT) {
legendSpace = legend._manageLegendPosition (chartWorkingHeight / 2);
chartWorkingHeight -= legendSpace.bottom;
} else {
legendSpace = legend._manageLegendPosition (chartWorkingHeight / 2);
chartWorkingWidth -= legendSpace.right;
}
}
chart._allocateSpace (legendSpace);
// Now get the max width required for all display text
// set the style
SmartLabel.useEllipsesOnOverflow(chartConfig.useEllipsesWhenOverflow);
if (length !== 1) { // Fix for single data in Pie makes pie very small in size.
for (; length--; ) {
SmartLabel.setStyle (data[length].config.style || chartConfig.dataLabelStyle);
textWidthArr[length] = textObj = SmartLabel.getOriSize (data[length].config.displayValue);
labelMaxW = mathMax (labelMaxW, textObj.width);
}
}
// If redius not supplyed then auto calculate it
if (pieRadius === 0) {
pieMinRadius = chart._stubRadius (chartWorkingWidth, labelMaxW, chartWorkingHeight, labelDistance,
slicingDistance, lineHeight, pieMinRadius);
}
else {
conf.slicingDistance = slicingDistance;
conf.pieMinRadius = pieMinRadius;
dataLabelOptions.distance = labelDistance;
}
avaiableMaxpieSliceDepth = chartWorkingHeight - (2 * ((pieMinRadius * pieYScale) + lineHeight));
conf.managedPieSliceDepth = (pieSliceDepth > avaiableMaxpieSliceDepth) ? (pieSliceDepth -
avaiableMaxpieSliceDepth) : conf.pieSliceDepth;
dataLabelOptions.isSmartLineSlanted = isSmartLineSlanted;
dataLabelOptions.enableSmartLabels = enableSmartLabels;
dataLabelOptions.skipOverlapLabels = skipOverlapLabels;
dataLabelOptions.manageLabelOverflow = manageLabelOverflow;
},
// manages the spaces when no radius is given.
_stubRadius: function (chartWorkingWidth, labelMaxW, chartWorkingHeight, labelDistance, slicingDistance,
lineHeight, pieMinRadius) {
var chart = this,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
conf = dataSet.config,
fcJSONChart = chart.jsonData.chart,
userGivenSlicingDist = pluckNumber (fcJSONChart.slicingdistance),
dataLabelOptions = conf.dataLabelOptions ||
(conf.dataLabelOptions = dataSet._parseDataLabelOptions ()),
availableRadius = 0,
// Slicing distance can not be less then the MINSLICINGDIST (10)
MINSLICINGDIST = 10,
shortFall;
availableRadius = mathMin((chartWorkingWidth / 2) - labelMaxW - slicingDistance,
(chartWorkingHeight / 2) - lineHeight) - labelDistance;
if (availableRadius >= pieMinRadius) {//there has space for min width
pieMinRadius = availableRadius;
}
else if (!userGivenSlicingDist) {/** @todo smartyfy Labels */
// If slicing distance is not given by the user, adjust slicing distance based on pie radius.
shortFall = pieMinRadius - availableRadius;
// Now reduce the pie slicing distance, but restrict the minimum slicing distance to 10 pixels.
slicingDistance = labelDistance = mathMax(mathMin(labelDistance - shortFall,
slicingDistance), MINSLICINGDIST);
}
conf.slicingDistance = slicingDistance;
conf.pieMinRadius = pieMinRadius;
dataLabelOptions.distance = labelDistance;
return pieMinRadius;
},
getDataSet: function (index) {
return this.components.dataset[index];
},
_startingAngle: function (angle, relative) {
var ang,
chart = this,
dataSet = chart.components.dataset[0],
seriesData = dataSet.config,
// Angle is differently handled in Pie2D and Pie3D. So, angles is converted
// accordingly to the same base. Its radian in 2D while in degrees in 3D.
// Moreover, sense of positive angle is opposite in the two.
currentAngle = (ang = seriesData.startAngle) * (-rad2deg) +
((-1) * ang < 0 ? 360 : 0);
if (!isNaN(angle)) {
if (!(seriesData.singletonCase || seriesData.isRotating)) {
angle += relative ? currentAngle : 0;
seriesData.startAngle = -angle * deg2rad;
chart._rotate(angle);
currentAngle = angle;
}
else {
currentAngle = seriesData.startAngle;
}
}
// Angle normalised in the range of [0, 360]
return mathRound(((currentAngle %= 360) + (currentAngle < 0 ? 360 : 0)) * 100) / 100;
},
eiMethods: /** @lends FusionCharts# */ {
/**
* Pie charts have slices that can be clicked to slice in and out.
* Checks whether a particular wedge of Pie or Doughnut chart is sliced-out or sliced-in.
*
* > Available on `pie` and `doughnut` chart types only.
*
* @group chart:pie-slice
*
* @param {number} index - The index of the data corresponding to the pie/doughnut slice.
* @returns {boolean} - The sliced state of the pie/doughnut wedge. Returns `true` if it's sliced out,
* or `false` if it's sliced in.
*
* @example
* // Render a pie 2d chart with some data in sliced out state, provide data index
* // in an input textfield and get the sliced state of the pie on click of a button
* FusionCharts.ready(function () {
* var chart = new FusionCharts({
* type: "pie2d",
* renderAt: "chart-container",
* dataSource: "data.json",
* dataFormat: "jsonurl"
* }).render();
*
* // Get the sliced state of a pie returned when clicked on a button
* // (with an id pie-sliced-state). It picks the data index from
* // an input textfield (with id pie-data-index).
* document.getElementById("pie-sliced-state").onclick = function () {
* var dataIndex = document.getElementById("pie-data-index").value,
* slicedState = chart.isPlotItemSliced(dataIndex);
* };
* });
*/
isPlotItemSliced: function (index) {
var num,
data,
config,
dataSet,
apiInstance = this.apiInstance;
return (apiInstance && apiInstance.components.dataset &&
(dataSet = apiInstance.components.dataset[0]) &&
(data = dataSet.components.data) && (num = data.length) &&
data[index] && (config = data[index].config)) &&
config.sliced;
},
addData: function () {
var dataSet,
apiInstance = this.apiInstance;
return (apiInstance && apiInstance.components.dataset &&
(dataSet = apiInstance.components.dataset[0]) && dataSet.addData.apply(dataSet, arguments));
},
removeData: function () {
var dataSet,
apiInstance = this.apiInstance;
return (apiInstance && apiInstance.components.dataset &&
(dataSet = apiInstance.components.dataset[0]) && dataSet.removeData.apply(dataSet, arguments));
},
updateData: function () {
var dataSet,
apiInstance = this.apiInstance;
return (apiInstance && apiInstance.components.dataset &&
(dataSet = apiInstance.components.dataset[0]) && dataSet.updateData.apply(dataSet, arguments));
},
/**
* Pie charts have slices. These slices can be clicked by users to slice in or slice out.
* Slices a pie/doughnut wedge to in / out state. In absence of the optional second parameter, it
* toggles the sliced state of the pie. The second parameter only enforces a specific sliced state.
*
* > Available on `pie` and `doughnut` chart types only.
*
* @group chart:pie-slice
*
* @param {number} index - The index of the data corresponding to the pie/doughnut slice.
* @param {boolean=} [slice] - Gives direction to chart on what is the required sliced state. For
* `true`, it slices out, if in sliced-in state. Or else, maintains it's sliced-out state. And
* vice-versa.
*
* @returns {boolean} - The final sliced state of the pie/doughnut wedge. Returns `true` if it's
* sliced out, or `false` if it's sliced in.
*
* @fires FusionCharts#slicingStart
* @fires FusionCharts#slicingEnd
*
* @example
* // Render a pie 2d chart, provide data index in an input textfield
* // and toggle the sliced state of the pie on click of a button
* FusionCharts.ready(function () {
* var chart = new FusionCharts({
* type: "pie2d",
* renderAt: "chart-container",
* dataSource: "data.json",
* dataFormat: "jsonurl"
* }).render();
*
* // Toggle the sliced state of the pie when clicked on a button
* // (with an id pie-sliced-state). It picks the data index from
* // an input textfield (with id pie-data-index).
* document.getElementById("pie-sliced-state").onclick = function () {
* var dataIndex = document.getElementById("pie-data-index").value;
* chart.slicePlotItem(dataIndex);
* };
* });
*/
slicePlotItem: function (index, slice, callback) {
var dataSet,
data,
config,
num,
output,
sliceVal = !!slice,
apiInstance = this.apiInstance,
asyncRender = apiInstance.chartInstance.args.asyncRender,
list = apiInstance.getJobList();
if (callback || asyncRender) {
list.eiMethods.push(schedular.addJob(function(){
output = (apiInstance && apiInstance.components.dataset &&
(dataSet = apiInstance.components.dataset[0]) && (data = dataSet.components.data) &&
(num = data.length) && data[index = dataSet.config.reversePlotOrder ?
(num - index - 1) : index] && (config = data[index].config)) &&
((sliceVal !== config.sliced || slice === UNDEFINED) &&
apiInstance._plotGraphicClick.call(data[index].graphics.element) || config.sliced);
if (typeof callback === 'function') {
callback(output);
}
}, apiInstance, [], priorityList.postRender));
}
else {
return (apiInstance && apiInstance.components.dataset &&
(dataSet = apiInstance.components.dataset[0]) &&
(data = dataSet.components.data) && (num = data.length) &&
data[index = dataSet.config.reversePlotOrder ? (num - index - 1) : index] &&
(config = data[index].config)) && ((sliceVal !== config.sliced || slice === UNDEFINED) &&
apiInstance._plotGraphicClick.call(data[index].graphics.element) || config.sliced);
}
},
/**
* Sets the center label in Dougnut 2D chart. The label cosmetics are configurable via the second
* optional parameter, which accepts a host of related properties.
*
* > Available on `doughnut` chart only.
*
* @group chart:pie-center-label
*
* @param {string} labelText - The text to be displayed at doughnut center.
* @param {object=} [options] - The optional parameter that holds a host of configurable params
* with most them being cosmetic properties of the center label. The properties are case sensitive.
*
* @param {string=} [options.font] - Sets the font face of the label.
* @param {string=} [options.fontSize] - Defines the font size of the label.
* @param {boolean=} [options.bold] - Specifies of whether the label be bold.
* @param {boolean=} [options.italic] - Specifies of whether the label be in italic.
* @param {hexcolor=} [options.color] - Sets the color of the label text.
* @param {alpha=} [options.alpha] - Sets the opacity of the label text.
* @param {hexcolor=} [options.hoverColor] - Sets the hover color of the label text.
* @param {alpha=} [options.hoverAlpha] - Sets the hover opacity of the label text.
* @param {hexcolor=} [options.bgColor] - Sets the color of the label background.
* @param {alpha=} [options.bgAlpha] - Sets the opacity of the label background.
* @param {hexcolor=} [options.borderColor] - Sets the color of the label background border.
* @param {alpha=} [options.borderAlpha] - Sets the opacity of the label background border.
* @param {number=} [options.borderThickness] - Sets the thickness of the label background border.
* @param {number=} [options.borderRadius] - Sets the radius for rounded label background.
* @param {number=} [options.padding] - The padding between extremities of the label and inner periphery
* of the doughnut. For rectangular label background, it's relative to any of the 4 corners. While for
* circular background, it's the gap between the 2 concentric circles, background border and inner
* periphery.
* @param {number=} [options.textPadding] - For rectangular label background, it's the gutter between
* the text and the background border. While for circular background, it's the minimum space between
* the background border and the containing circle of the text.
* @param {string=} [options.toolText] - Sets the tooltext for the label.
*
* @fires FusionCharts#centerLabelChanged
*
* @example
* // Render a doughnut 2d chart and set center label with some
* // configuring params on click of a button
* FusionCharts.ready(function () {
* var chart = new FusionCharts({
* type: "doughnut2d",
* renderAt: "chart-container",
* dataSource: "data.json",
* dataFormat: "jsonurl"
* }).render();
*
* // Assign the functionality of setting the center label when clicked on
* // a button (with an id set-center-label).
* document.getElementById("set-center-label").onclick = function () {
* chart.centerLabel("The central label", {bold: true, toolText: "center label tooltext"});
* };
* });
*/
centerLabel: function (labelText, options) {
var chart = this.apiInstance,
list = chart.getJobList(),
asyncRender = chart.chartInstance.args.asyncRender,
_helperFn = function() {
var seriesData = chart.components.dataset[0],
config = seriesData.config,
piePlotOptions = config.piePlotOptions,
innerSize = piePlotOptions.innerSize,
pieCenter = config.pieCenter,
cx = pieCenter[0],
cy = pieCenter[1],
centerLabelConfig = config.centerLabelConfig,
key;
if (typeof options !== 'object') {
options = centerLabelConfig;
}
else {
// Create the config cosmetics object from those obtained
// from argument and default values
for (key in centerLabelConfig) {
options[key] === UNDEFINED && (options[key] = centerLabelConfig[key]);
}
}
options.label = labelText;
seriesData.centerLabelConfig = options;
innerSize && chart.drawDoughnutCenterLabel(labelText || '',
cx, cy, innerSize, innerSize, options, true);
};
if (asyncRender) {
list.eiMethods.push(schedular.addJob(_helperFn, chart, [], priorityList.postRender));
}
else {
_helperFn();
}
},
/**
* Rotates the pie/doughnut chart to a specific angle or by a specific angle. The mode of
* operation is controlled by the optional second parameter. Even the first parameter is optional,
* in absence of which, the chart doesn't rotate and simply returns the current starting angle
* of the pie/doughnut chart.
*
* Starting angle of a pie/doughnut chart is the angle at which the starting face of the first data is
* aligned to. Each pie is drawn in counter clock-wise direction.
*
* > Available on `pie` and `doughnut` chart types only.
*
* @group chart:pie-slice
*
* @param {degrees=} [angle=0] - The angle by which to rotate the entire pie/doughnut chart.
* @param {boolean=} [relative=false] - Specify whether the angle being set is relative to the current
* angle or with respect to absolute 0.
* @returns {degrees} - The final state of the starting angle of the chart.
*
* @example
* // Render a pie 2d chart and rotate the chart by 90 degrees on click of a button
* FusionCharts.ready(function () {
* var chart = new FusionCharts({
* type: "pie2d",
* renderAt: "chart-container",
* dataSource: "data.json",
* dataFormat: "jsonurl"
* }).render();
*
* // Assign the functionality of rotating the chart by 90 degrees when clicked on
* // a button (with an id rotate-chart).
* document.getElementById("rotate-chart").onclick = function () {
* chart.startingAngle(90, true);
* };
* });
*/
startingAngle: function (angle, relative, callback) {
var chart = this.apiInstance,
asyncRender = chart.chartInstance.args.asyncRender,
list = chart.getJobList(),
output;
if (callback || asyncRender) {
list.eiMethods.push(schedular.addJob(function(){
output = chart._startingAngle(angle, relative);
if (typeof callback === 'function') {
callback(output);
}
}, chart, [], priorityList.postRender));
}
else {
return chart._startingAngle(angle, relative);
}
}
}
}, chartAPI.guageBase, {
plotborderthickness: 1,
alphaanimation: 0,
singletonPlaceValue: true,
usedataplotcolorforlabels: 0,
enableslicing: ONESTRING
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-pie3d', function () {
var global = this,
lib = global.hcLib,
R = lib.Raphael,
hasTouch = lib.hasTouch,
win = global.window,
getPosition = lib.getPosition,
//add the tools thats are requared
pluck = lib.pluck,
pluckNumber = lib.pluckNumber,
ROLLOVER = 'DataPlotRollOver',
ROLLOUT = 'DataPlotRollOut',
EVENTARGS = 'eventArgs',
each = lib.each,
plotEventHandler = lib.plotEventHandler,
docMode8 = win.document.documentMode === 8,
HIDDEN = 'hidden',
VISIBLE = docMode8 ? 'visible' : '',
M = 'M',
L = 'L',
V = 'v',
A = 'A',
Z = 'Z',
math = Math,
mathSin = math.sin,
mathCos = math.cos,
mathRound = math.round,
mathATan2 = math.atan2,
mathMin = math.min,
mathMax = math.max,
mathAbs = math.abs,
mathPI = math.PI,
mathCeil = math.ceil,
mathFloor = math.floor,
pi = Math.PI,
piBy2 = pi / 2,
pi2 = 2 * pi,
pi3By2 = pi + piBy2,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
getClickArcTangent = function (x, y, center, ref, pieYScale) {
return mathATan2 ((y - center[1] - ref.top) / pieYScale, x - center[0] - ref.left);
},
getAbsScaleAngle = function (start, end) {
return (start > end ? pi2 : 0) + end - start;
},
getDarkColor = lib.graphics.getDarkColor,
getLightColor = lib.graphics.getLightColor,
getFirstValue = lib.getFirstValue,
convertColor = lib.graphics.convertColor,
COMMASTRING = lib.COMMASTRING,
toRaphaelColor = lib.toRaphaelColor,
hasSVG = lib.hasSVG,
UNDEFINED,
map = function (arr, fn) {
var results = [],
i = 0,
len = arr.length;
for (; i < len; i++) {
results[i] = fn.call (arr[i], arr[i], i, arr);
}
return results;
},
/* Helper function */
normalizeAngle = function (angle, inDegrees) {
var fullCycle = inDegrees ? 360 : pi2;
angle = (angle || 0) % fullCycle;
return angle < 0 ? fullCycle + angle : angle;
},
defined = function (obj) {
return obj !== UNDEFINED && obj !== null;
},
isObject = function (obj) {
return typeof obj === 'object';
},
isString = function (s) {
return typeof s === 'string';
},
pInt = function (s, mag) {
return parseInt (s, mag || 10);
},
pie3DCacheColorStore = {
lighting3D: {
},
lighting2D: {
}
};
chartAPI ('pie3d', {
defaultDatasetType : 'Pie3D',
applicableDSList: {
'Pie3D': true
},
is3D: true,
friendlyName: '3D Pie Chart',
fireGroupEvent: true,
creditLabel: creditLabel,
getPointColor: function (color) {
return color;
},
_configureManager: function () {
var chart = this,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
dataSetConfig = dataSet.config,
dataSetComponents = dataSet.components,
pie3DManager = dataSetComponents.Pie3DManager,
data = dataSetComponents.data;
if (pie3DManager) {
pie3DManager.configure(dataSetConfig.pieSliceDepth, data.length === 1, dataSetConfig.use3DLighting,
false);
}
},
// Pie2D (base) has defaultPlotShadow, but 3d does not.
defaultPlotShadow: 0,
//Initialising the pie3D manager class.
_preDrawCalculate: function () {
var i,
point,
chart = this,
chartConfig = chart.config,
chartComponents = chart.components,
precision = 1000,
total = 0,
dataSet = chartComponents.dataset[0],
dataSetConfig = dataSet.config,
dataSetComponents = dataSet.components,
dataLabelOptions = dataSetConfig.dataLabelOptions,
pie3DOptions = (dataSetConfig.pie3DOptions = dataSet._parsePie3DOptions()),
startingAngle = (pluck (dataSetConfig.startAngle, 0) % pi2),
fontSize,
managedPieSliceDepth = dataSetConfig.managedPieSliceDepth,
slicedOffset = dataSetConfig.slicedOffset = pie3DOptions.slicedOffset,
plotWidth = chartConfig.canvasWidth,
plotHeight = chartConfig.canvasHeight,
positions = [chartConfig.canvasLeft + plotWidth * 0.5,
chartConfig.canvasTop + plotHeight * 0.5 - managedPieSliceDepth * 0.5],
start,
pointConfig,
end,
angle,
lastEnd,
maxEnd,
data = dataSetComponents.data,
fraction,
smallestSize = mathMin (plotWidth, plotHeight),
isPercent,
radiusX, // the x component of the radius vector for a given point
radiusY,
labelDistance = dataLabelOptions.distance,
pieYScale = dataSetConfig.pieYScale,
slicedOffsetY = dataSetConfig.slicedOffsetY || (dataSetConfig.slicedOffsetY =
slicedOffset * dataSetConfig.pieYScale),
pie3DManager = dataSetComponents.Pie3DManager;
// get positions - either an integer or a percentage string must be given
positions.push ((2 * dataSetConfig.pieMinRadius), pie3DOptions.innerSize || 0);
positions = map (positions, function (length, i) {
isPercent = /%$/.test (length);
return isPercent ?
// i == 0: centerX, relative to width
// i == 1: centerY, relative to height
// i == 2: size, relative to smallestSize
// i == 4: innerSize, relative to smallestSize
[plotWidth, plotHeight - managedPieSliceDepth, smallestSize, smallestSize][i] *
pInt (length) / 100:
length;
});
//convert all diameter into radius
positions[2] /= 2;
positions[3] /= 2;
//Add the ry
positions.push (positions[2] * pieYScale);
//centerRadiusX
positions.push ((positions[2] + positions[3]) / 2);
//centerRadiusY
positions.push (positions[5] * pieYScale);
// utility for getting the x value from a given y, used for anticollision logic in data labels
dataSet.getX = function (y, left) {
angle = math.asin ((y - positions[1]) / (positions[2] + labelDistance));
return positions[0] +
(left ? -1 : 1) *
(mathCos (angle) * (positions[2] + labelDistance));
};
// set center for later use
dataSetConfig.center = positions;
// get the total sum
each (data, function (point) {
total += point.config.y;
});
dataSetConfig.labelsRadius = positions[2] + labelDistance;
dataSetConfig.labelsRadiusY = dataSetConfig.labelsRadius * pieYScale;
dataSetConfig.quadrantHeight = (plotHeight - managedPieSliceDepth) / 2;
dataSetConfig.quadrantWidth = plotWidth / 2;
lastEnd = startingAngle;
lastEnd = mathRound (lastEnd * precision) / precision;
maxEnd = lastEnd + pi2;
fontSize = pluckNumber (parseInt (dataLabelOptions.style.fontSize, 10), 10) + 4,//2px padding
dataSetConfig.maxLabels = mathFloor (dataSetConfig.quadrantHeight / fontSize); //max labels per quarter
dataSetConfig.labelFontSize = fontSize;
dataSetConfig.connectorPadding = pluckNumber (dataLabelOptions.connectorPadding, 5);
dataSetConfig.isSmartLineSlanted = pluck (dataLabelOptions.isSmartLineSlanted, true);
dataSetConfig.connectorWidth = pluckNumber (dataLabelOptions.connectorWidth, 1);
dataSetConfig.enableSmartLabels = dataLabelOptions.enableSmartLabels;
if (!pie3DManager) {
pie3DManager = dataSetComponents.Pie3DManager = new Pie3DManager (chart);
// enabling the tooltip options for slices as it is the parent group being used by the pie3dmanager
chart.get('graphics','datasetGroup').trackTooltip(true);
}
chart._configureManager();
// each (data, function (point) {
for (i = data.length - 1; i >= 0; i -= 1) {
point = data[i];
pointConfig = point.config;
// set start and end angle
start = lastEnd;
fraction = total ? pointConfig.y / total : 0;
lastEnd = mathRound ((lastEnd + (fraction * pi2)) * precision) / precision;
if (lastEnd > maxEnd) {
lastEnd = maxEnd;
}
end = lastEnd;
// set the shape
pointConfig.shapeArgs = {
start: mathRound (start * precision) / precision,
end: mathRound (end * precision) / precision
};
// center for the sliced out slice
pointConfig.centerAngle = angle = ((end + start) / 2) % pi2;
/** @todo: slicedTranslation is implemented as string */
pointConfig.slicedTranslation = [
mathRound (mathCos (angle) * slicedOffset),
mathRound (mathSin (angle) * slicedOffsetY)
];
// set the anchor point for tooltips
radiusX = mathCos (angle) * positions[2];
dataSetConfig.radiusY = radiusY = mathSin (angle) * positions[4];
pointConfig.tooltipPos = [
positions[0] + radiusX * 0.7,
positions[1] + radiusY//changed to reducr mouce on tooltip condition
];
// API properties
pointConfig.percentage = fraction * 100;
pointConfig.total = total;
}
},
placeDataLabels: (function () {
/*
* Pie Helper Functions.
*/
var sortArrayByPoint = function (a, b) {
return a.point.value - b.point.value;
},
sortArrayByAngle = function (a, b) {
return a.angle - b.angle;
},
alignments = ['start', 'start', 'end', 'end'],
alignCenter = 'middle',
ySign = [-1, 1, 1, -1],
xSign = [1, 1, -1, -1];
return function (isRotating) {
var attr,
isNewElem,
prevTextPos,
chart = this,
chartConfig = chart.config,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
plotOptions = dataSet.config,
plotItems = dataSet.components.data,
piePlotOptions = plotOptions.piePlotOptions,
canvasLeft = chartConfig.canvasLeft,
canvasTop = chartConfig.canvasTop,
canvasWidth = chartConfig.canvasWidth,
cx = canvasLeft + chartConfig.canvasWidth * 0.5,
cy = canvasTop + chartConfig.canvasHeight * 0.5,
smartLabel = chart.linkedItems.smartLabel,
dataLabelsOptions = plotOptions.dataLabelOptions,
style = dataLabelsOptions.style,
lineHeight = pluckNumber (mathCeil (parseFloat (style.lineHeight)), 12),
placeInside = getFirstValue (dataLabelsOptions.placeInside, false),
skipOverlapLabels = dataLabelsOptions.skipOverlapLabels,
manageLabelOverflow = dataLabelsOptions.manageLabelOverflow,
connectorPadding = dataLabelsOptions.connectorPadding,
distanceOption = dataLabelsOptions.distance,
connectorWidth = dataLabelsOptions.connectorWidth,
remainingHeight,
// divide the points into quarters for anti collision
quarters = [
[], // top right
[], // bottom right
[], // bottom left
[] // top left
],
// todo remove the dupliace variables.
plotLeft = canvasLeft,
plotTop = canvasTop,
plotWidth = canvasWidth,
labelFontSize = parseInt (style.fontSize, 10),
labelHeight = labelFontSize,
halfLabelHeight = labelHeight / 2,
xDisplacement = [connectorPadding,
connectorPadding, -connectorPadding, -connectorPadding
],
isSmartLineSlanted = dataLabelsOptions.isSmartLineSlanted,
align,
i,
labelWidth,
j,
oriY,
maxYmayHave,
spaceRequired,
length,
k,
sliced,
x1,
x2,
x3,
x4,
y1,
y2,
y3,
points,
point,
angle,
excess,
excessArr,
dataLabel,
quarter,
transX,
transY,
smartLabelObj,
pointConfig,
pointGraphics,
connector,
connectorPath,
outside = distanceOption > 0,
center = plotOptions.center || (plotOptions.center = [
cx,
cy,
piePlotOptions.size,
(piePlotOptions.innerSize || 0)
]),
centerY = center[1],
centerX = center[0],
radius = center[2],
radiusY = center[4],
dataLabelsRadius = plotOptions.labelsRadius,
dataLabelsRadiusY = mathRound (plotOptions.labelsRadiusY * 100) / 100,
maxLabels = plotOptions.maxLabels,
enableSmartLabels = plotOptions.enableSmartLabels,
labelQuardentHeight,
maxQuardentLabel,
pieSliceDepthHalf = plotOptions.pieSliceDepth / 2,
animationObj = chart.get('config', 'animationObj'),
animationDuration = isRotating ? 0 : animationObj.duration,
mainElm = animationObj.dummyObj,
animObj = animationObj.animObj,
animType = animationObj.animType,
_textCss,
_textAttrs,
plotDragMove = chart._plotDragMove,
plotDragStart = chart._plotDragStart,
plotDragEnd = chart._plotDragEnd,
plotRollOver = chart._plotRollOver,
plotRollOut = chart._plotRollOut,
paper = chartComponents.paper,
datasetGraphics = dataSet.graphics,
dataLabelContainer = datasetGraphics.dataLabelContainer;
smartLabel.useEllipsesOnOverflow(chartConfig.useEllipsesWhenOverflow);
// save the world if there is no labels to be placed.
if (!plotOptions.dataLabelCounter) {
return;
}
if (!isRotating) {
//do not set the style every time
// Do it for first time
smartLabel.setStyle (style);
}
// arrange points for detection collision
// Creates an array of quarter containing labels of each quarter
//if there has only one label the draw it inside
if (plotItems.length == 1) {
point = plotItems[0];
pointGraphics = point.graphics;
pointConfig = point.config;
_textAttrs = pointConfig._textAttrs;
_textCss = pointConfig._textCss;
dataLabel = pointGraphics.label;
connector = pointGraphics.connector;
pointConfig.slicedTranslation = [plotLeft, plotTop];
if (pointConfig.y !== null && pointConfig.y !== undefined) {
_textAttrs.visibility = VISIBLE;
_textAttrs['text-anchor'] = alignCenter;
_textAttrs.x = centerX;
_textAttrs.y = centerY + halfLabelHeight - 2;
_textAttrs._x = centerX;
}
if (!dataLabel) {
dataLabel = pointGraphics.label = paper
.text(_textAttrs, _textCss, dataLabelContainer)
.drag(plotDragMove, plotDragStart, plotDragEnd)
.hover(plotRollOver, plotRollOut);
}
else {
dataLabel.animateWith (mainElm, animObj, _textAttrs, animationDuration, animType);
}
if (_textAttrs._x) {
dataLabel.x = _textAttrs._x;
delete _textAttrs.x;
}
dataLabel
.data('plotItem', _textAttrs.plotItem)
.data(EVENTARGS, _textAttrs.eventArgs);
if (_textAttrs.visibility === VISIBLE) {
dataLabel.show();
}
if (connector) {
connector.hide();
}
}
else {
if (placeInside) {
each (plotItems, function (point) {
pointGraphics = point.graphics;
pointConfig = point.config;
_textAttrs = pointConfig._textAttrs;
dataLabel = pointGraphics.label;
if (pointConfig.y !== null && pointConfig.y !== undefined) {
angle = pointConfig.angle;
y3 = centerY + (center[6] * mathSin (angle)) + halfLabelHeight - 2;
x3 = centerX + (center[5] * mathCos (angle));
_textAttrs._x = x3;
_textAttrs._y = y3;
if (pointConfig.sliced) {
var slicedTranslation = point.slicedTranslation,
transX = slicedTranslation[0] - plotLeft,
transY = slicedTranslation[1] - plotTop;
x3 = x3 + transX;
y3 = y3 + transY;
}
_textAttrs.visibility = VISIBLE;
_textAttrs.align = alignCenter;
_textAttrs.x = x3;
_textAttrs.y = y3;
}
if (!dataLabel) {
dataLabel = pointGraphics.label = paper
.text(_textAttrs, _textCss, dataLabelContainer)
.drag(plotDragMove, plotDragStart, plotDragEnd)
.hover(plotRollOver, plotRollOut);
}
else {
dataLabel.animateWith (mainElm, animObj, _textAttrs, animationDuration, animType);
}
dataLabel
.data('plotItem', _textAttrs.plotItem)
.data(EVENTARGS, _textAttrs.eventArgs);
if (_textAttrs.visibility === VISIBLE) {
dataLabel.show();
}
dataLabel.x = _textAttrs._x;
dataLabel._x = _textAttrs._x;
dataLabel._y = _textAttrs._y;
});
}
else { //outside
each (plotItems, function (point) {
pointGraphics = point.graphics;
pointConfig = point.config;
_textCss = pointConfig._textCss;
_textAttrs = pointConfig._textAttrs;
if (!(_textAttrs.text = pointConfig.displayValue)) {
return;
}
pointGraphics = point.graphics;
if (pointConfig.y !== null && pointConfig.y !== undefined) {
dataLabel = pointGraphics.label;
connector = pointGraphics.connector;
connector && connector.show();
dataLabel && dataLabel.show ();
}
dataLabel = pointGraphics.label;
angle = pointConfig.angle;
if (angle < 0) {
angle = pi2 + angle;
}
// Calculate top right quarter labels
if (angle >= 0 && angle < piBy2) {
quarter = 1;
} else
// Calculate bottom right quarter labels
if (angle < pi) {
quarter = 2;
} else
// Calculate bottom left quarter labels
if (angle < (pi3By2)) {
quarter = 3;
}
// Calculate bottom left quarter labels
else {
quarter = 0;
}
// Now put labels according to each quarter
quarters[quarter].push ( {
point : point,
angle : angle
});
/*dataLabel = pointGraphics.label;
if (dataLabel) {
angle = pointConfig.angle;
if (angle < 0) {
angle = pi2 + angle;
}
// Calculate top right quarter labels
if (angle >= 0 && angle < piBy2) {
quarter = 1;
} else
// Calculate bottom right quarter labels
if (angle < pi) {
quarter = 2;
} else
// Calculate bottom left quarter labels
if (angle < (pi3By2)) {
quarter = 3;
}
// Calculate bottom left quarter labels
else {
quarter = 0;
}
// Now put labels according to each quarter
quarters[quarter].push ( {
point : point,
angle : angle
});
}*/
});
i = k = 4;
//if excess then remove the low value slice first
while (i --) {
if (skipOverlapLabels) {
// Find labels can fit into the quarters or not
excess = quarters[i].length - maxLabels;
if (excess > 0) {
quarters[i].sort (sortArrayByPoint); // sort by point.y
// remove extra data form the array
// which labels can not be fitted into the quarters
excessArr = quarters[i].splice (0, excess);
//hide all removed labels
for (j = 0, length = excessArr.length; j < length; j += 1) {
point = excessArr[j].point;
pointGraphics = point.graphics;
if (pointGraphics.label) {
pointGraphics.label.attr('visibility', HIDDEN);
}
if (pointGraphics.connector) {
pointGraphics.connector.attr ( {
visibility: HIDDEN
});
}
}
}
}
// now we sort the data labels by its label angle
quarters[i].sort (sortArrayByAngle);
}
maxQuardentLabel = mathMax (
quarters[0].length,
quarters[1].length,
quarters[2].length,
quarters[3].length
);
labelQuardentHeight = mathMax (
mathMin (maxQuardentLabel, maxLabels) * labelHeight,
dataLabelsRadiusY + labelHeight
);
// reverse 1st and 3rd quardent points
quarters[1].reverse ();
quarters[3].reverse ();
smartLabel.setStyle (style);
while (k --) {
points = quarters[k];
length = points.length;
if (!skipOverlapLabels) {
if (length > maxLabels) {
labelHeight = labelQuardentHeight / length;
}
else {
labelHeight = labelFontSize;
}
halfLabelHeight = labelHeight / 2;
}
//1st pass
//place all labels at 1st quarter
// calculate the total available space to put labels
spaceRequired = length * labelHeight;
// calculate the remaining height
remainingHeight = labelQuardentHeight;
//place all child point
for (i = 0; i < length; i += 1, spaceRequired -= labelHeight) {
// Get the y position of the label (radius where data label is to draw)
oriY = mathAbs (labelQuardentHeight * mathSin (points[i].angle));
if (remainingHeight < oriY) {
oriY = remainingHeight;
}
else if (oriY < spaceRequired) {
oriY = spaceRequired;
}
remainingHeight = (points[i].oriY = oriY) - labelHeight;
}
//2nd pass (reverse)
align = alignments[k];
//place all labels at 1st quarter
maxYmayHave = labelQuardentHeight - ((length - 1) * labelHeight);
remainingHeight = 0;
//place all child point
for (i = points.length - 1; i >= 0; i -= 1, maxYmayHave += labelHeight) {
point = points[i].point;
pointGraphics = point.graphics;
pointConfig = point.config;
_textAttrs = pointConfig._textAttrs;
_textCss = pointConfig._textCss;
if (pointConfig.y === null || !_textAttrs.text) {
continue;
}
angle = points[i].angle;
sliced = pointConfig.sliced;
dataLabel = pointGraphics.label;
oriY = mathAbs (labelQuardentHeight * mathSin (angle));
if (oriY < remainingHeight) {
oriY = remainingHeight;
}
else if (oriY > maxYmayHave) {
oriY = maxYmayHave;
}
remainingHeight = oriY + labelHeight;
y1 = ((oriY + points[i].oriY) / 2);
x1 = centerX + xSign[k] * dataLabelsRadius * mathCos (math.asin (y1 /
labelQuardentHeight));
y1 *= ySign[k];
y1 += centerY;
y2 = centerY + (radiusY * mathSin (angle));
x2 = centerX + (radius * mathCos (angle));
// Relation: centerX <= connectorStartX <= connectorEndX (for right half and vice
// versa for left half)
(k < 2 && x1 < x2 || k > 1 && x1 > x2) && (x1 = x2);
x3 = x1 + xDisplacement[k];
y3 = y1 + halfLabelHeight - 2;
x4 = x3 + xDisplacement[k];
_textAttrs._x = x4;
if (manageLabelOverflow) {
labelWidth = k > 1 ? x4 - canvasLeft: canvasLeft + plotWidth - x4;
smartLabel.setStyle (pointConfig.style);
lineHeight = pluckNumber (mathCeil (parseFloat (pointConfig.style.lineHeight)),
12) + ((mathCeil (parseFloat (pointConfig.style.border), 12) * 2) || 0);
smartLabelObj = smartLabel.getSmartText (pointConfig.displayValue, labelWidth,
lineHeight);
_textAttrs.text = smartLabelObj.text;
_textAttrs.tooltip = smartLabelObj.tooltext;
}
//shift the labels at front pieSliceDepthHalf
if (angle < pi) {
y1 += pieSliceDepthHalf;
y2 += pieSliceDepthHalf;
y3 += pieSliceDepthHalf;
}
_textAttrs._y = y3;
// dataLabel.y = y3;
if (sliced) {
transX = pointConfig.transX;
transY = pointConfig.transY;
x3 = x3 + transX;
x1 = x1 + transX;
x2 = x2 + transX;
y2 = y2 + transY;
x4 = x4 + transX;
}
_textAttrs.visibility = VISIBLE;
_textAttrs['text-anchor'] = align;
prevTextPos = dataLabel && dataLabel.data('textPos');
if (prevTextPos) {
dataLabel.attr({
x: prevTextPos.x,
y: prevTextPos.y
});
}
_textAttrs.x = x4;
_textAttrs.y = y1;
if (!isRotating && prevTextPos) {
dataLabel.animateWith(mainElm, animObj, _textAttrs, animationDuration,
animType);
}
else {
if (!dataLabel) {
dataLabel = pointGraphics.label = paper
.text(_textAttrs, _textCss, dataLabelContainer)
.drag(plotDragMove, plotDragStart, plotDragEnd)
.hover(plotRollOver, plotRollOut);
}
else {
_textAttrs && dataLabel.attr(_textAttrs);
}
}
dataLabel.data('textPos', {
x: x4,
y: y1
})
.data('plotItem', _textAttrs.plotItem)
.data(EVENTARGS, _textAttrs.eventArgs);
dataLabel.x = _textAttrs._x;
// storing original x value
// to use while slicing in (IE Issue original x get changed form animate)
dataLabel._x = _textAttrs._x;
dataLabel.y = _textAttrs._y;
if (_textAttrs.tooltip) {
dataLabel.tooltip(_textAttrs.tooltip);
delete _textAttrs.tooltip;
}
//draw the connector
// draw the connector
if (outside && connectorWidth && enableSmartLabels) {
connector = pointGraphics.connector;
if (!pointConfig.connectorPath) {
isNewElem = true;
}
pointConfig.connectorPath = connectorPath = [
M,
x2, y2, // base
L,
isSmartLineSlanted ? x1 : x2, y1, // first break, next to the label
x3, y1 // end of the string at the label
];
attr = {
path: connectorPath,
'stroke-width': connectorWidth,
stroke: dataLabelsOptions.connectorColor || '#606060',
visibility: VISIBLE
};
if (connector) {
if (!isRotating && !isNewElem) {
connector.animateWith(mainElm, animObj, attr, animationDuration,
animType);
}
else {
connector.attr(attr);
}
}
}
}
}
}
}
};
} ()),
animate: function () {
var i,
point,
graphic,
pointGraphics,
pointConfig,
element,
args,
up,
start,
end,
chart = this,
chartGraphics = chart.graphics,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
dataSetConfig = dataSet.config,
dataSetComponents = dataSet.components,
plotItems = dataSetComponents.data,
dataSetGroup = chartGraphics.datasetGroup,
len = plotItems.length,
alphaAnim = dataSetConfig.alphaAnimation,
animStartFN = function () {
if (chart.disposed || chart.disposing) {
return;
}
chart.placeDataLabels (false);
},
animationObj = chart.get('config', 'animationObj'),
animationDuration = animationObj.duration || 0,
mainElm = animationObj.dummyObj,
animObj = animationObj.animObj,
animType = animationObj.animType;
if (alphaAnim) {
dataSetGroup.attr ( {
opacity: 0
});
dataSetGroup.animateWith(mainElm, animObj, {
opacity: 1
}, animationDuration, animType, animStartFN);
}
else {
for (i = 0; i < len; i++) {
point = plotItems[i];
pointGraphics = point.graphics;
pointConfig = point.config;
args = pointConfig.shapeArgs;
up = 2 * mathPI;
element = pointGraphics.element;
// start values
if (element) {
element.attr ( {
start: up,
end: up
});
start = args.start;
end = args.end;
/* Raphael animation do not support start and end attributes.
* Since the attribute setting for Pie3D goes through attrFN
* method of Pie3DManager, we can safely use some unused
* attributes for pie3D to pass through Raphael animation module
* and trap the attributes to convert to start and end in attrFN */
graphic.animateWith (mainElm, animObj, {
cx: start - up,
cy: end - up
}, animationDuration, animType);
}
}
}
},
_rotate: function (setAngle) {
var chart = this,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
dataSetConfig = dataSet.config,
dataSetComponents = dataSet.components,
data = dataSetComponents.data,
slicedOffset = dataSetConfig.slicedOffset,
slicedOffsetY = dataSetConfig.slicedOffsetY,
startingAngle = dataSetConfig.startAngle,
angle;
setAngle = !isNaN (setAngle) ? setAngle : -dataSetConfig._lastAngle;
angle = (setAngle - startingAngle) % 360;
dataSetConfig.startAngle = pluckNumber (setAngle,
dataSetConfig.startAngle) % 360;
angle = - (angle * mathPI) / 180;
if (dataSetComponents.Pie3DManager) {
dataSetComponents.Pie3DManager.rotate (angle);
}
each (data, function (point) {
var slicedTranslation = [],
pointGraphics = point.graphics,
pointConfig = point.config,
element = pointGraphics.element,
args = pointConfig.shapeArgs,
newAngleArgs = {
start: (args.start += angle),
end: (args.end += angle)
},
pointAngle = pointConfig.angle = normalizeAngle ((newAngleArgs.start + newAngleArgs.end) / 2),
sliced = pointConfig.sliced,
cosAngle = mathCos (pointAngle),
sinAngle = mathSin (pointAngle);
//set the slicedTranslation
slicedTranslation = pointConfig.slicedTranslation = [
mathRound (cosAngle * slicedOffset),
mathRound (sinAngle * slicedOffsetY)
];
pointConfig.transX = slicedTranslation[0];
pointConfig.transY = slicedTranslation[1];
pointConfig.slicedX = sliced ? mathCos (angle) * slicedOffset : 0;
pointConfig.slicedY = sliced ? mathSin (angle) * slicedOffsetY : 0;
if (element && sliced) {
element.attr ( {
transform: 't' + slicedTranslation[0] + ',' + slicedTranslation[1]
});
}
});
chart.placeDataLabels (true, data);
},
_plotRollOver: function (e) {
var plotItem = this.data ('plotItem'),
index = plotItem.index,
chart = plotItem.chart,
chartConfig = chart.config,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
dataSetConfig = dataSet.config,
dataSetComponents = dataSet.components,
data = dataSetComponents.data[index],
setGraphics = data.graphics,
setConfig = data.config,
element = setGraphics.element,
hoverEffects = setConfig.hoverEffects;
if (!dataSetConfig.isRotating) {
plotEventHandler.call (element, chart, e, ROLLOVER);
hoverEffects.enabled && element.attr(hoverEffects);
}
chartConfig.isHovered = true;
},
_plotRollOut: function (e) {
var plotItem = this.data ('plotItem'),
index = plotItem.index,
chart = plotItem.chart,
chartConfig = chart.config,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
dataSetConfig = dataSet.config,
dataSetComponents = dataSet.components,
data = dataSetComponents.data[index],
setGraphics = data.graphics,
setConfig = data.config,
element = setGraphics.element;
if (!dataSetConfig.isRotating) {
plotEventHandler.call (element, chart, e, ROLLOUT);
element.attr({
color: setConfig.color.color.split(',')[0],
alpha: setConfig._3dAlpha,
borderWidth: setConfig.borderWidth,
borderColor: setConfig.borderColor
});
}
chartConfig.isHovered = false;
},
_plotDragStart: function (x, y, evt) {
var plotItem = this.data ('plotItem'),
chart = plotItem.chart,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
dataSetConfig = dataSet.config,
angle;
dataSetConfig.isRightClicked = !hasTouch && (evt.button !== 0 && evt.button !== 1) ? true : false;
if (!dataSetConfig.enableRotation || dataSetConfig.isRightClicked) {
return;
}
dataSetConfig.isRotating = false;
angle = getClickArcTangent.call (evt, x, y, dataSetConfig.center,
(dataSetConfig.chartPosition = getPosition(chart.linkedItems.container)), dataSetConfig.pieYScale);
dataSetConfig.dragStartAngle = angle;
dataSetConfig._lastAngle = -dataSetConfig.startAngle;
dataSetConfig.startingAngleOnDragStart = dataSetConfig.startAngle;
},
_plotDragEnd: function (e) {
var plotItem = this.data ('plotItem'),
index = plotItem.index,
chart = plotItem.chart,
chartConfig = chart.config,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
dataSetConfig = dataSet.config,
dataSetComponents = dataSet.components,
Pie3DManager = dataSetComponents.Pie3DManager,
startingAng = dataSetConfig.startAngle;
// save state
/*reflowUpdate = {
hcJSON: {
series: [ {
startAngle: startingAng
}]
}
};*/
/*if (!chart.disposed) {
extend2 (chart.logic.chartInstance.jsVars._reflowData,
reflowUpdate, true);
}*/
if (dataSetConfig.isRightClicked) {
return;
}
if (dataSetConfig.isRotating) {
/* The events mouseup, dragend and click are raised in order. In order
* to update the flag isRotating to false post click event, setTimeout
* called, to take immediate effect, is programmed to update the flag.
* Thus, the flag gets updated post the series of events, in effect.
* NB: Click event is subscribed conditionally.
*/
setTimeout (function () {
dataSetConfig.isRotating = false;
}, 0);
/**
* @event FusionCharts#rotationEnd
* @group chart:pie-slice
*
* @param { number} startingAngle - The initial angle. (desc)
* @param { number} changeInAngle - It is the difference between the starting angle and the starting
* angle on the drag start.
*/
global.raiseEvent ('rotationEnd', {
startingAngle: normalizeAngle (startingAng, true),
changeInAngle: startingAng - dataSetConfig.startingAngleOnDragStart
}, chart.chartInstance);
!chartConfig.isHovered && Pie3DManager.colorObjs[index] &&
Pie3DManager.onPlotHover (index, false);
}
else {
chart._plotGraphicClick.call (this,e);
}
},
_plotDragMove: function (dx, dy, x, y, evt) {
var plotItem = this.data ('plotItem'),
chart = plotItem.chart,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
dataSetConfig = dataSet.config,
angle,
currentTime,
deltaAngle;
if (isNaN(dx) || isNaN(dy) || !dataSetConfig.enableRotation || dataSetConfig.singletonCase ||
dataSetConfig.isRightClicked) {
return;
}
angle = getClickArcTangent.call (evt, x, y, dataSetConfig.center,
dataSetConfig.chartPosition, dataSetConfig.pieYScale);
if (!dataSetConfig.isRotating) {
dataSetConfig.dragStartAngle !== angle && (dataSetConfig.isRotating = true);
/**
* This event is fired when a pie or doughnut chart's rotation is triggered.
*
* @event FusionCharts#rotationStart
* @group chart:pie-slice
* @param { number} startingAngle - This indicates the angle from where rotation started.
*/
global.raiseEvent ('rotationStart', { startingAngle: normalizeAngle (dataSetConfig.startAngle,
true)}, chart.chartInstance);
}
deltaAngle = angle - dataSetConfig.dragStartAngle;
dataSetConfig.dragStartAngle = angle;
dataSetConfig.moveDuration = 0;
dataSetConfig._lastAngle += (deltaAngle * 180 / mathPI);
currentTime = new Date ().getTime ();
if (!dataSetConfig._lastTime || (dataSetConfig._lastTime + dataSetConfig.timerThreshold <
currentTime)) {
if (!dataSetConfig._lastTime) {
chart._rotate ();
}
dataSetConfig.timerId = setTimeout (function () {
if (!chart.disposed || !chart.disposing) {
chart._rotate ();
}
}, dataSetConfig.timerThreshold);
dataSetConfig._lastTime = currentTime;
}
},
// manages the spaces when no radius is given.
_stubRadius: function (chartWorkingWidth, labelMaxW, chartWorkingHeight, labelDistance, slicingDistance,
lineHeight, pieMinRadius) {
var chart = this,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
conf = dataSet.config,
fcJSONChart = dataSet.config,
userGivenSlicingDist = pluckNumber (fcJSONChart.slicingdistance),
dataLabelOptions = conf.dataLabelOptions ||
(conf.dataLabelOptions = dataSet._parseDataLabelOptions ()),
availableRadius = 0,
// Slicing distance can not be less then the MINSLICINGDIST (10)
MINSLICINGDIST = 10,
pieYScale = conf.pieYScale,
pieSliceDepth = conf.pieSliceDepth,
shortFall;
chartWorkingHeight -= pieSliceDepth;
availableRadius = mathMin ((chartWorkingWidth / 2) - labelMaxW - slicingDistance,
((chartWorkingHeight / 2) - lineHeight) / pieYScale) - labelDistance;
if (availableRadius >= pieMinRadius) { //there has space for min width
pieMinRadius = availableRadius;
}
else if (!userGivenSlicingDist) { /** @todo smartyfy Labels */
// If slicing distance is not given by the user, adjust slicing distance based on pie radius.
shortFall = pieMinRadius - availableRadius;
// Now reduce the pie slicing distance, but restrict the minimum slicing distance to 10 pixels.
slicingDistance = labelDistance = mathMax (mathMin (labelDistance - shortFall,
slicingDistance), MINSLICINGDIST);
}
conf.slicingDistance = slicingDistance;
conf.pieMinRadius = pieMinRadius;
dataLabelOptions.distance = labelDistance;
return pieMinRadius;
},
_startingAngle: function (angle, relative) {
var ang,
chart = this,
dataSet = chart.components.dataset[0],
seriesData = dataSet.config,
// Angle is differently handled in Pie2D and Pie3D. So, angles is converted
// accordingly to the same base. Its radian in 2D while in degrees in 3D.
// Moreover, sense of positive angle is opposite in the two.
currentAngle = (ang = seriesData.startAngle) + (ang < 0 ? 360 : 0);
if (!isNaN(angle)) {
if (!(seriesData.singletonCase || seriesData.isRotating)) {
angle += relative ? currentAngle : 0;
chart._rotate(angle);
currentAngle = angle;
}
}
// Angle normalised in the range of [0, 360]
return mathRound(((currentAngle %= 360) + (currentAngle < 0 ? 360 : 0)) * 100) / 100;
}
}, chartAPI.pie2d, {
plotborderthickness: 0.1,
alphaanimation: 1
});
// **** pie3d manager ***** //
// set the new aimatable properites
if(R._availableAnimAttrs && R._availableAnimAttrs.cx){
// assuming that cx is also numaric type
R._availableAnimAttrs.innerR = R._availableAnimAttrs.depth = R._availableAnimAttrs.radiusYFactor =
R._availableAnimAttrs.start = R._availableAnimAttrs.end = R._availableAnimAttrs.cx;
}
// Pie 3D point class
function Pie3DManager (chart) {
var manager = this;
manager.config = {};
manager.linkedItems = {
chart: chart
};
}
Pie3DManager.prototype = {
configure: function (depth, hasOnePoint, use3DLighting, isDoughnut) {
var manager = this,
chart = manager.linkedItems.chart,
renderer = chart.get('components', 'paper'),
seriesGroup = chart.get('graphics', 'datasetGroup');
if (isObject (depth)) {
depth = depth.depth;
hasOnePoint = depth.hasOnePoint;
use3DLighting = depth.use3DLighting;
isDoughnut = depth.isDoughnut;
}
// @todo: need to have pie to daughnut transpose
//add the values to the instance
if (!manager.renderer) {
manager.renderer = renderer;
}
manager.hasOnePoint = hasOnePoint;
manager.use3DLighting = use3DLighting;
manager.isDoughnut = isDoughnut;
manager.depth = depth;
//create required groups
/** @todo if requared create bottom side group */
!manager.bottomBorderGroup && (manager.bottomBorderGroup =
renderer.group('bottom-border', seriesGroup));
// @todo: need to have animate with for dept
manager.bottomBorderGroup.attr ({
transform: 't0,' + depth
});
!manager.slicingWallsBackGroup && (manager.slicingWallsBackGroup =
renderer.group ('slicingWalls-back-Side', seriesGroup));
!manager.slicingWallsFrontGroup && (manager.slicingWallsFrontGroup = renderer
.group ('slicingWalls-front-Side', seriesGroup));
!manager.topGroup && (manager.topGroup = renderer.group ('top-Side', seriesGroup));
// if arrays are not availabel the create the m for first time
!manager.pointElemStore && (manager.pointElemStore = []);
!manager.slicingWallsArr && (manager.slicingWallsArr = []);
//few reusable code
manager.moveCmdArr = [M];
manager.lineCmdArr = [L];
manager.closeCmdArr = [Z];
manager.colorObjs = [];
},
getArcPath : function (cX, cY, startX, startY, endX, endY, rX, rY, isClockWise, isLargeArc) {
return (startX == endX && startY == endY) ? [] : [A, rX, rY, 0, isLargeArc, isClockWise, endX, endY];
},
_parseSliceColor : function (color, alpha, pointConf) {
var manager = this,
dark1,
dark2,
dark3,
dark4,
dark5,
dark6,
light1,
light2,
light3,
light4,
light5,
light6,
alpha1,
colorStr1,
colorStr2,
alphaStr1,
alphaStr2,
alphaStr3,
colorStr3,
colorStr4,
colorStr5,
alphaFactor = 3,
lighting3D = manager.use3DLighting,
cacheStore = lighting3D ? pie3DCacheColorStore.lighting3D : pie3DCacheColorStore.lighting2D,
colorsObj,
radiusYFactor = pointConf.radiusYFactor,
cx = pointConf.cx,
cy = pointConf.cy,
rx = pointConf.r,
ry = rx * radiusYFactor,
innerRx = pointConf.innerR || 0,
rightX = cx + rx,
leftX = cx - rx,
rightInnerX = cx + innerRx,
leftInnerX = cx - innerRx;
alpha = alpha || 100;
alpha1 = alpha / 2;
// check in cacheStore
if (cacheStore[color] && cacheStore[color][alpha]) { // if found return the color
colorsObj = cacheStore[color][alpha];
}
else {// create the color components
if (!cacheStore[color]) {
cacheStore[color] = {};
}
if (!cacheStore[color][alpha]) {
cacheStore[color][alpha] = {};
}
colorsObj = cacheStore[color][alpha];
if (lighting3D) {
dark1 = getDarkColor(color, 80);
dark2 = getDarkColor(color, 75);
light1 = getLightColor(color, 85);
light2 = getLightColor(color, 70);
light3 = getLightColor(color, 40);
light4 = getLightColor(color, 50);
light5 = getLightColor(color, 30);
light6 = getLightColor(color, 65);
dark3 = getDarkColor(color, 85);
dark4 = getDarkColor(color, 69);
dark5 = getDarkColor(color, 75);
dark6 = getDarkColor(color, 95);
}
else {
alphaFactor = 10;
dark1 = getDarkColor(color, 90);
dark2 = getDarkColor(color, 87);
light1 = getLightColor(color, 93);
light2 = getLightColor(color, 87);
light3 = getLightColor(color, 80);
light6 = light4 = getLightColor(color, 85);
light5 = getLightColor(color, 80);
dark6 = dark3 = getDarkColor(color, 85);
dark4 = getDarkColor(color, 75);
dark5 = getDarkColor(color, 80);
}
colorStr1 = dark2 + COMMASTRING + light1 + COMMASTRING + light2 +
COMMASTRING + light1 + COMMASTRING + dark2;
alphaStr1 = alpha + COMMASTRING + alpha + COMMASTRING + alpha +
COMMASTRING + alpha + COMMASTRING + alpha;
colorStr2 = dark2 + COMMASTRING + color + COMMASTRING + light1 +
COMMASTRING + color + COMMASTRING + dark2;
alphaStr2 = alpha1 + COMMASTRING + alpha1 + COMMASTRING + alpha1 +
COMMASTRING + alpha1 + COMMASTRING + alpha1;
colorStr3 = dark2 + COMMASTRING + color + COMMASTRING + light3 +
COMMASTRING + color + COMMASTRING + dark2;
colorStr4 = dark5 + COMMASTRING + light1 + COMMASTRING + light4 +
COMMASTRING + light1 + COMMASTRING + dark4;
colorStr5 = 'FFFFFF' + COMMASTRING + 'FFFFFF' + COMMASTRING + 'FFFFFF' +
COMMASTRING + 'FFFFFF' + COMMASTRING + 'FFFFFF';
alphaStr3 = 0 + COMMASTRING + alpha1/alphaFactor + COMMASTRING + alpha/alphaFactor +
COMMASTRING + alpha1/alphaFactor + COMMASTRING + 0;
if (hasSVG) {
colorsObj.top = {
FCcolor : {
gradientUnits : 'userSpaceOnUse',
radialGradient : true,
color : light6 + COMMASTRING + dark6,
alpha : alpha + COMMASTRING + alpha,
ratio : '0,100'
}
};
}
else {
colorsObj.top = {
FCcolor : {
gradientUnits : 'objectBoundingBox',
color : light2 + COMMASTRING + light2 + COMMASTRING + light1 + COMMASTRING + dark2,
alpha : alpha + COMMASTRING + alpha + COMMASTRING + alpha + COMMASTRING + alpha,
angle : -72,
ratio : '0,8,15,77'
}
};
}
colorsObj.frontOuter = {
FCcolor : {
gradientUnits : 'userSpaceOnUse',
y1 : 0,
y2 : 0,
color : colorStr4,
alpha : alphaStr1,
angle : 0,
ratio : '0,20,15,15,50'
}
};
colorsObj.backOuter = {
FCcolor : {
gradientUnits : 'userSpaceOnUse',
y1 : 0,
y2 : 0,
color : colorStr3,
alpha : alphaStr2,
angle : 0,
ratio : '0,62,8,8,22'
}
};
colorsObj.frontInner = {
FCcolor : {
gradientUnits : 'userSpaceOnUse',
y1 : 0,
y2 : 0,
color : colorStr2,
alpha : alphaStr2,
angle : 0,
ratio : '0,25,5,5,65'
}
};
colorsObj.backInner = {
FCcolor : {
gradientUnits : 'userSpaceOnUse',
y1 : 0,
y2 : 0,
color : colorStr1,
alpha : alphaStr1,
angle : 0,
ratio : '0,62,8,8,22'
}
};
colorsObj.topBorder = {
FCcolor : {
gradientUnits : 'userSpaceOnUse',
y1 : 0,
y2 : 0,
color : colorStr5,
alpha : alphaStr3,
angle : 0,
ratio : '0,20,15,15,50'
}
};
colorsObj.topInnerBorder = {
FCcolor : {
gradientUnits : 'userSpaceOnUse',
y1 : 0,
y2 : 0,
color : colorStr5,
alpha : alphaStr3,
angle : 0,
ratio : '0,50,15,15,20'
}
};
colorsObj.bottom = toRaphaelColor(convertColor(color, alpha1));
/** @todo will be changed w. r. t. angle */
colorsObj.startSlice = toRaphaelColor(convertColor(dark1, alpha));
colorsObj.endSlice = toRaphaelColor(convertColor(dark1, alpha));
}
// check if non color attributes match, else apply them
if (colorsObj.cx !== cx || colorsObj.cy !== cy || colorsObj.rx !== rx ||
colorsObj.radiusYFactor !== radiusYFactor || colorsObj.innerRx !== innerRx) {
// appaly positional properties in colors
if (hasSVG) {
colorsObj.top.FCcolor.cx = cx;
colorsObj.top.FCcolor.cy = cy;
colorsObj.top.FCcolor.r = rx;
colorsObj.top.FCcolor.fx = cx - 0.3 * rx;
colorsObj.top.FCcolor.fy = cy + ry * 1.2;
}
colorsObj.topBorder.FCcolor.x1 = colorsObj.backOuter.FCcolor.x1 = colorsObj.frontOuter.FCcolor.x1 =
leftX;
colorsObj.topBorder.FCcolor.x2 = colorsObj.backOuter.FCcolor.x2 = colorsObj.frontOuter.FCcolor.x2 =
rightX;
colorsObj.topInnerBorder.FCcolor.x1 = colorsObj.backInner.FCcolor.x1 =
colorsObj.frontInner.FCcolor.x1 = leftInnerX;
colorsObj.topInnerBorder.FCcolor.x2 = colorsObj.backInner.FCcolor.x2 =
colorsObj.frontInner.FCcolor.x2 = rightInnerX;
// Set positional attributes in color Object
colorsObj.cx = cx;
colorsObj.cy = cy;
colorsObj.rx = rx;
colorsObj.radiusYFactor = radiusYFactor;
colorsObj.innerRx = innerRx;
}
return colorsObj;
},
rotate : function (angle) {
var manager = this,
pointElemStore = manager.pointElemStore,
x = 0, ln = pointElemStore.length, point, confObject;
if (!manager.hasOnePoint) {
for (; x < ln; x += 1) {
point = pointElemStore[x];
confObject = point._confObject;
confObject.start += angle;
confObject.end += angle;
manager._setSliceShape(confObject);
}
manager.refreshDrawing();
}
},
removeSlice: function (slice) {
var manager = this,
pointElemStore = manager.pointElemStore,
confObject = slice._confObject,
elements = confObject.elements,
slicingWallsArr = manager.slicingWallsArr,
x,
ln = pointElemStore.length,
point,
wallElement;
// remove the slice from the point store
for (x = ln - 1; x >= 0; x -= 1) {
point = pointElemStore[x];
if (point === slice) {
pointElemStore.splice(x,1);
}
}
// remove the side elements from the side wall arr
ln = slicingWallsArr.length;
for (x = ln - 1; x >= 0; x -= 1) {
wallElement = slicingWallsArr[x];
if (wallElement === elements.startSlice || wallElement === elements.frontOuter1 ||
wallElement === elements.frontOuter || wallElement === elements.backInner ||
wallElement === elements.endSlice) {
slicingWallsArr.splice(x, 1);
}
}
// hide corrosponding elements
slice.hide && slice.hide();
// store the element in pool for future reuse
if (!manager._slicePool) {
manager._slicePool = [];
}
manager._slicePool.push(slice);
manager.refreshDrawing();
},
useSliceFromPool: function () {
var manager = this,
slicePool = manager._slicePool || (manager._slicePool = []),
slicingWallsArr = manager.slicingWallsArr,
newSlice = false,
elements;
if (slicePool.length) {
newSlice = slicePool.shift();
// restore the element in point store
manager.pointElemStore.push(newSlice);
// show the slice
newSlice.show();
// push the side walls in to the side wall array
elements = newSlice._confObject.elements;
slicingWallsArr.push(elements.startSlice, elements.frontOuter1, elements.frontOuter);
elements.backInner && slicingWallsArr.push(elements.backInner);
slicingWallsArr.push(elements.endSlice);
}
return newSlice;
},
// This function do the z-index management
refreshDrawing : (function () {
var elementZSortFn = function (a, b) {
var centerAngleDiff = (a._conf.index - b._conf.index) || (a._conf.cIndex - b._conf.cIndex) ||
(a._conf.isStart - b._conf.isStart) || (a._conf.si - b._conf.si);
//!centerAngleDiff && console.log("aaa");
return centerAngleDiff;
},
getStartIndex = function (array) {
var l,
i,
startsAtFrontHalf,
startIndex = array[0] && array[0]._conf.index,
atFrontHalf,
index;
startsAtFrontHalf = startIndex <= pi;
for (i = 1, l = array.length; i < l; i += 1) {
index = array[i]._conf.index;
atFrontHalf = index <= pi;
if (atFrontHalf != startsAtFrontHalf || index < startIndex) {
return i;
}
}
return 0;
};
return function () {
var slicingWallsArr = this.slicingWallsArr,
x = 0,
sWall,
ln = slicingWallsArr.length,
startIndex,
lastElem2,
lastElem3,
index,
frontGroup = this.slicingWallsFrontGroup,
backGroup = this.slicingWallsBackGroup;
// sort the slicing walls for z-placing
slicingWallsArr.sort(elementZSortFn);
// find the index which cross the pi line
startIndex = getStartIndex(slicingWallsArr);
for (; x < ln; x += 1, startIndex += 1) {
if (startIndex === ln) {
startIndex = 0;
}
sWall = slicingWallsArr[startIndex], index = sWall._conf.index;
if (index < piBy2) {
frontGroup.appendChild(sWall);
}
else if (index <= pi) {
if (lastElem2) {
sWall.insertBefore(lastElem2);
}
else {
frontGroup.appendChild(sWall);
}
lastElem2 = sWall;
}
else if (index <= pi3By2) {
if (lastElem3) {
sWall.insertBefore(lastElem3);
}
else {
backGroup.appendChild(sWall);
}
lastElem3 = sWall;
}
else{
backGroup.appendChild(sWall);
}
}
};
})(),
// @todo: needs to be optimize
_setSliceShape : function (pointConf, doNotApply) {
var Pie3DManager = this,
getArcPath = Pie3DManager.getArcPath,
startOri = pointConf.start, endOri = pointConf.end,
start = normalizeAngle(startOri),
end = normalizeAngle(endOri),
scaleAngle,
startCos, startSin, endCos, endSin, startOuterX, startOuterY,
startOuterTopClipX, startOuterTopClipY, endOuterTopClipX, endOuterTopClipY,
startInnerX, startInnerY, endInnerX, endInnerY, startInnerY1, endInnerY1,
borderThickness = 1,
isDoughnut = Pie3DManager.isDoughnut,
radiusYFactor = pointConf.radiusYFactor,
cx = pointConf.cx,
cy = pointConf.cy,
rx = pointConf.r,
ry = rx * radiusYFactor,
topCliprx = rx + (hasSVG ? -borderThickness : 2),
topClipry = ry + (hasSVG ? -borderThickness : 2),
innerRx = pointConf.innerR || 0,
innerRy = innerRx * radiusYFactor,
depth = Pie3DManager.depth,
depthY = depth + cy,
rightX = cx + rx,
leftX = cx - rx,
rightInnerX = cx + innerRx,
leftInnerX = cx - innerRx,
topY = cy - ry,
bottomY = depthY + ry,
clipPathforNoClip = [M, leftInnerX, topY, L, leftInnerX, bottomY, Z],
elements = pointConf.elements,
startOuterY1, endOuterX, endOuterY, endOuterY1,
tempArr1, tempArr2, tempArr3, tempArr4, tempArr5, tempArr6,
moveCmdArr,
lineCmdArr,
closeCmdArr,
centerPoint,
leftPoint,
topPoint,
rightPoint,
bottomPoint,
leftDepthPoint,
rightDepthPoint,
leftInnerPoint,
rightInnerPoint,
leftInnerDepthPoint,
rightInnerDepthPoint,
pathAttrString = 'path',
middleAngle = (start + end) / 2, // not applicable for the slices that are crossing the 0deg
frontOuterIndex,
BackOuterIndex,
crossed2Pi = start > end;
startCos = mathCos(start);
startSin = mathSin(start);
endCos = mathCos(end);
endSin = mathSin(end);
startOuterX = cx + (rx * startCos);
startOuterY = cy + (ry * startSin);
startOuterTopClipX = cx + (topCliprx * startCos);
startOuterTopClipY = cy + (topClipry * startSin);
startOuterY1 = startOuterY + depth;
endOuterX = cx + (rx * endCos);
endOuterY = cy + (ry * endSin);
endOuterTopClipX = cx + (topCliprx * endCos);
endOuterTopClipY = cy + (topClipry * endSin);
endOuterY1 = endOuterY + depth;
if (isDoughnut) {//doughnut like slice
startInnerX = cx + (innerRx * startCos);
startInnerY = cy + (innerRy * startSin);
startInnerY1 = startInnerY + depth;
endInnerX = cx + (innerRx * endCos);
endInnerY = cy + (innerRy * endSin);
endInnerY1 = endInnerY + depth;
pointConf.startSlice = [M, startOuterX, startOuterY, L, startOuterX, startOuterY1, startInnerX,
startInnerY1, startInnerX, startInnerY, Z];
pointConf.endSlice = [M, endOuterX, endOuterY, L, endOuterX, endOuterY1, endInnerX, endInnerY1,
endInnerX, endInnerY, Z];
}
else {
pointConf.startSlice = [M, startOuterX, startOuterY, L, startOuterX, startOuterY1, cx, depthY, cx,
cy, Z];
pointConf.endSlice = [M, endOuterX, endOuterY, L, endOuterX, endOuterY1, cx, depthY, cx, cy, Z];
}
if (hasSVG) {
scaleAngle = getAbsScaleAngle (start, end);
//create the clip for top and bottom
if (!isDoughnut) {
pointConf.clipTopPath = [[M, startOuterX, startOuterY,
A, rx, ry, 0, (scaleAngle > pi ? 1 : 0), 1, endOuterX, endOuterY,
L, cx, cy,
Z]];
}
else {
pointConf.clipTopPath = [[M, startOuterX, startOuterY, A, rx, ry, 0, (scaleAngle > pi ? 1 : 0),
1, endOuterX, endOuterY, L, endInnerX, endInnerY, A, innerRx, innerRy,
0, (scaleAngle > pi ? 1 : 0), 0, startInnerX, startInnerY, Z]];
}
pointConf.clipOuterFrontPath1 = [clipPathforNoClip];
pointConf.clipTopBorderPath = [[M, startOuterTopClipX, startOuterTopClipY, A, topCliprx, topClipry,
0, (scaleAngle > pi ? 1 : 0), 1, endOuterTopClipX, endOuterTopClipY,
L, endOuterX, endOuterY, endOuterX, endOuterY + borderThickness, A,
rx, ry, 0, (scaleAngle > pi ? 1 : 0), 0, startOuterX, startOuterY +
borderThickness, L, startOuterX, startOuterY, Z]];
if (startOri != endOri) {
if(start > end) {//crossed the 0 deg line
if (start < pi){//crossed the 180 deg line also
pointConf.clipOuterFrontPath = [[M, rightX, cy,
A, rx, ry, 0, 0, 1, endOuterX, endOuterY,
V, depth,
A, rx, ry, 0, 0, 0, rightX, cy + depth,
Z]];
pointConf.clipOuterFrontPath1 = [[M, leftX, cy,
A, rx, ry, 0, 0, 0, startOuterX, startOuterY,
V, depth,
A, rx, ry, 0, 0, 1, leftX, cy + depth,
Z]];
pointConf.clipOuterBackPath = [[M, rightX, cy,
A, rx, ry, 0, 1, 0, leftX, cy,
V, depth,
A, rx, ry, 0, 1, 1, rightX, cy + depth,
Z]];
if (isDoughnut) {
pointConf.clipInnerBackPath = [[M, rightInnerX, cy,
A, innerRx, innerRy, 0, 1, 0, leftInnerX, cy,
V, depth,
A, innerRx, innerRy, 0, 1, 1, rightInnerX, cy +
depth, Z]];
pointConf.clipInnerFrontPath = [[M, rightInnerX, cy,
A, innerRx, innerRy, 0, 0, 1, endInnerX, endInnerY,
V, depth,
A, innerRx, innerRy, 0, 0, 0, rightInnerX, cy +
depth, Z,
M, leftInnerX, cy,
A, innerRx, innerRy, 0, 0, 0, startInnerX,
startInnerY, V, depth,
A, innerRx, innerRy, 0, 0, 1, leftInnerX, cy +
depth, Z]];
}
}
else if( end > pi) {//crossed the 180 deg line also
pointConf.clipOuterFrontPath = [[M, rightX, cy,
A, rx, ry, 0, 1, 1, leftX, cy,
V, depth,
A, rx, ry, 0, 1, 0, rightX, cy + depth,
Z]];
pointConf.clipOuterBackPath = [[M, leftX, cy,
A, rx, ry, 0, 0, 1, endOuterX, endOuterY,
V, depth,
A, rx, ry, 0, 0, 0, leftX, cy + depth,
Z,
M, rightX, cy,
A, rx, ry, 0, 0, 0, startOuterX, startOuterY,
V, depth,
A, rx, ry, 0, 0, 1, rightX, cy + depth,
Z]];
if (isDoughnut) {
pointConf.clipInnerFrontPath = [[M, rightInnerX, cy,
A, innerRx, innerRy, 0, 1, 1, leftInnerX, cy,
V, depth,
A, innerRx, innerRy, 0, 1, 0, rightInnerX, cy +
depth, Z]];
pointConf.clipInnerBackPath = [[M, leftInnerX, cy,
A, innerRx, innerRy, 0, 0, 1, endInnerX, endInnerY,
V, depth,
A, innerRx, innerRy, 0, 0, 0, leftInnerX, cy +
depth, Z,
M, rightInnerX, cy,
A, innerRx, innerRy, 0, 0, 0, startInnerX,
startInnerY, V, depth,
A, innerRx, innerRy, 0, 0, 1, rightInnerX, cy +
depth, Z]];
}
}
else {
pointConf.clipOuterFrontPath = [[M, rightX, cy,
A, rx, ry, 0, 0, 1, endOuterX, endOuterY,
V, depth,
A, rx, ry, 0, 0, 0, rightX, cy + depth,
Z]];
pointConf.clipOuterBackPath = [[M, startOuterX, startOuterY,
A, rx, ry, 0, 0, 1, rightX, cy,
V, depth,
A, rx, ry, 0, 0, 0, startOuterX, startOuterY1,
Z]];
if (isDoughnut) {
pointConf.clipInnerFrontPath = [[M, rightInnerX, cy,
A, innerRx, innerRy, 0, 0, 1, endInnerX, endInnerY,
V, depth,
A, innerRx, innerRy, 0, 0, 0, rightInnerX, cy +
depth, Z]];
pointConf.clipInnerBackPath = [[M, startInnerX, startInnerY,
A, innerRx, innerRy, 0, 0, 1, rightInnerX, cy,
V, depth,
A, innerRx, innerRy, 0, 0, 0, startInnerX,
startInnerY1, Z]];
}
}
}
else if (start < pi){
if (end > pi) {//crossed the 180 deg line only
pointConf.clipOuterFrontPath = [[M, startOuterX, startOuterY,
A, rx, ry, 0, 0, 1, leftX, cy,
V, depth,
A, rx, ry, 0, 0, 0, startOuterX, startOuterY1,
Z]];
pointConf.clipOuterBackPath = [[M, leftX, cy,
A, rx, ry, 0, 0, 1, endOuterX, endOuterY,
V, depth,
A, rx, ry, 0, 0, 0, leftX, cy + depth,
Z]];
if (isDoughnut) {
pointConf.clipInnerFrontPath = [[M, startInnerX, startInnerY,
A, innerRx, innerRy, 0, 0, 1, leftInnerX,
cy, V, depth,
A, innerRx, innerRy, 0, 0, 0, startInnerX,
startInnerY1, Z]];
pointConf.clipInnerBackPath = [[M, leftInnerX, cy,
A, innerRx, innerRy, 0, 0, 1, endInnerX,
endInnerY, V, depth,
A, innerRx, innerRy, 0, 0, 0, leftInnerX,
cy + depth, Z]];
}
}
else {//haven't crossed any thing
pointConf.clipOuterFrontPath = [[M, startOuterX, startOuterY,
A, rx, ry, 0, 0, 1, endOuterX, endOuterY,
V, depth,
A, rx, ry, 0, 0, 0, startOuterX, startOuterY1,
Z]];
pointConf.clipOuterBackPath = [clipPathforNoClip];
if (isDoughnut) {
pointConf.clipInnerFrontPath = [[M, startInnerX, startInnerY,
A, innerRx, innerRy, 0, 0, 1, endInnerX, endInnerY,
V, depth,
A, innerRx, innerRy, 0, 0, 0, startInnerX,
startInnerY1, Z]];
pointConf.clipInnerBackPath = [clipPathforNoClip];
}
}
}
else {//haven't crossed any thing
pointConf.clipOuterFrontPath = [clipPathforNoClip];
pointConf.clipOuterBackPath = [[M, startOuterX, startOuterY,
A, rx, ry, 0, 0, 1, endOuterX, endOuterY,
V, depth,
A, rx, ry, 0, 0, 0, startOuterX, startOuterY1,
Z]];
if (isDoughnut) {
pointConf.clipInnerFrontPath = [clipPathforNoClip];
pointConf.clipInnerBackPath = [[M, startInnerX, startInnerY,
A, innerRx, innerRy, 0, 0, 1, endInnerX, endInnerY,
V, depth,
A, innerRx, innerRy, 0, 0, 0, startInnerX, startInnerY1,
Z]];
}
}
}
else {//zero Pie
pointConf.clipOuterFrontPath = pointConf.clipOuterBackPath = pointConf.clipInnerBackPath =
pointConf.clipInnerFrontPath = [clipPathforNoClip];
}
pathAttrString = 'litepath';
pointConf.clipBottomBorderPath = pointConf.clipTopPath;
pointConf.startSlice = [pointConf.startSlice];
pointConf.endSlice = [pointConf.endSlice];
}
else {//for VML
moveCmdArr = Pie3DManager.moveCmdArr;
lineCmdArr = Pie3DManager.lineCmdArr;
closeCmdArr = Pie3DManager.closeCmdArr;
centerPoint = [cx, cy];
leftPoint = [leftX, cy];
topPoint = [cx, topY];
rightPoint = [rightX, cy];
bottomPoint = [cx, cy + ry];
leftDepthPoint = [leftX, depthY];
rightDepthPoint = [rightX, depthY];
leftInnerPoint = [leftInnerX, cy];
rightInnerPoint = [rightInnerX, cy];
leftInnerDepthPoint = [leftInnerX, depthY];
rightInnerDepthPoint = [rightInnerX, depthY];
pointConf.clipOuterFrontPath1 = [];
if (startOri != endOri) {
if(start > end) {//crossed the 0 deg line
if (start < pi){//crossed the 180 deg line also
tempArr1 = getArcPath(cx, cy, startOuterX, startOuterY, leftX, cy, rx, ry, 1, 0);
tempArr3 = getArcPath(cx, cy, leftX, cy, rightX, cy, rx, ry, 1, 0);
tempArr5 = getArcPath(cx, cy, rightX, cy, endOuterX, endOuterY, rx, ry, 1, 0);
pointConf.clipOuterBackPath = moveCmdArr.concat(leftPoint, tempArr3, lineCmdArr,
rightDepthPoint, getArcPath(cx, depthY, rightX,
depthY, leftX, depthY, rx, ry, 0, 0), closeCmdArr);
pointConf.clipOuterFrontPath1 = moveCmdArr.concat( [startOuterX, startOuterY], tempArr1,
lineCmdArr, leftDepthPoint, getArcPath(cx, depthY, leftX, depthY, startOuterX,
startOuterY1, rx, ry, 0, 0), closeCmdArr);
pointConf.clipOuterFrontPath = moveCmdArr.concat(rightPoint,
tempArr5, lineCmdArr, [endOuterX, endOuterY1], getArcPath(cx, depthY, endOuterX,
endOuterY1, rightX, depthY, rx, ry, 0, 0), closeCmdArr);
pointConf.clipTopBorderPath = moveCmdArr.concat([startOuterX, startOuterY], tempArr1,
tempArr3, tempArr5);
if (isDoughnut) {
tempArr2 = getArcPath(cx, cy, endInnerX, endInnerY, rightInnerX, cy, innerRx,
innerRy, 0, 0);
tempArr4 = getArcPath(cx, cy, rightInnerX, cy, leftInnerX, cy, innerRx,
innerRy, 0, 0);
tempArr6 = getArcPath(cx, cy, leftInnerX, cy, startInnerX, startInnerY,
innerRx, innerRy, 0, 0);
pointConf.clipInnerBackPath = moveCmdArr.concat(rightInnerPoint, tempArr4,
lineCmdArr, leftInnerDepthPoint,
getArcPath(cx, depthY, leftInnerX, depthY, rightInnerX, depthY,
innerRx, innerRy, 1, 0), closeCmdArr);
pointConf.clipInnerFrontPath = moveCmdArr.concat(leftInnerPoint, tempArr6,
lineCmdArr, [startInnerX, startInnerY1], getArcPath(cx, depthY, startInnerX,
startInnerY1, leftInnerX, depthY, innerRx, innerRy, 1, 0), closeCmdArr,
moveCmdArr,
[endInnerX, endInnerY], tempArr2, lineCmdArr, rightInnerDepthPoint,
getArcPath(cx, depthY, rightInnerX, depthY, endInnerX, endInnerY1,
innerRx, innerRy, 1, 0), closeCmdArr);
pointConf.clipTopPath = pointConf.clipTopBorderPath.concat(lineCmdArr, [endInnerX,
endInnerY], tempArr2, tempArr4, tempArr6, closeCmdArr);
pointConf.clipTopBorderPath = pointConf.clipTopBorderPath.concat(moveCmdArr,
[endInnerX, endInnerY], tempArr2, tempArr4, tempArr6);
}
else {
pointConf.clipTopPath = pointConf.clipTopBorderPath.concat(lineCmdArr, centerPoint,
closeCmdArr);
}
}
else if( end > pi) {//crossed the 180 deg line also
tempArr1 = getArcPath(cx, cy, startOuterX, startOuterY, rightX, cy, rx, ry, 1, 0);
tempArr3 = getArcPath(cx, cy, rightX, cy, leftX, cy, rx, ry, 1, 0);
tempArr5 = getArcPath(cx, cy, leftX, cy, endOuterX, endOuterY, rx, ry, 1, 0);
pointConf.clipOuterFrontPath = moveCmdArr.concat(rightPoint, tempArr3, lineCmdArr,
leftDepthPoint,
getArcPath(cx, depthY, leftX,
depthY, rightX, depthY, rx, ry, 0, 0), closeCmdArr);
pointConf.clipOuterBackPath = moveCmdArr.concat( [startOuterX, startOuterY], tempArr1,
lineCmdArr,
rightDepthPoint, getArcPath(cx, depthY, rightX,
depthY, startOuterX, startOuterY1, rx, ry, 0, 0), closeCmdArr, moveCmdArr,
leftPoint,
tempArr5, lineCmdArr, [endOuterX, endOuterY1], getArcPath(cx, depthY, endOuterX,
endOuterY1, leftX, depthY, rx, ry, 0, 0), closeCmdArr);
pointConf.clipTopBorderPath = moveCmdArr.concat([startOuterX, startOuterY], tempArr1,
tempArr3, tempArr5);
if (isDoughnut) {
tempArr2 = getArcPath(cx, cy, endInnerX, endInnerY, leftInnerX, cy, innerRx,
innerRy, 0, 0);
tempArr4 = getArcPath(cx, cy, leftInnerX, cy, rightInnerX, cy, innerRx,
innerRy, 0, 0);
tempArr6 = getArcPath(cx, cy, rightInnerX, cy, startInnerX, startInnerY,
innerRx, innerRy, 0, 0);
pointConf.clipInnerFrontPath = moveCmdArr.concat(leftInnerPoint, tempArr4,
lineCmdArr, rightInnerDepthPoint,
getArcPath(cx, depthY, rightInnerX, depthY, leftInnerX, depthY,
innerRx, innerRy, 1, 0), closeCmdArr);
pointConf.clipInnerBackPath = moveCmdArr.concat(rightInnerPoint, tempArr6,
lineCmdArr, [startInnerX, startInnerY1], getArcPath(cx, depthY, startInnerX,
startInnerY1, rightInnerX, depthY, innerRx, innerRy, 1, 0),
closeCmdArr, moveCmdArr, [endInnerX, endInnerY], tempArr2, lineCmdArr,
leftInnerDepthPoint, getArcPath(cx, depthY, leftInnerX, depthY,
endInnerX, endInnerY1, innerRx, innerRy, 1, 0), closeCmdArr);
pointConf.clipTopPath = pointConf.clipTopBorderPath.concat(lineCmdArr, [endInnerX,
endInnerY], tempArr2, tempArr4, tempArr6, closeCmdArr);
pointConf.clipTopBorderPath = pointConf.clipTopBorderPath.concat(moveCmdArr,
[endInnerX, endInnerY], tempArr2, tempArr4, tempArr6);
}
else {
pointConf.clipTopPath = pointConf.clipTopBorderPath.concat(lineCmdArr, centerPoint,
closeCmdArr);
}
}
else {
tempArr1 = getArcPath(cx, cy, startOuterX, startOuterY, rightX, cy, rx, ry, 1, 0);
tempArr3 = getArcPath(cx, cy, rightX, cy, endOuterX, endOuterY, rx, ry, 1, 0);
pointConf.clipOuterFrontPath = moveCmdArr.concat(rightPoint, tempArr3, lineCmdArr,
[endOuterX, endOuterY1], getArcPath(cx, depthY, endOuterX, endOuterY1, rightX,
depthY, rx, ry, 0, 0), closeCmdArr);
pointConf.clipOuterBackPath = moveCmdArr.concat( [startOuterX, startOuterY], tempArr1,
lineCmdArr, rightDepthPoint, getArcPath(cx, depthY, rightX, depthY,
startOuterX, startOuterY1, rx, ry, 0, 0), closeCmdArr);
pointConf.clipTopBorderPath = moveCmdArr.concat([startOuterX, startOuterY], tempArr1,
tempArr3);
if (isDoughnut) {
tempArr2 = getArcPath(cx, cy, endInnerX, endInnerY, rightInnerX, cy, innerRx,
innerRy, 0, 0);
tempArr4 = getArcPath(cx, cy, rightInnerX, cy, startInnerX, startInnerY,
innerRx, innerRy, 0, 0);
pointConf.clipInnerFrontPath = moveCmdArr.concat([endInnerX, endInnerY], tempArr2,
lineCmdArr, rightInnerDepthPoint,
getArcPath(cx, depthY, rightInnerX, depthY, endInnerX, endInnerY1, innerRx,
innerRy, 1, 0), closeCmdArr);
pointConf.clipInnerBackPath = moveCmdArr.concat(rightInnerPoint, tempArr4,
lineCmdArr, [startInnerX, startInnerY1], getArcPath(cx, depthY, startInnerX,
startInnerY1, rightInnerX, depthY, innerRx, innerRy, 1, 0),
closeCmdArr);
pointConf.clipTopPath = pointConf.clipTopBorderPath.concat(lineCmdArr, [endInnerX,
endInnerY], tempArr2, tempArr4, closeCmdArr);
pointConf.clipTopBorderPath = pointConf.clipTopBorderPath.concat(moveCmdArr,
[endInnerX, endInnerY], tempArr2, tempArr4);
}
else {
pointConf.clipTopPath = pointConf.clipTopBorderPath.concat(lineCmdArr, centerPoint,
closeCmdArr);
}
}
}
else if (start < pi){
if (end > pi) {//crossed the 180 deg line only
tempArr1 = getArcPath(cx, cy, startOuterX, startOuterY, leftX, cy, rx, ry, 1, 0);
tempArr3 = getArcPath(cx, cy, leftX, cy, endOuterX, endOuterY, rx, ry, 1, 0);
pointConf.clipOuterBackPath = moveCmdArr.concat(leftPoint, tempArr3, lineCmdArr,
[endOuterX, endOuterY1],
getArcPath(cx, depthY, endOuterX,
endOuterY1, leftX, depthY, rx, ry, 0, 0), closeCmdArr);
pointConf.clipOuterFrontPath = moveCmdArr.concat( [startOuterX, startOuterY], tempArr1,
lineCmdArr,
leftDepthPoint, getArcPath(cx, depthY, leftX,
depthY, startOuterX, startOuterY1, rx, ry, 0, 0), closeCmdArr);
pointConf.clipTopBorderPath = moveCmdArr.concat([startOuterX, startOuterY], tempArr1,
tempArr3);
if (isDoughnut) {
tempArr2 = getArcPath(cx, cy, endInnerX, endInnerY, leftInnerX, cy, innerRx,
innerRy, 0, 0);
tempArr4 = getArcPath(cx, cy, leftInnerX, cy, startInnerX, startInnerY,
innerRx, innerRy, 0, 0);
pointConf.clipInnerBackPath = moveCmdArr.concat([endInnerX, endInnerY], tempArr2,
lineCmdArr, leftInnerDepthPoint,
getArcPath(cx, depthY, leftInnerX, depthY, endInnerX, endInnerY1, innerRx,
innerRy, 1, 0), closeCmdArr);
pointConf.clipInnerFrontPath = moveCmdArr.concat(leftInnerPoint, tempArr4,
lineCmdArr, [startInnerX, startInnerY1], getArcPath(cx, depthY, startInnerX,
startInnerY1, leftInnerX, depthY, innerRx, innerRy, 1, 0),
closeCmdArr);
pointConf.clipTopPath = pointConf.clipTopBorderPath.concat(lineCmdArr, [endInnerX,
endInnerY], tempArr2, tempArr4, closeCmdArr);
pointConf.clipTopBorderPath = pointConf.clipTopBorderPath.concat(moveCmdArr,
[endInnerX, endInnerY], tempArr2, tempArr4);
}
else {
pointConf.clipTopPath = pointConf.clipTopBorderPath.concat(lineCmdArr, centerPoint,
closeCmdArr);
}
}
else {//haven't crossed any thing
tempArr1 = getArcPath(cx, cy, startOuterX, startOuterY, endOuterX, endOuterY, rx, ry, 1,
0);
pointConf.clipOuterBackPath = moveCmdArr.concat([startOuterX, startOuterY]);
pointConf.clipTopBorderPath = pointConf.clipOuterBackPath.concat(tempArr1);
pointConf.clipOuterFrontPath = pointConf.clipTopBorderPath.concat( lineCmdArr,
[endOuterX, endOuterY1], getArcPath(cx, depthY, endOuterX,
endOuterY1, startOuterX, startOuterY1, rx, ry, 0, 0), closeCmdArr);
if (isDoughnut) {
tempArr2 = getArcPath(cx, cy, endInnerX, endInnerY, startInnerX, startInnerY,
innerRx, innerRy, 0, 0);
pointConf.clipInnerBackPath = moveCmdArr.concat([endInnerX, endInnerY]);
pointConf.clipTopPath = pointConf.clipTopBorderPath.concat(lineCmdArr, [endInnerX,
endInnerY], tempArr2, closeCmdArr);
pointConf.clipTopBorderPath = pointConf.clipTopBorderPath.concat(moveCmdArr,
[endInnerX, endInnerY], tempArr2);
pointConf.clipInnerFrontPath = pointConf.clipInnerBackPath.concat(tempArr2,
lineCmdArr, [startInnerX, startInnerY1], getArcPath(cx, depthY, startInnerX,
startInnerY1, endInnerX, endInnerY1, innerRx, innerRy, 1, 0), closeCmdArr);
}
else {
pointConf.clipTopPath = pointConf.clipTopBorderPath.concat(lineCmdArr, centerPoint,
closeCmdArr);
}
}
}
else {//haven't crossed any thing
tempArr1 = getArcPath(cx, cy, startOuterX, startOuterY, endOuterX, endOuterY, rx, ry, 1, 0);
pointConf.clipOuterFrontPath = moveCmdArr.concat([startOuterX, startOuterY]);
pointConf.clipTopBorderPath = pointConf.clipOuterFrontPath.concat(tempArr1);
pointConf.clipOuterBackPath = pointConf.clipTopBorderPath.concat( lineCmdArr,
[endOuterX, endOuterY1], getArcPath(cx, depthY, endOuterX,
endOuterY1, startOuterX, startOuterY1, rx, ry, 0, 0), closeCmdArr);
if (isDoughnut) {
tempArr2 = getArcPath(cx, cy, endInnerX, endInnerY, startInnerX, startInnerY, innerRx,
innerRy, 0, 0);
pointConf.clipInnerFrontPath = moveCmdArr.concat([endInnerX, endInnerY]);
pointConf.clipTopPath = pointConf.clipTopBorderPath.concat(lineCmdArr, [endInnerX,
endInnerY], tempArr2, closeCmdArr);
pointConf.clipTopBorderPath = pointConf.clipTopBorderPath.concat(
pointConf.clipInnerFrontPath, tempArr2);
pointConf.clipInnerBackPath = pointConf.clipInnerFrontPath.concat(tempArr2,
lineCmdArr, [startInnerX, startInnerY1], getArcPath(cx, depthY, startInnerX,
startInnerY1, endInnerX, endInnerY1, innerRx, innerRy, 1, 0), closeCmdArr);
}
else {
pointConf.clipTopPath = pointConf.clipTopBorderPath.concat(lineCmdArr, centerPoint,
closeCmdArr);
}
}
//enlarge the bounded box so that the gradient works perfactly
tempArr1 = moveCmdArr.concat(leftPoint, lineCmdArr, rightPoint);
tempArr2 = moveCmdArr.concat(topPoint, lineCmdArr, bottomPoint);
pointConf.clipTopPath = pointConf.clipTopPath.concat(tempArr1, tempArr2);
pointConf.clipOuterFrontPath = pointConf.clipOuterFrontPath.concat(tempArr1);
pointConf.clipOuterFrontPath1 = pointConf.clipOuterFrontPath1.concat(tempArr1);
pointConf.clipOuterBackPath = pointConf.clipOuterBackPath.concat(tempArr1);
if (isDoughnut) {
tempArr2 = moveCmdArr.concat(leftInnerPoint, lineCmdArr, rightInnerPoint);
pointConf.clipInnerFrontPath = pointConf.clipInnerFrontPath.concat(tempArr2);
pointConf.clipInnerBackPath = pointConf.clipInnerBackPath.concat(tempArr2);
}
}
else {//zero Pie
pointConf.clipTopPath =
pointConf.clipOuterFrontPath =
pointConf.clipOuterBackPath = [];
if (isDoughnut) {
pointConf.clipInnerFrontPath =
pointConf.clipInnerBackPath = [];
}
}
pointConf.clipBottomBorderPath = pointConf.clipTopBorderPath;
}
//now apply the changes
if (!doNotApply) {
elements.startSlice._conf.index = start;
elements.endSlice._conf.index = end;
elements.backOuter._conf.index = BackOuterIndex =
(crossed2Pi && (start <= pi3By2 || end > pi3By2)) ||
(start <= pi3By2 && end > pi3By2) ? pi3By2 : (start > pi ? start : end);
elements.frontOuter._conf.index = frontOuterIndex = end <= piBy2 ? end : ((start > end ||
start <= piBy2) ? piBy2 : start);
elements.frontOuter1._conf.index = start;
elements.frontOuter1._conf.cIndex = pi;
if (start > end) {
elements.backOuter._conf.cIndex = start < pi3By2 ? pi3By2 : pi2;
elements.startSlice._conf.cIndex = start < pi ? (start + pi) / 2 : (start + pi2) / 2;
elements.endSlice._conf.cIndex = elements.frontOuter._conf.cIndex = 0;
}
else {
elements.backOuter._conf.cIndex = elements.startSlice._conf.cIndex =
elements.endSlice._conf.cIndex = elements.frontOuter._conf.cIndex = middleAngle;
}
if (scaleAngle > pi) {
elements.frontOuter1.show().attr(
pathAttrString, pointConf.clipOuterFrontPath1
);
}
else {
elements.frontOuter1.hide();
}
pointConf.thisElement._attr(
pathAttrString, pointConf.clipTopPath
);
elements.bottom.attr(
pathAttrString, pointConf.clipTopPath
);
elements.bottomBorder.attr(
pathAttrString, pointConf.clipBottomBorderPath
);
elements.topBorder && elements.topBorder.attr(
pathAttrString, pointConf.clipTopBorderPath
);
elements.frontOuter.attr(
pathAttrString, pointConf.clipOuterFrontPath
);
elements.backOuter.attr(
pathAttrString, pointConf.clipOuterBackPath
);
if (isDoughnut) {
elements.backInner.attr(
pathAttrString, pointConf.clipInnerBackPath
);
elements.frontInner.attr(
pathAttrString, pointConf.clipInnerFrontPath
);
elements.backInner._conf.index = BackOuterIndex;
elements.frontInner._conf.index = frontOuterIndex;
if (start > end) {
elements.backInner._conf.cIndex = pi2;
elements.frontInner._conf.cIndex = 0;
}
else {
elements.backInner._conf.cIndex =
elements.frontInner._conf.cIndex = middleAngle;
}
}
if (Pie3DManager.hasOnePoint) {
elements.startSlice.hide();
elements.endSlice.hide();
}
else {
elements.startSlice.attr(
pathAttrString, pointConf.startSlice
).show();
elements.endSlice.attr(
pathAttrString, pointConf.endSlice
).show();
}
}
},
_setSliceCosmetics: function (pointConf) {
var manager = this,
topElement = pointConf.thisElement,
showBorderEffect = pointConf.showBorderEffect,
elements = pointConf.elements,
colorObj,
borderColor = convertColor(pointConf.borderColor, pluckNumber(pointConf.borderAlpha,
pointConf.alpha)),
borderWidth = pointConf.borderWidth,
topAttrObj;
if (!pointConf.color) {
return;
}
colorObj = manager._parseSliceColor(pointConf.color, pointConf.alpha, pointConf);
if (hasSVG) {
topAttrObj = {fill: toRaphaelColor(colorObj.top), 'stroke-width': 0};
if (showBorderEffect) {
elements.topBorder.show().attr({
fill: toRaphaelColor(colorObj.topBorder),
'stroke-width': 0
});
}
else {
elements.topBorder.hide();
topAttrObj.stroke = borderColor;
topAttrObj['stroke-width'] = borderWidth;
}
// top
topElement._attr(topAttrObj);
}
else {
topElement._attr({
fill: toRaphaelColor(colorObj.top),
'stroke-width': 0
});
// top border
elements.topBorder.attr({
stroke: borderColor,
'stroke-width': borderWidth
});
}
// bottom
elements.bottom.attr({
fill: toRaphaelColor(colorObj.bottom)
});
// bottom
elements.bottomBorder.attr({
stroke: borderColor,
'stroke-width': borderWidth
});
elements.frontOuter.attr({
fill: toRaphaelColor(colorObj.frontOuter)
});
elements.frontOuter1.attr({
fill: toRaphaelColor(colorObj.frontOuter)
});
// outerback
elements.backOuter.attr({
fill: toRaphaelColor(colorObj.backOuter)
});
// startSlice
// whenAtBack
elements.startSlice.attr({
fill: toRaphaelColor(colorObj.startSlice),
stroke: borderColor,
'stroke-width': borderWidth
});
// endSlice
// whenAtBack
elements.endSlice.attr({
fill: toRaphaelColor(colorObj.endSlice),
stroke: borderColor,
'stroke-width': borderWidth
});
if (manager.isDoughnut) {
// innerFront
elements.frontInner.attr({
fill: toRaphaelColor(colorObj.frontInner)
});
elements.backInner.attr({
fill: toRaphaelColor(colorObj.backInner)
});
}
},
/**
* This function create a new 3d slice and return that for futher use
* @returns {slice} an raphael like composite object that can be used as a slice element
* @todo update slice color depending upon angle
* @todo For proper z-index create 2 back outer element for larger (>180 deg) slices
* @todo Optimize ***** the element creation, instead of outer1, back and from for all elements,
* create few extra element per manager, which will act as outer1, back / front (inner / outer wall) for
* slices, who has both. Because, at max 1 slice can have outer1 and only 2 slice can have
* back and front both
*/
createSlice: (function () {
var attrKeyList = {
//block following attribute
stroke: true,
strokeWidth: true,
'stroke-width': true,
dashstyle: true,
'stroke-dasharray': true,
translateX: true,
translateY: true,
'stroke-opacity': true,
fill: true,
opacity: true,
// attribute that has direct effect
transform: true,
ishot : true,
cursor: true,
start: true,
end : true,
color: true,
alpha: true,
borderColor: true,
borderAlpha: true,
borderWidth: true,
rolloverProps: true,
showBorderEffect: true,
positionIndex: true,
cx: true,
cy: true,
radiusYFactor: true,
r: true,
innerR: true
},
attrFN = function(hash, val) {
var key,
value,
slice = this,
confObject = slice._confObject,
commonAttr = {},
elements = confObject.elements,
x,
updateShape,
updateColor,
Pie3DManager = confObject.Pie3DManager,
applyCommonAttr,
attrObj;
// single key-value pair
if (isString(hash) && defined(val)) {
key = hash;
hash = {};
hash[key] = val;
}
// used as a getter: first argument is a string, second is undefined
if (!hash || isString(hash)) {
if (attrKeyList[hash]) {
slice = confObject[hash];
}
else {
slice = slice._attr(hash);
}
// setter
} else {
for (key in hash) {
value = hash[key];
//if belongs from the list then handle here
if (attrKeyList[key]) {
//store the att in confObject for further use
confObject[key] = value;
if (key === 'ishot' || key === 'cursor' || key === 'transform') {
attrObj = {};
commonAttr[key] = value;
applyCommonAttr = true;
}
/* jshint ignore:end */
else if (key === 'start' || key === 'end' || key === 'cx' || key === 'cy' ||
key === 'radiusYFactor' || key === 'r' || key === 'innerR') {
updateShape = true;
}
else if (key === 'color' || key === 'alpha' || key === 'borderColor' ||
key === 'borderAlpha' || key === 'borderWidth'){
updateColor = true;
}
} else { //else leve for the original attr
slice._attr(key, value);
}
}
// if paths need to be updated
if (updateShape) {
Pie3DManager._setSliceShape(confObject);
//refreash the drawinh for proper z lavel for elements
Pie3DManager.refreshDrawing();
}
// if colors need to be updated
// If the shape got changed, then also cange the color
if (updateColor || updateShape) {
Pie3DManager._setSliceCosmetics(confObject);
}
// apply common attributes
if (applyCommonAttr) {
//other elements
for (x in elements) {
elements[x].attr(commonAttr);
}
//main element
slice._attr(commonAttr);
}
}
return slice;
},
onFN = function (eventType, handler) {
var slice = this,
confObject = slice._confObject,
elements = confObject.elements,
element;
for (element in elements) {
elements[element].on(eventType, handler);
}
return slice._on(eventType, handler);
},
onDragFN = function (dragStart, dragMove, dragEnd) {
var element,
slice = this,
confObject = slice._confObject,
elements = confObject.elements,
navigator = win.navigator,
ua = navigator.userAgent.toLowerCase(),
isAndroid = ua.indexOf('android') > -1;
for (element in elements) {
if (isAndroid) {
if (element === 'topBorder' || element === 'frontOuter' || element === 'startSlice' ||
element === 'endSlice') {
elements[element].drag(dragStart, dragMove, dragEnd);
}
}
else {
elements[element].drag(dragStart, dragMove, dragEnd);
}
}
return slice._drag(dragStart, dragMove, dragEnd);
},
hideFN = function () {
var slice = this,
confObject = slice._confObject,
elements = confObject.elements,
element;
for (element in elements) {
elements[element].hide();
}
return slice._hide();
},
showFN = function () {
var slice = this,
confObject = slice._confObject,
elements = confObject.elements,
element;
for (element in elements) {
elements[element].show();
}
return slice._show();
},
destroyFN = function () {
var confObject = this._confObject,
elements = confObject.elements, x;
//other elements
for (x in elements) {
elements[x].destroy();
}
if (hasSVG) {
//destory other element
/** @todo check whether this clip elements are not destroying from else where */
confObject.clipTop.destroy();
confObject.clipOuterFront.destroy();
confObject.clipOuterBack.destroy();
if (confObject.clipOuterFront1) {
confObject.clipOuterFront1.destroy();
}
if (confObject.clipInnerFront) {
confObject.clipInnerFront.destroy();
}
if (confObject.clipInnerBack) {
confObject.clipInnerBack.destroy();
}
}
//main element
return this._destroy();
},
tooltipFN = function (tooltext) {
var slice = this,
confObject = slice._confObject,
elements = confObject.elements,
element;
//other elements
for (element in elements) {
elements[element].tooltip(tooltext);
}
//main element
return slice._tooltip(tooltext);
},
dataFN = function (key, value) {
var slice = this,
confObject = slice._confObject,
elements = confObject.elements,
element;
if (value === undefined) {
return slice._data(key);
}
else {
//other elements
for (element in elements) {
elements[element].data(key, value);
}
//main element
return slice._data(key, value);
}
},
si = 0; // slice index
return function () {
var manager = this,
renderer = manager.renderer,
returnElement,
confObject = {
elements : {},
Pie3DManager : manager
},
slicingWallsArr = manager.slicingWallsArr,
elements = confObject.elements,
renderingPath = hasSVG ? 'litepath' : 'path';
// create elements
returnElement = renderer[renderingPath](manager.topGroup);
//store the _confObject reference
returnElement._confObject = confObject;
confObject.thisElement = returnElement;
//modify few core function
returnElement._destroy = returnElement.destroy;
returnElement.destroy = destroyFN;
returnElement._show = returnElement.show;
returnElement.show = showFN;
returnElement._hide = returnElement.hide;
returnElement.hide = hideFN;
returnElement._on = returnElement.on;
returnElement.on = onFN;
returnElement._drag = returnElement.drag;
returnElement.drag = onDragFN;
returnElement._attr = returnElement.attr;
returnElement.attr = attrFN;
returnElement._tooltip = returnElement.tooltip;
returnElement.tooltip = tooltipFN;
returnElement._data = returnElement.data;
returnElement.data = dataFN;
//add the element to the store
manager.pointElemStore.push(returnElement);
elements.topBorder = renderer[renderingPath](manager.topGroup);
elements.bottom = renderer[renderingPath](manager.bottomBorderGroup)
.attr({
'stroke-width' : 0
});
elements.bottomBorder = renderer[renderingPath](manager.bottomBorderGroup);
elements.frontOuter = renderer[renderingPath](manager.slicingWallsFrontGroup)
.attr({
'stroke-width' : 0
});
elements.backOuter = renderer[renderingPath](manager.slicingWallsFrontGroup)
.attr({
'stroke-width' : 0
});
elements.startSlice = renderer[renderingPath](manager.slicingWallsFrontGroup);
elements.endSlice = renderer[renderingPath](manager.slicingWallsFrontGroup);
elements.frontOuter1 = renderer[renderingPath](manager.slicingWallsFrontGroup)
.attr({
'stroke-width' : 0
});
// update config object for proper z order
elements.frontOuter._conf = {
si: si,
isStart : 0.5
};
elements.frontOuter1._conf = {
si: si,
isStart : 0.5
};
elements.startSlice._conf = {
si: si,
isStart : 0
};
elements.endSlice._conf = {
si: si,
isStart : 1
};
elements.backOuter._conf = {
si: si,
isStart : 0.4
};
slicingWallsArr.push(elements.startSlice, elements.frontOuter1, elements.frontOuter,
elements.backOuter, elements.endSlice);
if (manager.isDoughnut) {
// innerFront
elements.frontInner = renderer[renderingPath](manager.slicingWallsFrontGroup)
.attr({
'stroke-width' : 0
});
elements.backInner = renderer[renderingPath](manager.slicingWallsFrontGroup)
.attr({
'stroke-width' : 0
});
elements.backInner._conf = {
si: si,
isStart : 0.5
};
elements.frontInner._conf = {
si: si,
isStart : 0.4
};
slicingWallsArr.push(elements.frontInner, elements.backInner);
}
si += 1;
return returnElement;
};
})()
};
Pie3DManager.prototype.constructor = Pie3DManager;
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-doughnut2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI,
getFirstColor = lib.getFirstColor,
getFirstAlpha = lib.getFirstAlpha,
hasSVG = lib.hasSVG,
math = Math,
docMode8 = win.document.documentMode === 8,
toRaphaelColor = lib.toRaphaelColor,
hashify = lib.hashify,
HIDDEN = 'hidden',
VISIBLE = docMode8 ? 'visible' : '',
getLightColor = lib.graphics.getLightColor,
mathFloor = math.floor,
COMMA = ',',
getDarkColor = lib.graphics.getDarkColor;
chartAPI ('doughnut2d', {
friendlyName: 'Doughnut Chart',
defaultDatasetType : 'Doughnut2D',
creditLabel: creditLabel,
applicableDSList: {
'Doughnut2D': true
},
getPointColor: function (color, alpha, radius3D) {
var colorObj,
loLight,
hiLight;
color = getFirstColor (color);
alpha = getFirstAlpha (alpha);
// Radial gradient is not supported in VML, hence we use for SVG
// alone.
if (radius3D < 100 && hasSVG) {
loLight = getDarkColor (color,
mathFloor ((85 - 0.2 * (100 - radius3D)) * 100) / 100);
hiLight = getLightColor (color,
mathFloor ((100 - 0.5 * radius3D) * 100) / 100);
colorObj = {
FCcolor: {
color: loLight + COMMA + hiLight + COMMA + hiLight +
COMMA + loLight,
alpha: alpha + COMMA + alpha + COMMA + alpha + COMMA + alpha,
radialGradient: true,
gradientUnits: 'userSpaceOnUse',
r: radius3D
}
};
}
else {
/** @todo replace the single shade radial to solid fill */
colorObj = {
FCcolor: {
color: color + COMMA + color,
alpha: alpha + COMMA + alpha,
ratio: '0,100'
}
};
}
return colorObj;
},
drawDoughnutCenterLabel: function (labelText, cx, cy, dx, dy, centerLabelConfig, updateConfig) {
var chart = this,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
seriesData = dataSet.config,
labelConfig = centerLabelConfig || seriesData.lastCenterLabelConfig,
paper = chartComponents.paper,
smartLabel = chart.linkedItems.smartLabel,
chartGraphics = chart.graphics,
grp = chartGraphics.datasetGroup,
labelPadding = labelConfig.padding,
textpadding = labelConfig.textPadding * 2,
cssObj = {
fontFamily: labelConfig.font,
fontSize: labelConfig.fontSize + 'px',
lineHeight: (1.2 * labelConfig.fontSize) + 'px',
fontWeight: labelConfig.bold ? 'bold' : '',
fontStyle: labelConfig.italic ? 'italic' : ''
},
txtW = ((dx * 0.5 - labelPadding) * 1.414) - textpadding,
txtH = ((dy * 0.5 - labelPadding) * 1.414) - textpadding,
centerLabel,
smartLabelObj,
labelOvalBg;
smartLabel.setStyle(cssObj);
smartLabel.useEllipsesOnOverflow(chart.config.useEllipsesWhenOverflow);
smartLabelObj = smartLabel.getSmartText(labelText, txtW, txtH);
if (!(centerLabel = chartGraphics.doughnutCenterLabel)) {
labelConfig.bgOval &&
(chartGraphics.centerLabelOvalBg = labelOvalBg = paper.circle(cx, cy, dx * 0.5 - labelPadding,
grp));
centerLabel = chartGraphics.doughnutCenterLabel = paper.text(grp)
.hover(chart.centerLabelRollover, chart.centerLabelRollout)
.click(chart.centerLabelClick);
/** @todo: Fix reference issue */
centerLabel.chart = chart;
}
else {
centerLabel.attr('text') !== labelText && chart.centerLabelChange(labelText);
labelOvalBg = chartGraphics.centerLabelOvalBg;
}
if (labelText) {
centerLabel.css(cssObj)
.attr({
x: cx,
y: cy,
text: smartLabelObj.text,
visibility: VISIBLE,
direction: seriesData.textDirection,
//title: labelConfig.toolText ? '' : smartLabelObj.tooltext || '',
fill: toRaphaelColor({
FCcolor: {
color: labelConfig.color,
alpha: labelConfig.alpha
}
}),
'text-bound': labelConfig.bgOval ? 'none' : [
toRaphaelColor({
FCcolor: {
color: labelConfig.bgColor,
alpha: labelConfig.bgAlpha
}
}),
toRaphaelColor({
FCcolor: {
color: labelConfig.borderColor,
alpha: labelConfig.borderAlpha
}
}),
labelConfig.borderThickness,
labelConfig.textPadding,
labelConfig.borderRadius
]
})
.tooltip(labelConfig.toolText || smartLabelObj.tooltext);
if (labelConfig.bgOval) {
labelOvalBg && labelOvalBg.attr({
visibility: VISIBLE,
fill: hashify(labelConfig.bgColor),
'fill-opacity': labelConfig.bgAlpha / 100,
stroke: hashify(labelConfig.borderColor),
'stroke-width': labelConfig.borderThickness,
'stroke-opacity': labelConfig.borderAlpha / 100
});
}
}
else {
centerLabel.attr('visibility', HIDDEN);
labelOvalBg && labelOvalBg.attr('visibility', HIDDEN);
}
if (updateConfig) {
seriesData.lastCenterLabelConfig = labelConfig;
seriesData.centerLabelConfig = labelConfig;
}
},
centerLabelRollover: function () {
var chart = this.chart,
chartComponents = chart.components,
chartConfig = chart.config,
dataSet = chartComponents.dataset[0],
chartInstance = chart.chartInstance,
seriesData = dataSet.config,
ref = chartInstance.ref,
labelConfig = seriesData.lastCenterLabelConfig,
eventArgs = {
height: chartConfig.height,
width: chartConfig.width,
pixelHeight: ref.offsetHeight,
pixelWidth: ref.offsetWidth,
id: chartInstance.id,
renderer: chartInstance.args.renderer,
container: chart.linkedItems.container,
centerLabelText: labelConfig && labelConfig.label
};
/**
* This event is fired on mouse rollover on label at center of doughnut 2D.
*
* > Available on `doughnut` chart only.
*
* @group chart:pie-center-label
* @event FusionCharts#centerLabelRollover
*
* @param {string} centerLabelText - is the text for display at center label
* @param {number} chartX - is the relative X-Cordinate to chart container where the chart was clicked
* @param {number} chartY - is the relative Y-Cordinate to chart container where the chart was clicked.
* @param {string} container - is the DOM element where the chart is being rendered.
* @param {numeric|percent} height - height of the chart
* @param {numeric|percent} width - width of the chart
* @param {string} id - is the chart id
* @param {number} pageX - is the relative X-Cordinate to screen where the chart is clicked
* @param {number} pageY - is the relative Y-Cordinate to screen where the chart is clicked
* @param {number} pixelHeight - is the height of the DOM element where the chart is being rendered in
* pixels
* @param {number} pixelWidth - is the width of the DOM element where the chart is being rendered in
* pixels
* @param {string} renderer - tells if the chart is rendered using JavaScript or Flash
*/
this.attr('text') && global.raiseEvent('centerLabelRollover',
eventArgs, chartInstance, this, chart.hoverOnCenterLabel);
},
centerLabelRollout: function () {
var chart = this.chart,
chartConfig = chart.config,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
chartInstance = chart.chartInstance,
ref = chartInstance.ref,
seriesData = dataSet.config,
labelConfig = seriesData.lastCenterLabelConfig,
eventArgs = {
height: chartConfig.height,
width: chartConfig.width,
pixelHeight: ref.offsetHeight,
pixelWidth: ref.offsetWidth,
id: chartInstance.id,
renderer: chartInstance.args.renderer,
container: chart.linkedItems.container,
centerLabelText: labelConfig && labelConfig.label
};
/**
* This event is fired on mouse rollout from label at center of
* doughnut 2D.
*
* > Available on `doughnut` chart only.
*
* @group chart:pie-center-label
* @event FusionCharts#centerLabelRollout
*
* @param {string} centerLabelText - is the text for display at center label
* @param {number} chartX - is the relative X-Cordinate to chart container where the chart was clicked
* @param {number} chartY - is the relative Y-Cordinate to chart container where the chart was clicked.
* @param {string} container - is the DOM element where the chart is being rendered.
* @param {numeric|percent} height - height of the chart
* @param {numeric|percent} width - width of the chart
* @param {string} id - is the chart id
* @param {number} pageX - is the relative X-Cordinate to screen where the chart is clicked
* @param {number} pageY - is the relative Y-Cordinate to screen where the chart is clicked
* @param {number} pixelHeight - is the height of the DOM element where the chart is being rendered in
* pixels
* @param {number} pixelWidth - is the width of the DOM element where the chart is being rendered in
* pixels
* @param {string} renderer - tells if the chart is rendered using JavaScript or Flash
*/
this.attr('text') && global.raiseEvent('centerLabelRollout', eventArgs, chartInstance, this,
chart.hoverOffCenterLabel);
},
centerLabelClick: function () {
var chart = this.chart,
chartConfig = chart.config,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
chartInstance = chart.chartInstance,
ref = chartInstance.ref,
seriesData = dataSet.config,
labelConfig = seriesData.lastCenterLabelConfig,
eventArgs = {
height: chartConfig.height,
width: chartConfig.width,
pixelHeight: ref.offsetHeight,
pixelWidth: ref.offsetWidth,
id: chartInstance.id,
renderer: chartInstance.args.renderer,
container: chart.linkedItems.container,
centerLabelText: labelConfig && labelConfig.label
};
/**
* This event is fired on click on label at center of doughnut 2D.
*
* > Available on `doughnut` chart only.
*
* @group chart:pie-center-label
* @event FusionCharts#centerLabelClick
*
* @param {string} centerLabelText - is the text for display at center label.
* @param {number} chartX - is the relative X-Cordinate to chart container where the chart was clicked.
* @param {number} chartY - is the relative Y-Cordinate to chart container where the chart was clicked.
* @param {string} container - is the DOM element where the chart is being rendered.
* @param {numeric|percent} height - height of the chart
* @param {numeric|percent} width - width of the chart
* @param {string} id - is the chart id
* @param {number} pageX - is the relative X-Cordinate to screen where the chart is clicked
* @param {number} pageY - is the relative Y-Cordinate to screen where the chart is clicked
* @param {number} pixelHeight - is the height of the DOM element where the chart is being rendered in
* pixels
* @param {number} pixelWidth - is the width of the DOM element where the chart is being rendered in
* pixels
* @param {string} renderer - tells if the chart is rendered using JavaScript or Flash
*/
this.attr('text') && global.raiseEvent('centerLabelClick', eventArgs, chartInstance);
},
centerLabelChange: function (labelText) {
var chart = this,
chartConfig = chart.config,
chartInstance = chart.chartInstance,
ref = chartInstance.ref,
eventArgs = {
height: chartConfig.height,
width: chartConfig.width,
pixelHeight: ref.offsetHeight,
pixelWidth: ref.offsetWidth,
id: chartInstance.id,
renderer: chartInstance.args.renderer,
container: chart.linkedItems.container,
centerLabelText: labelText
};
/**
* This event is fired on change of label at center of doughnut 2D.
*
* > Available on `doughnut` chart only.
*
* @group chart:pie-center-label
* @event FusionCharts#centerLabelChanged
*
* @param {string} centerLabelText - is the text for display at center label
* @param {number} chartX - is the relative X-Cordinate to chart container where the chart was clicked
* @param {number} chartY - is the relative Y-Cordinate to chart container where the chart was clicked.
* @param {string} container - is the DOM element where the chart is being rendered.
* @param {numeric|percent} height - height of the chart
* @param {numeric|percent} width - width of the chart
* @param {string} id - is the chart id
* @param {number} pageX - is the relative X-Cordinate to screen where the chart is clicked
* @param {number} pageY - is the relative Y-Cordinate to screen where the chart is clicked
* @param {number} pixelHeight - is the height of the DOM element where the chart is being rendered in
* pixels
* @param {number} pixelWidth - is the width of the DOM element where the chart is being rendered in
* pixels
* @param {string} renderer - tells if the chart is rendered using JavaScript or Flash
*/
global.raiseEvent('centerLabelChanged', eventArgs, chartInstance);
},
hoverOnCenterLabel: function () {
var chart = this.chart,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
seriesData = dataSet.config,
labelConfig = seriesData.lastCenterLabelConfig;
if (labelConfig.hoverColor || labelConfig.hoverAlpha) {
this.attr({fill: toRaphaelColor({
FCcolor: {
color: labelConfig.hoverColor || labelConfig.color,
alpha: labelConfig.hoverAlpha || labelConfig.alpha
}
})
});
}
},
hoverOffCenterLabel: function () {
var chart = this.chart,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
seriesData = dataSet.config,
labelConfig = seriesData.lastCenterLabelConfig;
if (labelConfig.hoverColor || labelConfig.hoverAlpha) {
this.attr({fill: toRaphaelColor({
FCcolor: {
color: labelConfig.color,
alpha: labelConfig.alpha
}
})
});
}
}
}, chartAPI.pie2d, {
singletonPlaceValue: false
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-doughnut3d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('doughnut3d', {
friendlyName: '3D Doughnut Chart',
defaultDatasetType : 'Doughnut3D',
creditLabel: creditLabel,
applicableDSList: {
'Doughnut3D': true
},
_configureManager: function () {
var chart = this,
chartComponents = chart.components,
dataSet = chartComponents.dataset[0],
dataSetConfig = dataSet.config,
dataSetComponents = dataSet.components,
pie3DManager = dataSetComponents.Pie3DManager,
data = dataSetComponents.data;
if (pie3DManager) {
pie3DManager.configure(dataSetConfig.pieSliceDepth, data.length === 1, dataSetConfig.use3DLighting,
true);
}
}
}, chartAPI.pie3d);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-mscolumn2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('mscolumn2d', {
standaloneInit: true,
friendlyName: 'Multi-series Column Chart',
creditLabel: creditLabel,
defaultDatasetType : 'column',
applicableDSList: { 'column': true },
eiMethods : { }
},chartAPI.mscartesian, {
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-mscolumn3d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('mscolumn3d', {
standaloneInit: true,
defaultDatasetType: 'column3d',
friendlyName: 'Multi-series 3D Column Chart',
applicableDSList: { 'column3d': true },
// Default shadow is visible for 3D variant of MSColumn2D chart
defaultPlotShadow: 1,
fireGroupEvent: true,
is3D: true,
creditLabel: creditLabel,
defaultZeroPlaneHighlighted: false
}, chartAPI.mscartesian3d, {
showplotborder: 0,
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-msbar2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname);
chartAPI ('msbar2d', {
standaloneInit: true,
friendlyName: 'Multi-series Bar Chart',
isBar: true,
hasLegend: true,
creditLabel: creditLabel,
defaultDatasetType: 'bar2d',
applicableDSList: { 'bar2d': true }
}, chartAPI.msbarcartesian, {
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-msbar3d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname);
chartAPI ('msbar3d', {
standaloneInit: true,
defaultSeriesType: 'bar3d',
friendlyName: 'Multi-series 3D Bar Chart',
fireGroupEvent: true,
defaultPlotShadow: 1,
is3D: true,
isBar: true,
hasLegend: true,
creditLabel: creditLabel,
defaultZeroPlaneHighlighted: false,
defaultDatasetType: 'bar3d',
applicableDSList: { 'bar3d': true }
}, chartAPI.msbarcartesian3d, {
showplotborder: 0,
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-msarea', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('msarea', {
standaloneInit: true,
friendlyName: 'Multi-series Area Chart',
creditLabel: creditLabel,
defaultDatasetType : 'area',
defaultPlotShadow: 0,
applicableDSList: { 'area': true }
}, chartAPI.areabase, {
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-msline', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname);
chartAPI ('msline', {
standaloneInit: true,
friendlyName: 'Multi-series Line Chart',
creditLabel: creditLabel,
defaultDatasetType : 'line',
defaultPlotShadow: 1,
axisPaddingLeft: 0,
axisPaddingRight: 0,
applicableDSList: { 'line': true }
}, chartAPI.areabase, {
zeroplanethickness: 1,
zeroplanealpha: 40,
showzeroplaneontop: 0,
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-stackedarea2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
preDefStr = lib.preDefStr,
HUNDREDSTRING = preDefStr.HUNDREDSTRING;
chartAPI ('stackedarea2d', {
friendlyName: 'Stacked Area Chart',
showsum: 0,
creditLabel: creditLabel
}, chartAPI.msarea, {
plotfillalpha: HUNDREDSTRING,
isstacked: 1
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-stackedcolumn2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname);
chartAPI ('stackedcolumn2d', {
friendlyName: 'Stacked Column Chart',
creditLabel: creditLabel
}, chartAPI.mscolumn2d, {
isstacked: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-stackedcolumn3d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('stackedcolumn3d', {
friendlyName: '3D Stacked Column Chart',
creditLabel: creditLabel,
_mouseEvtHandler: function (e) {
var iapi = this,
data = e.data,
mouseTracker = data.mouseTracker,
oriEvent = e.originalEvent,
chartConfig = iapi.config,
canvasLeft = chartConfig.canvasLeft,
canvasRight = chartConfig.canvasRight,
canvasBottom = chartConfig.canvasBottom,
canvasTop = chartConfig.canvasTop,
datasets = chartConfig.datasetOrder || iapi.components.dataset,
coordinate = lib.getMouseCoordinate(iapi.linkedItems.container, oriEvent, iapi),
chartX = coordinate.chartX,
chartY = coordinate.chartY,
dataset,
chartComponents = iapi.components,
group = 'datasetGroup_' + datasets[0].type,
hoveredInfo,
pointFound = false,
i = datasets.length,
j,
l,
derivedEvensInfo,
_lastDatasetIndex = mouseTracker._lastDatasetIndex,
_lastPointIndex = mouseTracker._lastPointIndex,
MOUSEOUT = 'mouseout';
// todo we have to implement this for charts with more than one canvas like candle stick
// if inside the canvas
if (chartX > canvasLeft && chartX < canvasRight && chartY > canvasTop && chartY < canvasBottom ||
iapi.config.plotOverFlow) {
// todo make sure the datasets are as per their z-order
while (i-- && !pointFound) {
dataset = datasets[i];
dataset.valueLook = true;
if (dataset && dataset.visible) {
hoveredInfo = dataset._getHoveredPlot && dataset._getHoveredPlot(chartX, chartY);
if (hoveredInfo && hoveredInfo.hovered) {
pointFound = true;
hoveredInfo.datasetIndex = i;
derivedEvensInfo = mouseTracker._getMouseEvents(e, hoveredInfo.datasetIndex,
hoveredInfo.pointIndex);
}
}
}
i = 0;
while (i < datasets.length) {
if (pointFound) {
break;
}
dataset = datasets[i];
dataset.valueLook = false;
if (dataset && dataset.visible) {
hoveredInfo = dataset._getHoveredPlot && dataset._getHoveredPlot(chartX, chartY);
if (hoveredInfo && hoveredInfo.hovered) {
pointFound = true;
hoveredInfo.datasetIndex = i;
derivedEvensInfo = mouseTracker._getMouseEvents(e, hoveredInfo.datasetIndex,
hoveredInfo.pointIndex);
}
}
i++;
}
}
// todo instead of sending event names, create a event object of that type and send it
// fire out on last hovered plot
if ((!pointFound || (derivedEvensInfo && derivedEvensInfo.fireOut)) &&
_lastDatasetIndex !== undefined) {
// delete stored last ds details
delete mouseTracker._lastDatasetIndex;
delete mouseTracker._lastPointIndex;
datasets[_lastDatasetIndex] && datasets[_lastDatasetIndex]._firePlotEvent &&
datasets[_lastDatasetIndex]._firePlotEvent(MOUSEOUT, _lastPointIndex, e);
// todo scope to have sticky tracked tooltip
}
// fire remaining events
if (pointFound) {
l = derivedEvensInfo.events && derivedEvensInfo.events.length;
// store the index of the hovered DS and plot
mouseTracker._lastDatasetIndex = hoveredInfo.datasetIndex;
_lastPointIndex = mouseTracker._lastPointIndex = hoveredInfo.pointIndex;
chartConfig.drawTrendRegion && chartComponents[group]._notifyGroup(true, e);
for (j = 0; j < l; j += 1) {
dataset && dataset._firePlotEvent && dataset._firePlotEvent(derivedEvensInfo.events[j],
_lastPointIndex, e, hoveredInfo.datasetIndex);
}
}
else {
chartConfig.drawTrendRegion && chartComponents[group]._notifyGroup(false, e);
}
chartConfig.drawTrendRegion && chartComponents[group]._getHoveredRegion(chartX, chartY, e);
}
}, chartAPI.mscolumn3d, {
showplotborder: 0,
enablemousetracking: true
}, chartAPI.stackedcolumn2d);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-stackedbar2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('stackedbar2d', {
friendlyName: 'Stacked Bar Chart',
creditLabel: creditLabel
}, chartAPI.msbar2d, {
maxbarheight: 50,
enablemousetracking: true
}, chartAPI.stackedcolumn2d);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-stackedbar3d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('stackedbar3d', {
friendlyName: '3D Stacked Bar Chart',
creditLabel: creditLabel,
_mouseEvtHandler: function (e) {
var iapi = this,
data = e.data,
mouseTracker = data.mouseTracker,
oriEvent = e.originalEvent,
chartConfig = iapi.config,
canvasLeft = chartConfig.canvasLeft,
canvasRight = chartConfig.canvasRight,
canvasBottom = chartConfig.canvasBottom,
canvasTop = chartConfig.canvasTop,
datasets = chartConfig.datasetOrder || iapi.components.dataset,
coordinate = lib.getMouseCoordinate(iapi.linkedItems.container, oriEvent, iapi),
chartX = coordinate.chartX,
chartY = coordinate.chartY,
dataset,
chartComponents = iapi.components,
group = 'datasetGroup_' + datasets[0].type,
hoveredInfo,
pointFound = false,
i = datasets.length,
k,
j,
l,
derivedEvensInfo,
_lastDatasetIndex = mouseTracker._lastDatasetIndex,
_lastPointIndex = mouseTracker._lastPointIndex,
MOUSEOUT = 'mouseout',
datasetBuffer,
hoveredInfoBuffer;
// todo we have to implement this for charts with more than one canvas like candle stick
// if inside the canvas
if (chartX > canvasLeft && chartX < canvasRight && chartY > canvasTop && chartY < canvasBottom ||
iapi.config.plotOverFlow) {
// todo make sure the datasets are as per their z-order
i = datasets.length;
while (i-- && !pointFound) {
dataset = datasets[i];
dataset.valueLook = true;
if (dataset && dataset.visible) {
hoveredInfo = dataset._getHoveredPlot && dataset._getHoveredPlot(chartX, chartY);
if (hoveredInfo && hoveredInfo.hovered) {
for (k = 0; k <= i; ++k) {
if (i === k || !hoveredInfo) {
continue;
}
datasetBuffer = datasets[k];
hoveredInfoBuffer = datasetBuffer._getHoveredPlot &&
datasetBuffer._getHoveredPlot(chartX, chartY);
if (hoveredInfoBuffer &&
hoveredInfoBuffer.pointIndex === hoveredInfo.pointIndex - 1) {
hoveredInfo = hoveredInfoBuffer;
i = k;
dataset = datasetBuffer;
dataset.valueLook = true;
break;
}
}
}
if (hoveredInfo && hoveredInfo.hovered) {
pointFound = true;
hoveredInfo.datasetIndex = i;
derivedEvensInfo = mouseTracker._getMouseEvents(e, hoveredInfo.datasetIndex,
hoveredInfo.pointIndex);
}
}
}
i = 0;
while (i < datasets.length) {
if (pointFound) {
break;
}
dataset = datasets[i];
dataset.valueLook = false;
if (dataset && dataset.visible) {
hoveredInfo = dataset._getHoveredPlot && dataset._getHoveredPlot(chartX, chartY);
if (hoveredInfo && hoveredInfo.hovered) {
for (k = 0; k <= i; ++k) {
if (i === k || !hoveredInfo) {
continue;
}
datasetBuffer = datasets[k];
hoveredInfoBuffer = datasetBuffer._getHoveredPlot &&
datasetBuffer._getHoveredPlot(chartX, chartY);
if (hoveredInfoBuffer &&
hoveredInfoBuffer.pointIndex === hoveredInfo.pointIndex - 1) {
hoveredInfo = hoveredInfoBuffer;
i = k;
dataset = datasetBuffer;
dataset.valueLook = false;
break;
}
}
}
if (hoveredInfo && hoveredInfo.hovered) {
pointFound = true;
hoveredInfo.datasetIndex = i;
derivedEvensInfo = mouseTracker._getMouseEvents(e, hoveredInfo.datasetIndex,
hoveredInfo.pointIndex);
}
}
i++;
}
}
// todo instead of sending event names, create a event object of that type and send it
// fire out on last hovered plot
if ((!pointFound || (derivedEvensInfo && derivedEvensInfo.fireOut)) &&
_lastDatasetIndex !== undefined) {
// delete stored last ds details
delete mouseTracker._lastDatasetIndex;
delete mouseTracker._lastPointIndex;
datasets[_lastDatasetIndex] && datasets[_lastDatasetIndex]._firePlotEvent &&
datasets[_lastDatasetIndex]._firePlotEvent(MOUSEOUT, _lastPointIndex, e);
// todo scope to have sticky tracked tooltip
}
// fire remaining events
if (pointFound) {
l = derivedEvensInfo.events && derivedEvensInfo.events.length;
// store the index of the hovered DS and plot
mouseTracker._lastDatasetIndex = hoveredInfo.datasetIndex;
_lastPointIndex = mouseTracker._lastPointIndex = hoveredInfo.pointIndex;
chartConfig.drawTrendRegion && chartComponents[group]._notifyGroup(true, e);
for (j = 0; j < l; j += 1) {
dataset && dataset._firePlotEvent && dataset._firePlotEvent(derivedEvensInfo.events[j],
_lastPointIndex, e, hoveredInfo.datasetIndex);
}
}
else {
chartConfig.drawTrendRegion && chartComponents[group]._notifyGroup(false, e);
}
chartConfig.drawTrendRegion && chartComponents[group]._getHoveredRegion(chartX, chartY, e);
}
/*,
_mouseEvtHandler: function (e) {
chartAPI.stackedcolumn3d._mouseEvtHandler.call(this, e);
}*/
}, chartAPI.msbar3d, {
showplotborder: 0,
enablemousetracking: true
}, chartAPI.stackedcolumn2d);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-marimekko', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
math = Math,
mathMin = math.min,
mathMax = math.max;
chartAPI ('marimekko', {
standaloneInit: true,
friendlyName: 'Marimekko Chart',
isValueAbs: true,
distributedColumns: true,
stack100percent: true,
defaultDatasetType: 'marimekko',
applicableDSList: {'marimekko': true},
isStacked: true,
showsum: 1,
creditLabel: creditLabel,
_setAxisLimits : function () {
var iapi = this,
components = iapi.components,
dataset = components.dataset,
yAxis = components.yAxis,
xAxis = components.xAxis,
currentDataset,
length = dataset.length,
i,
infMin = -Infinity,
infMax = +Infinity,
max = infMin,
min = infMax,
xMin = infMax,
xMax = infMin,
lastXPos,
maxminObj,
groupManager,
xLimit,
xRange,
len,
catLen,
value,
catXVal,
groupManagerObj = { },
categories = iapi.config.categories,
stackPercentValue,
stackValWidth,
noManager = [],
getMaxMin = function (maxminObj) {
max = mathMax (max, maxminObj.max);
min = mathMin (min, maxminObj.min);
xMax = mathMax (xMax, maxminObj.xMax || infMin);
xMin = mathMin (xMin, maxminObj.xMin || infMax);
};
for (i=0; i<length; i++) {
currentDataset = dataset[i];
groupManager = currentDataset.groupManager;
if (groupManager) {
groupManagerObj[currentDataset.type] = groupManager;
}
else {
noManager.push (currentDataset);
}
}
for (groupManager in groupManagerObj) {
maxminObj = groupManagerObj[groupManager].getDataLimits ();
getMaxMin (maxminObj);
}
length =noManager.length;
for (i=0; i<length; i++) {
maxminObj = noManager[i].getDataLimits ();
getMaxMin (maxminObj);
}
(max === -Infinity) && (max = 0);
(min === +Infinity) && (min = 0);
yAxis[0].setAxisConfig ( {
isPercent : iapi.config.isstacked ? iapi.config.stack100percent : 0
});
yAxis[0].setDataLimit (max, min);
if ((xMax !== infMin) || (xMin !== infMax)) {
xAxis[0].config.xaxisrange = {
max : xMax,
min : xMin
};
xAxis[0].setDataLimit (xMax, xMin);
}
// get the stack sum values and calculate the stack width
stackPercentValue = groupManagerObj[groupManager].getStackSumPercent();
len = stackPercentValue.length;
catLen = xAxis[0].getCategoryLen();
if (catLen > len) {
categories.splice(len, catLen - len);
}
iapi._setCategories();
xLimit = xAxis[0].getLimit();
xMin = xLimit.min;
xMax = xLimit.max;
lastXPos = xMin;
xRange = xMax - xMin;
for (i = 0; i < stackPercentValue.length; i++) {
value = stackPercentValue[i];
stackValWidth = xRange * value/100;
catXVal = lastXPos + (stackValWidth / 2);
xAxis[0].updateCategory(i, {x: catXVal});
lastXPos += stackValWidth;
}
}
}, chartAPI.mscartesian, {
isstacked: true,
showpercentvalues: 0,
usepercentdistribution: 1,
showsum: 1,
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-msstackedcolumn2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI,
pluck = lib.pluck,
componentDispose = lib.componentDispose;
chartAPI ('msstackedcolumn2d', {
standaloneInit: true,
defaultDatasetType: 'column',
applicableDSList: { 'column': true },
friendlyName: 'Multi-series Stacked Column Chart',
_createDatasets: function () {
var iapi = this,
components = iapi.components,
dataObj = iapi.jsonData,
dataset = dataObj.dataset,
length = (dataset && dataset.length) || 0,
i,
j,
datasetStore,
datasetObj,
defaultSeriesType = iapi.defaultDatasetType,
applicableDSList = iapi.applicableDSList,
GroupManager,
dsType,
DsClass,
DsGroupClass,
datasetJSON,
groupManagerName,
parentDataObj,
innerLength,
lineSets = dataObj.lineset || [],
lineSetJSON,
config = iapi.config,
dataSetMap = config.dataSetMap,
lineSetMap = config.lineSetMap,
dataSetMapLen = dataSetMap && dataSetMap.length,
legend = components.legend,
tempDataStoreMap = [],
tempLineStoreMap = [],
datasetIndex = 0,
currDataLength,
prevDataLength,
prevLineSetLen,
prevData,
k = 0,
len,
inited,
positionIndex = -1,
dataSet,
currCatLen,
catLen = iapi.config.catLen,
xAxis = components.xAxis[0],
dataDiff,
diff,
catDiff,
startIndex,
diffObj,
properDsStructure,
currentDataStore = components.dataset,
prevDSInnerLen;
if (!dataset && lineSets.length === 0) {
iapi.setChartMessage();
return;
}
iapi.config.categories = dataObj.categories && dataObj.categories[0].category;
datasetStore = components.dataset = [];
for (j=0; j<length; j++) {
parentDataObj = dataset[j];
positionIndex ++;
if (parentDataObj.dataset) {
properDsStructure = true;
innerLength = (parentDataObj.dataset && parentDataObj.dataset.length) || 0;
tempDataStoreMap[j] = [];
for (i=0; i<innerLength; i++) {
datasetJSON = parentDataObj.dataset[i];
dsType = pluck (datasetJSON.renderas, defaultSeriesType);
dsType = dsType && dsType.toLowerCase ();
if (!applicableDSList[dsType]) {
dsType = defaultSeriesType;
}
/// get the DsClass
DsClass = FusionCharts.get('component', ['dataset', dsType]);
if (DsClass) {
groupManagerName = 'datasetGroup_' + dsType;
// get the ds group class
DsGroupClass = FusionCharts.register('component', ['datasetGroup', dsType]);
GroupManager = components[groupManagerName];
if (DsGroupClass && !GroupManager) {
GroupManager = components[groupManagerName] = new DsGroupClass ();
GroupManager.chart = iapi;
GroupManager.init ();
}
// Resetting the groupManager object if it is an update.
else if (GroupManager && (dataSetMap && dataSetMap.length !== 0) && !inited) {
GroupManager.init ();
inited = true;
}
// If the dataset does not exists.
if (!(dataSetMap && dataSetMap[j] && dataSetMap[j][i])) {
// create the dataset Object
datasetObj = new DsClass ();
datasetObj.chart = iapi;
datasetObj.index = datasetIndex;
datasetObj.init (datasetJSON);
}
else {
datasetObj = dataSetMap[j][i];
datasetObj.index = datasetIndex;
prevData = datasetObj.JSONData;
prevDataLength = (prevData.data && prevData.data.length) || 0;
currDataLength = (datasetJSON.data && datasetJSON.data.length) || 0;
currCatLen = xAxis.getCategoryLen();
catDiff = catLen - currCatLen;
dataDiff = prevDataLength - currDataLength;
diffObj = iapi._getDiff(dataDiff, currDataLength, catDiff, currCatLen);
diff = diffObj.diff;
startIndex = diffObj.startIndex;
// Removing data plots if the number of current data plots is more than the existing
//ones based of category difference and data difference.
if (diff > 0) {
datasetObj.removeData(startIndex, diff, false);
}
datasetObj.JSONData = datasetJSON;
datasetObj.configure();
}
datasetIndex ++;
tempDataStoreMap[j].push(datasetObj);
datasetStore.push(datasetObj);
// add to group manager
GroupManager && GroupManager.addDataSet (datasetObj, positionIndex, i);
}
}
}
else {
// Resetting inner Length and i for dispose of any previous dataset
innerLength = i = 0;
positionIndex --;
}
prevDSInnerLen = dataSetMap && dataSetMap[j] && dataSetMap[j].length;
// When the number of inner datasets entered vis setChartData is less than the existing inner
// dataset then unadd the extra datasets and dispose them.
if (prevDSInnerLen > innerLength) {
for (k = i, len = prevDSInnerLen - innerLength + i; k < len; k ++ ) {
dataSet = dataSetMap[j][k];
legend.removeItem(dataSet.legendItemId);
componentDispose.call(dataSet);
}
}
}
if (dataSetMapLen > length) {
for (k = j, len = dataSetMapLen - length + j; k < len; k ++ ) {
innerLength = dataSetMap[k].length;
for (i = 0; i < innerLength; i ++) {
dataSet = dataSetMap[k][i];
legend.removeItem(dataSet.legendItemId);
componentDispose.call(dataSet);
}
}
}
config.dataSetMap = tempDataStoreMap;
if (iapi.lineset) {
// For setting the lineset data if any
for (j = 0, length = lineSets.length; j < length; j++) {
lineSetJSON = lineSets[j];
DsClass = FusionCharts.get('component', ['dataset', 'line']);
datasetObj = new DsClass ();
if (!(lineSetMap && lineSetMap[j])) {
datasetObj.chart = iapi;
datasetObj.index = datasetIndex;
datasetObj.init (lineSetJSON);
}
else {
datasetObj = lineSetMap[j];
datasetObj.index = datasetIndex;
prevData = datasetObj.JSONData;
prevDataLength = prevData.data.length;
currDataLength = (lineSetJSON.data && lineSetJSON.data.length) || 0;
// Removing data plots if the number of current data plots is more than
// the existing ones.
if (prevDataLength > currDataLength) {
datasetObj.removeData(currDataLength,
prevDataLength - currDataLength, false);
}
datasetObj.JSONData = lineSetJSON;
datasetObj.configure();
}
tempLineStoreMap.push (datasetObj);
datasetStore.push(datasetObj);
datasetIndex++;
}
prevLineSetLen = lineSetMap && lineSetMap.length;
if (prevLineSetLen > length) {
for (k = j, len = prevLineSetLen - length + j; k < len; k ++ ) {
dataSet = lineSetMap[k];
legend.removeItem(dataSet.legendItemId);
componentDispose.call(dataSet);
}
}
config.lineSetMap = tempLineStoreMap;
}
// When the data entered is of column2d or mscolumn2d
if (!properDsStructure) {
components.dataset = currentDataStore;
iapi.setChartMessage();
return;
}
iapi.config.catLen = xAxis.getCategoryLen();
},
creditLabel: creditLabel
}, chartAPI.mscartesian, {
isstacked: true,
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-mscombi2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
UNDEFINED,
//add the tools thats are requared
pluck = lib.pluck,
componentDispose = lib.componentDispose;
chartAPI ('mscombi2d', {
friendlyName: 'Multi-series Combination Chart',
standaloneInit: true,
creditLabel: creditLabel,
defaultDatasetType: 'column',
applicableDSList: { 'line': true, 'area': true, 'column': true },
_createDatasets : function () {
var iapi = this,
components = iapi.components,
dataObj = iapi.jsonData,
dataset = dataObj.dataset,
length = dataset && dataset.length,
i,
j,
datasetStore,
datasetObj,
defaultSeriesType = iapi.defaultDatasetType,
applicableDSList = iapi.applicableDSList,
legend = iapi.components.legend,
xAxis = components.xAxis[0],
GroupManager,
dsType,
DsClass,
DsGroupClass,
datasetJSON,
isStacked = iapi.config.isstacked,
groupManagerName,
parentyaxis,
prevDataLength,
currDataLength,
groupManagers = [],
dsCount = { },
config = iapi.config,
groupManager,
count,
diff,
catLen = iapi.config.catLen,
currCatLen,
dataDiff,
catDiff,
prevData,
diffObj,
startIndex,
// map,line,area,column, stores the index of the various dataplots in combinational charts.
datasetMap = config.datasetMap || (config.datasetMap = {
line : [],
area : [],
column : [],
column3d: [],
scrollcolumn2d: []
}),
dsTypeRef,
tempMap = {
line : [],
area : [],
column : [],
column3d: [],
scrollcolumn2d: []
};
if (!dataset) {
iapi.setChartMessage();
}
iapi.config.categories = dataObj.categories && dataObj.categories[0].category;
datasetStore = components.dataset = [];
legend && legend.emptyItems();
for (i=0; i<length; i++) {
datasetJSON = dataset[i];
parentyaxis = datasetJSON.parentyaxis || '';
if (iapi.config.isdual && parentyaxis.toLowerCase () === 's') {
if (iapi.defaultSecondaryDataset === 'line') {
dsType = iapi.sDefaultDatasetType;
}
else {
dsType = pluck (datasetJSON.renderas, iapi.sDefaultDatasetType);
}
}
else {
dsType = pluck (datasetJSON.renderas, defaultSeriesType);
}
dsType = dsType && dsType.toLowerCase ();
if (!applicableDSList[dsType]) {
dsType = defaultSeriesType;
}
/// get the DsClass
DsClass = FusionCharts.get('component', ['dataset', dsType]);
if (DsClass) {
if (dsCount[dsType] === UNDEFINED) {
dsCount[dsType] = 0;
}
else {
dsCount[dsType]++;
}
groupManagerName = 'datasetGroup_' + dsType;
// get the ds group class
DsGroupClass = FusionCharts.register('component', ['datasetGroup', dsType]);
GroupManager = components[groupManagerName];
GroupManager && groupManagers.push(GroupManager);
if (DsGroupClass && !GroupManager) {
GroupManager = components[groupManagerName] = new DsGroupClass ();
GroupManager.chart = iapi;
GroupManager.init ();
}
dsTypeRef = datasetMap[dsType];
datasetObj = dsTypeRef[0];
// If the dataset does not exists.
if (!datasetObj) {
// create the dataset Object
datasetObj = new DsClass ();
datasetObj.chart = iapi;
datasetObj.index = i;
// add to group manager
GroupManager && (isStacked ? GroupManager.addDataSet (datasetObj, 0, dsCount[dsType]) :
GroupManager.addDataSet (datasetObj, dsCount[dsType], 0));
datasetObj.init (datasetJSON);
}
// If the dataset exists incase the chart is updated using setChartData() method.
else {
delete datasetObj.legendItemId;
currCatLen = xAxis.getCategoryLen();
catDiff = catLen - currCatLen;
prevData = datasetObj.JSONData;
prevDataLength = prevData.data && prevData.data.length;
currDataLength = (datasetJSON.data && datasetJSON.data.length) || 0;
dataDiff = prevDataLength - currDataLength;
diffObj = iapi._getDiff(dataDiff, currDataLength, catDiff, currCatLen);
diff = diffObj.diff;
startIndex = diffObj.startIndex;
// Removing data plots if the number of current data plots/categories
// is more than the existing ones.
if (diff > 0) {
datasetObj.removeData(startIndex, diff, false);
}
datasetObj.index = i;
datasetObj.JSONData = datasetJSON;
datasetObj.configure();
dsTypeRef.splice(0, 1);
}
// Push new dataset object into both tempmap array and datastore array
tempMap[dsType].push(datasetObj);
datasetStore.push (datasetObj);
}
}
iapi._setDatasetOrder();
// Removing unused datasets if any
for (dataset in datasetMap) {
dsTypeRef = datasetMap[dataset];
groupManager = dsTypeRef[0] && dsTypeRef[0].groupManager;
length = dsTypeRef.length;
count = dsCount[dataset] === UNDEFINED ? 0 : dsCount[dataset] + 1;
if (length) {
isStacked && groupManager && groupManager.removeDataSet(0, count, length);
for (j = 0; j < length; j++) {
groupManager && !isStacked && groupManager.removeDataSet(count, 0, 1);
if (dsTypeRef[j].type === 'column' && iapi.is3D === true) {
dsTypeRef[j].visible = false;
dsTypeRef[j].draw();
}
else {
componentDispose.call(dsTypeRef[j]);
}
}
}
}
config.datasetMap = tempMap;
iapi.config.catLen = xAxis.getCategoryLen();
}
}, chartAPI.areabase, {
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-mscombi3d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('mscombi3d', {
standaloneInit: true,
friendlyName: 'Multi-series 3D Combination Chart',
defaultDatasetType: 'column3d',
is3D: true,
creditLabel: creditLabel,
defaultPlotShadow: 1,
applicableDSList: { 'column3d': true, 'line': true, 'area': true },
_createDatasets : chartAPI.mscombi2d
}, chartAPI.mscartesian3d, {
showplotborder: 0,
enablemousetracking: true
}, chartAPI.areabase);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-mscolumnline3d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('mscolumnline3d', {
friendlyName: 'Multi-series Column and Line Chart',
is3D: true,
creditLabel: creditLabel,
defaultPlotShadow: 1,
applicableDSList: { 'column3d': true, 'line': true }
}, chartAPI.mscombi3d, {
use3dlineshift: 1,
showplotborder: 0,
enablemousetracking: true
}, chartAPI.msarea);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-stackedcolumn2dline', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname);
chartAPI ('stackedcolumn2dline', {
friendlyName: 'Stacked Column and Line Chart',
defaultDatasetType: 'column',
creditLabel: creditLabel,
applicableDSList: { 'line': true, 'column': true }
}, chartAPI.mscombi2d, {
isstacked: true,
stack100percent: 0,
enablemousetracking: true
}, chartAPI.msarea);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-stackedcolumn3dline', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('stackedcolumn3dline', {
friendlyName: 'Stacked 3D Column and Line Chart',
is3D: true,
creditLabel: creditLabel,
_mouseEvtHandler: chartAPI.stackedcolumn3d._mouseEvtHandler,
applicableDSList: { 'column3d' :true, 'line': true }
}, chartAPI.mscombi3d, {
use3dlineshift: 1,
isstacked: true,
stack100percent: 0,
showplotborder: 0,
enablemousetracking: true
}, chartAPI.msarea);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-mscombidy2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('mscombidy2d', {
standaloneInit: true,
friendlyName: 'Multi-series Dual Y-Axis Combination Chart',
defaultDatasetType: 'column',
sDefaultDatasetType: 'line',
_createDatasets: chartAPI.mscombi2d,
creditLabel: creditLabel,
applicableDSList: { 'column': true, 'line': true, 'area': true }
}, chartAPI.msdybasecartesian, {
isdual: 1,
enablemousetracking: true
}, chartAPI.msarea);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-mscolumn3dlinedy', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('mscolumn3dlinedy', {
standaloneInit: true,
friendlyName: 'Multi-series 3D Column and Line Chart',
defaultDatasetType: 'column3d',
sDefaultDatasetType: 'line',
is3D: true,
creditLabel: creditLabel,
_createDatasets: chartAPI.mscombi2d,
defaultPlotShadow: 1,
applicableDSList: { 'column3d' :true, 'line': true }
}, chartAPI.msdybasecartesian3d, {
use3dlineshift: 1,
isdual: true,
showplotborder: 0,
enablemousetracking: true
}, chartAPI.msarea);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-stackedcolumn3dlinedy', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname);
chartAPI ('stackedcolumn3dlinedy', {
standaloneInit: true,
friendlyName: 'Stacked 3D Column and Line Chart',
is3D: true,
defaultDatasetType: 'column3d',
creditLabel: creditLabel,
sDefaultDatasetType: 'line',
defaultSecondaryDataset: 'line',
_createDatasets: chartAPI.mscombi2d,
_mouseEvtHandler: chartAPI.stackedcolumn3d._mouseEvtHandler,
applicableDSList: { 'column3d': true, 'line': true }
}, chartAPI.msdybasecartesian3d, {
use3dlineshift: 1,
isdual: true,
isstacked: true,
showplotborder: 0,
enablemousetracking: true
}, chartAPI.msarea);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-msstackedcolumn2dlinedy', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('msstackedcolumn2dlinedy', {
standaloneInit: true,
friendlyName: 'Multi-series Dual Y-Axis Stacked Column and Line Chart',
stack100percent: 0,
defaultDatasetType: 'column',
sDefaultDatasetType: 'line',
hasLineSet: true,
creditLabel: creditLabel,
applicableDSList: { 'column': true },
lineset : true,
_createDatasets: chartAPI.msstackedcolumn2d
}, chartAPI.msdybasecartesian, {
isdual: true,
haslineset: true,
isstacked: true,
enablemousetracking: true
}, chartAPI.msarea);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-scrollcolumn2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
math = Math,
mathMin = math.min,
mathMax = math.max,
mathFloor = math.floor,
mathRound = math.round,
pluckNumber = lib.pluckNumber,
UNDEFINED,
R = lib.Raphael;
chartAPI ('scrollcolumn2d', {
standaloneInit: true,
friendlyName: 'Scrollable Multi-series Column Chart',
tooltipConstraint: 'plot',
canvasborderthickness: 1,
creditLabel: creditLabel,
defaultDatasetType : 'scrollcolumn2d',
applicableDSList: { 'scrollcolumn2d': true },
avgScrollPointWidth: 40,
hasScroll: true,
defaultPlotShadow: 1,
binSize : 0,
//function to handle swipe gesture
_swipeX: function (e, chartX) {
var iapi = this,
chartConfig = iapi.config,
components = iapi.components,
xAxis = components.xAxis[0],
axisConfig = xAxis.config,
canvasConfig = components.canvas.config,
canvasBorderWidth = canvasConfig.canvasBorderWidth,
scrollBar = components.scrollBar,
scrollConf = scrollBar.conf,
scrollRatio = scrollConf.scrollRatio,
w = scrollConf.width - 1,
h = scrollConf.height - 1,
initialScrollPosition = chartConfig.scrollToEnd,
lastScrollPosition = chartConfig.lastScrollPosition,
canvasLeft = chartConfig.canvasLeft,
aXisLineStartExtension = pluckNumber(canvasBorderWidth, axisConfig.lineStartExtension),
prevPos = iapi.touchCurrPos,
swipeStart = iapi.swipeStart,
type = e.type,
x = canvasLeft - aXisLineStartExtension,
button = (chartConfig.scrollShowButtons ? mathMin(h, w * 0.5) : 0),
mousedown = iapi.config.mousedown,
track,
trackLength,
trackOffset,
anchorOffset,
pos,
position;
lastScrollPosition = lastScrollPosition !== undefined ? lastScrollPosition : initialScrollPosition;
switch (type) {
case 'touchstart':
case 'mousedown':
iapi.config.mousedown = true;
iapi.prevScrollPosition = lastScrollPosition;
iapi.touchCurrPos = chartX;
break;
case 'touchmove':
case 'mousemove':
if (mousedown) {
if(!swipeStart) {
global.raiseEvent('scrollStart', {
scrollPosition: lastScrollPosition
}, iapi.chartInstance);
}
// lib.toolTip && lib.toolTip.hide();
track = w * scrollRatio - (button * 2 * scrollRatio);
trackLength = w - 2 * button - track;
trackOffset = x + button + 0.5;
anchorOffset = trackOffset + (track - 1) * 0.5;
pos =lastScrollPosition + ((prevPos - chartX) / trackLength);
position = mathMin(mathMax(pos, 0),1);
iapi.updateManager(position);
// global.raiseEvent('onScroll', {
// scrollPosition: position
// }, iapi.chartInstance);
iapi.swipeStart = true;
iapi.touchCurrPos = chartX;
if(e.originalEvent.preventDefault){
e.originalEvent.preventDefault();
} else {
e.originalEvent.returnValue = false;
}
return true;
}
break;
case 'touchend':
case 'mouseup':
if(mousedown) {
if(iapi.swipeStart) {
global.raiseEvent('scrollEnd', {
prevScrollPosition: iapi.prevScrollPosition,
scrollPosition: chartConfig.lastScrollPosition
}, iapi.chartInstance);
iapi.swipeStart = false;
}
chartConfig.mousedown = false;
}
}
},
eiMethods: {
scrollTo: chartAPI.mscartesian._scrollTo
},
_manageScrollerPosition: function () {
var iapi = this,
config = iapi.config,
chartComponents = iapi.components,
_scrollBar = iapi._scrollBar,
availableHeight,
availableWidth,
scrollEnabled,
getScrollItems = _scrollBar.get,
scrollBar = chartComponents.scrollBar,
scrollItem,
scrollDimensions;
// Fetching the scroll Items.
scrollItem = getScrollItems()[0];
scrollBar.setConfiguaration(scrollItem.conf);
scrollEnabled = config.scrollEnabled;
//manage space for the scroll bar.
// fetch the availble space for the canvas area after other space management is over
availableHeight = config.canvasHeight;
availableWidth = config.canvasWidth;
scrollDimensions = scrollBar.getLogicalSpace();
//allocate space for toolBox and set the chart configurations.
// shift denotes the amount of shift required by the x-axis
iapi._allocateSpace ({
bottom: (config.shift = scrollEnabled === false ? 0 :
(scrollDimensions.height + scrollBar.conf.padding))
});
},
_resetViewPortConfig: function () {
var iapi = this;
iapi.config.viewPortConfig = {
scaleX: 1,
scaleY: 1,
x: 0,
y: 0
};
},
updateManager: function (pos) {
var iapi = this,
config = iapi.config,
viewPortConfig = iapi.config.viewPortConfig,
scaleX = viewPortConfig.scaleX,
canvasWidth = config.canvasWidth,
datasetLayer = iapi.graphics.datasetGroup,
dataLabelsLayer = iapi.graphics.datalabelsGroup,
trackerGroup = iapi.graphics.trackerGroup,
offset = (canvasWidth * (scaleX - 1) * pos),
xAxis = iapi.components.xAxis[0],
xAxisAnifConfig,
xAxisNameDrawConfig,
sumLabelsLayer = iapi.graphics.sumLabelsLayer,
transformAttr;
viewPortConfig.x = offset / scaleX;
// Taking mathround of offset for crisp value
transformAttr = 't' + -mathRound(offset) + ',0';
config.lastScrollPosition = pos;
if (pos !== UNDEFINED){
iapi.components.scrollBar.node && iapi.components.scrollBar.node.attr({
'scroll-position': pos
});
}
datasetLayer.attr({
transform: transformAttr
});
dataLabelsLayer.attr({
transform: transformAttr
});
trackerGroup.attr({
transform: transformAttr
});
sumLabelsLayer && sumLabelsLayer.attr({
transform: transformAttr
});
xAxisAnifConfig = xAxis.getAxisConfig('animateAxis');
xAxisNameDrawConfig = xAxis.getAxisConfig('drawAxisName');
// temp fix for axis animation
xAxis.setAxisConfig({
animateAxis : false,
drawAxisName : false
});
xAxis.draw();
// reset the animation configuration
xAxis.setAxisConfig({
animateAxis : xAxisAnifConfig,
drawAxisName : xAxisNameDrawConfig
});
},
_createToolBox: function () {
var iapi = this,
components = iapi.components,
_scrollBar = iapi._scrollBar,
getScrollItems = _scrollBar.get,
addScrollItems = _scrollBar.add,
tb,
Scroller,
scrollItem,
ComponentGroup,
scrollBar = components.scrollBar,
toolBoxAPI;
// create the export or print buttons if required.
chartAPI.mscartesian._createToolBox.call(iapi);
tb = components.tb;
toolBoxAPI = components.toolBoxAPI || tb.getAPIInstances(tb.ALIGNMENT_HORIZONTAL);
Scroller = toolBoxAPI.Scroller;
ComponentGroup = toolBoxAPI.ComponentGroup;
// temp code: On update scroll items needs to be reused.
_scrollBar.clear();
// Adding the scroll items in the scroll bar.
addScrollItems({
isHorizontal: true
}, {
// Attach the callback for scroll Interaction.
'scroll' : (function (ref) {
return function () {
var pos = arguments && arguments[0],
components = ref.components,
scrollBar = components.scrollBar,
scrollConf = scrollBar.conf,
isScrollTo = scrollConf.scrollTo;
ref.config.scrollCurrPosition = pos;
if (!isScrollTo){
global.raiseEvent('onScroll', {
scrollPosition: pos
}, ref.chartInstance);
}
scrollConf.scrollTo = false;
ref.updateManager.apply(ref, arguments);
};
})(iapi)
});
// Fetching the scroll Items.
scrollItem = getScrollItems()[0];
if (!scrollBar) {
// adding the scrollbar.
components.scrollBar = new Scroller(scrollItem.conf, tb.idCount, tb.pId)
.attachEventHandlers(scrollItem.handler);
}
},
_setAxisScale: function () {
var iapi = this,
components = iapi.components,
config = iapi.config,
xAxis = components.xAxis[0],
catCount = xAxis.getCategoryLen(),
jsonData = iapi.jsonData,
scrollOptions = config.scrollOptions || (config.scrollOptions = {}),
datasets = iapi.components.dataset,
outerDataSetLen = datasets.length,
chartAttr = jsonData.chart,
chartWidth = config.width,
scaleX,
dataset,
i,
numOfColumnSeries = 0,
offset,
noOfDatasets,
canvasWidth = config.canvasWidth,
totalNumPoint,
scrollToEnd = config.scrollToEnd,
lastScrollPosition = config.lastScrollPosition,
pos,
//number of columns to be drawn.
numVisiblePlot = pluckNumber (chartAttr.numvisibleplot,
mathFloor (chartWidth / iapi.avgScrollPointWidth));
for (i = 0; i < outerDataSetLen; i++) {
dataset = datasets[i];
if (dataset.type === 'column') {
numOfColumnSeries++;
}
}
noOfDatasets = numOfColumnSeries;
if (iapi.config.isstacked) {
numOfColumnSeries = 1;
}
totalNumPoint = catCount * (numOfColumnSeries || 1);
if (numVisiblePlot >= 2 && numVisiblePlot < totalNumPoint) {
//update the scaleX for the axis.
config.viewPortConfig.scaleX = scaleX = totalNumPoint / numVisiblePlot;
if (lastScrollPosition !== undefined) {
pos = lastScrollPosition;
}
else {
pos = scrollToEnd ? 1 : 0;
}
offset = (canvasWidth * (scaleX - 1) * pos);
config.viewPortConfig.x = offset / scaleX;
//parse the scroll properties
scrollOptions.vxLength = numVisiblePlot / outerDataSetLen;
config.scrollEnabled = true;
}
else {
config.scrollEnabled = false;
}
},
drawScrollBar: function () {
var iapi = this,
config = iapi.config,
viewPortConfig = config.viewPortConfig,
components = iapi.components,
graphics = iapi.graphics,
paper = components.paper,
xAxis = components.xAxis[0],
axisConfig = xAxis.config,
axisRange = xAxis.config.axisRange,
scrollOptions = config.scrollOptions || (config.scrollOptions = {}),
max = axisRange.max,
min = axisRange.min,
vxLength = scrollOptions.vxLength,
scrollBar = components.scrollBar,
scrollNode = scrollBar.node,
scrollToEnd = config.scrollToEnd,
lastScrollPosition = config.lastScrollPosition,
scaleX = viewPortConfig.scaleX,
canvasLeft,
canvasTop,
canvasHeight,
canvasWidth,
canvasConfig,
canvasBorderWidth,
aXisLineWidth,
startPercent,
aXisLineStartExtension,
aXisLineEndExtension,
scrollRatio,
windowedCanvasWidth,
fullCanvasWidth,
scrollBarParentGroup;
if (lastScrollPosition !== undefined) {
startPercent = lastScrollPosition;
}
else {
startPercent = scrollToEnd ? 1 : 0;
}
canvasLeft = config.canvasLeft;
canvasTop = config.canvasTop;
canvasHeight = config.canvasHeight;
canvasWidth = config.canvasWidth;
canvasConfig = components.canvas.config;
canvasBorderWidth = canvasConfig.canvasBorderWidth;
aXisLineWidth = axisConfig.showAxisLine ? axisConfig.axisLineThickness || 0 : 0;
aXisLineStartExtension = pluckNumber(canvasBorderWidth, axisConfig.lineStartExtension);
aXisLineEndExtension = pluckNumber(canvasBorderWidth, axisConfig.lineEndExtension);
scrollOptions.viewPortMin = min;
scrollOptions.viewPortMax = max;
scrollRatio = (scrollOptions.scrollRatio = 1 / scaleX);
windowedCanvasWidth = scrollOptions.windowedCanvasWidth = xAxis.getAxisPosition(vxLength);
fullCanvasWidth = scrollOptions.fullCanvasWidth =
xAxis.getAxisPosition(max - min) - windowedCanvasWidth;
scrollBarParentGroup = graphics.scrollBarParentGroup;
if (!scrollBarParentGroup) {
scrollBarParentGroup = graphics.scrollBarParentGroup = paper.group('scrollBarParentGroup',
graphics.parentGroup).insertBefore(graphics.datalabelsGroup);
}
//draw the scroller element
// todo padding needs to be included.
if (config.scrollEnabled !== false) {
scrollBar.draw (canvasLeft - aXisLineStartExtension,
canvasTop + canvasHeight + canvasBorderWidth + aXisLineWidth - 2,{
width: canvasWidth + aXisLineStartExtension + aXisLineEndExtension,
scrollRatio: scrollRatio,
roundEdges: canvasConfig.isRoundEdges,
fullCanvasWidth: fullCanvasWidth,
windowedCanvasWidth: windowedCanvasWidth,
scrollPosition: startPercent,
parentLayer: scrollBarParentGroup
});
// attach the callback for raising event only for it is a new scroll node.
!scrollNode && (function () {
var prevPos;
R.eve.on('raphael.scroll.start.' + scrollBar.node.id, function (pos) {
prevPos = pos;
global.raiseEvent('scrollstart', {
scrollPosition: pos
}, iapi.chartInstance);
});
R.eve.on('raphael.scroll.end.' + scrollBar.node.id, function (pos) {
global.raiseEvent('scrollend', {
prevScrollPosition: prevPos,
scrollPosition: pos
}, iapi.chartInstance);
});
}());
}
else {
scrollBar && scrollBar.node && scrollBar.node.hide();
}
},
_drawDataset: function () {
var iapi = this;
iapi._setClipping();
chartAPI.mscartesian._drawDataset.call(this);
},
_setClipping: function () {
var chart = this,
config = chart.config,
datasetLayer = chart.graphics.datasetGroup,
dataLabelsLayer = chart.graphics.datalabelsGroup,
trackerLayer = chart.graphics.trackerGroup,
viewPortConfig = config.viewPortConfig,
sumLabelsLayer = chart.graphics.sumLabelsLayer,
scaleX = viewPortConfig.scaleX,
animationObj = chart.get('config', 'animationObj'),
animation = animationObj.duration,
dummyAnimElem = animationObj.dummyObj,
dummyAnimObj = animationObj.animObj,
animType = animationObj.animType,
x = viewPortConfig.x,
chartHeight = config.height,
clipCanvas = chart.components.canvas.config.clip['clip-canvas'];
clipCanvas = (clipCanvas && clipCanvas.slice(0)) || [];
if (!chart.config.clipSet) {
datasetLayer.attr({
'clip-rect': clipCanvas
});
dataLabelsLayer.attr({
'clip-rect': clipCanvas
});
trackerLayer.attr({
'clip-rect': clipCanvas
});
clipCanvas[3] = chartHeight;
clipCanvas[1] = 0;
sumLabelsLayer && sumLabelsLayer.attr({
'clip-rect': clipCanvas
});
}
else {
datasetLayer.animateWith(dummyAnimElem, dummyAnimObj,{
'clip-rect': clipCanvas
}, animation, animType);
dataLabelsLayer.animateWith(dummyAnimElem, dummyAnimObj,{
'clip-rect': clipCanvas
}, animation, animType);
trackerLayer.attr({
'clip-rect': clipCanvas
});
clipCanvas[3] = chartHeight;
clipCanvas[1] = 0;
sumLabelsLayer && sumLabelsLayer.animateWith(dummyAnimElem, dummyAnimObj, {
'clip-rect': clipCanvas
}, animation, animType);
}
datasetLayer.attr({
transform: 'T' + - (x * scaleX) + ',0'
});
dataLabelsLayer.attr({
transform: 'T' + - (x * scaleX) + ',0'
});
trackerLayer.attr({
transform: 'T' + - (x * scaleX) + ',0'
});
sumLabelsLayer && sumLabelsLayer.attr({
transform: 'T' + - (x * scaleX) + ',0'
});
chart.config.clipSet = true;
},
configure: function () {
var chart = this,
jsonData = chart.jsonData,
chartAttr = jsonData.chart,
chartConfig;
chartAPI.mscolumn2d.configure.call(this);
chartConfig = chart.config;
chartConfig.scrollToEnd = pluckNumber(chartAttr.scrolltoend, 0);
chartConfig.lastScrollPosition = UNDEFINED;
}
}, chartAPI.scrollbase);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-scrollarea2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI,
pluckNumber = lib.pluckNumber,
math = Math,
mathFloor = math.floor;
chartAPI ('scrollarea2d', {
friendlyName: 'Scrollable Multi-series Area Chart',
tooltipConstraint: 'plot',
canvasborderthickness: 1,
creditLabel: creditLabel,
hasScroll: true,
defaultDatasetType: 'scrollarea2d',
applicableDSList: { 'scrollarea2d': true },
avgScrollPointWidth: 75,
defaultPlotShadow: 0,
binSize : 0,
_setAxisScale: function () {
var iapi = this,
components = iapi.components,
config = iapi.config,
xAxis = components.xAxis[0],
catCount = xAxis.getCategoryLen(),
jsonData = iapi.jsonData,
scrollOptions = config.scrollOptions || (config.scrollOptions = {}),
chartAttr = jsonData.chart,
chartWidth = config.width,
scaleX,
totalNumPoint = catCount,
offset,
lastScrollPosition = config.lastScrollPosition,
pos,
scrollToEnd = config.scrollToEnd,
canvasWidth = config.canvasWidth,
//number of columns to be drawn.
numVisiblePlot = pluckNumber (chartAttr.numvisibleplot,
mathFloor (chartWidth / iapi.avgScrollPointWidth));
if (numVisiblePlot >= 2 && numVisiblePlot < totalNumPoint) {
//update the scaleX for the axis.
config.viewPortConfig.scaleX = scaleX = totalNumPoint / numVisiblePlot;
if (lastScrollPosition !== undefined) {
pos = lastScrollPosition;
}
else {
pos = scrollToEnd ? 1 : 0;
}
offset = (canvasWidth * (scaleX - 1) * pos);
config.viewPortConfig.x = offset / scaleX;
//parse the scroll properties
scrollOptions.vxLength = numVisiblePlot;
config.scrollEnabled = true;
}
else {
config.scrollEnabled = false;
}
}
}, chartAPI.scrollcolumn2d, {
enablemousetracking: true
}, chartAPI.areabase);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-scrollline2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname);
chartAPI ('scrollline2d', {
friendlyName: 'Scrollable Multi-series Line Chart',
tooltipConstraint: 'plot',
canvasborderthickness: 1,
defaultDatasetType: 'line',
creditLabel: creditLabel,
avgScrollPointWidth: 75,
defaultPlotShadow: 1,
binSize : 0
}, chartAPI.scrollarea2d, {
zeroplanethickness: 1,
zeroplanealpha: 40,
showzeroplaneontop: 0,
enablemousetracking: true
}, chartAPI.areabase);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-scrollstackedcolumn2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('scrollstackedcolumn2d', {
friendlyName: 'Scrollable Stacked Column Chart',
canvasborderthickness: 1,
tooltipConstraint: 'plot',
avgScrollPointWidth: 75,
creditLabel: creditLabel
}, chartAPI.scrollcolumn2d, {}, chartAPI.stackedcolumn2d);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-scrollcombi2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('scrollcombi2d', {
friendlyName: 'Scrollable Combination Chart',
tooltipConstraint: 'plot',
hasScroll: true,
canvasborderthickness: 1,
avgScrollPointWidth: 40,
applicableDSList: {'area': true, 'line': true, 'column': true},
creditLabel: creditLabel,
_createDatasets : chartAPI.mscombi2d
}, chartAPI.scrollcolumn2d, {}, chartAPI.msarea);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-scrollcombidy2d', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI;
chartAPI ('scrollcombidy2d', {
friendlyName: 'Scrollable Dual Y-Axis Combination Chart',
tooltipConstraint: 'plot',
canvasborderthickness: 1,
avgScrollPointWidth: 40,
hasScroll: true,
_swipeX: chartAPI.scrollcolumn2d,
_drawDataset: chartAPI.scrollcolumn2d,
updateManager: chartAPI.scrollcolumn2d,
_setAxisScale: chartAPI.scrollcolumn2d,
_createToolBox: chartAPI.scrollcolumn2d,
_scrollBar: chartAPI.scrollcolumn2d,
_manageScrollerPosition: chartAPI.scrollcolumn2d,
drawScrollBar: chartAPI.scrollcolumn2d,
_setClipping: chartAPI.scrollcolumn2d,
creditLabel: creditLabel,
configure: chartAPI.scrollcolumn2d,
eiMethods: {
scrollTo: chartAPI.mscartesian._scrollTo
}
}, chartAPI.mscombidy2d, {
enablemousetracking: true,
isdual: true
}, chartAPI.areabase);
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-scatter', function () {
var global = this,
lib = global.hcLib,
win = global.window,
chartAPI = lib.chartAPI,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname);
chartAPI ('scatter', {
friendlyName: 'Scatter Chart',
isXY: true,
standaloneInit: true,
hasLegend: true,
defaultZeroPlaneHighlighted: false,
creditLabel: creditLabel,
defaultDatasetType : 'Scatter',
applicableDSList: {
'Scatter': true
},
drawTracker: true
}, chartAPI.scatterBase, {
allowreversexaxis: true,
enablemousetracking: true
});
}]);
FusionCharts.register ('module', ['private', 'modules.renderer.js-bubble', function () {
var global = this,
lib = global.hcLib,
win = global.window,
creditLabel = false && !lib.CREDIT_REGEX.test (win.location.hostname),
chartAPI = lib.chartAPI,
math = Math,
mathMax = math.max,
mathMin = math.min;
chartAPI ('bubble', {
friendlyName: 'Bubble Chart',
standaloneInit: true,
defaultDatasetType: 'bubble',
creditLabel: creditLabel,
applicableDSList: { 'bubble': true },
getDataLimits: function () {
var iapi = this,
datasets = iapi.components.dataset,
i,
dataset,
len,
limits,
zMax = -Infinity,
zMin = +Infinity;
for (i = 0,len = datasets.length; i < len; i++) {
dataset = datasets[i];
limits = dataset.getDataLimits();
zMax = mathMax(zMax, limits.zMax || -Infinity);
zMin = mathMin(zMin, limits.zMin || +Infinity);
}
zMax = zMax === -Infinity ? 0 : zMax;
zMin = zMin === +Infinity ? 0 : zMin;
return {
zMax: zMax,
zMin: zMin
};
}
}, chartAPI.scatter, {
enablemousetracking: true
});
}]);
}, [3, 2, 2, 'sr4']]);
/* jshint ignore: end */
/**
* FusionCharts JavaScript Library
* ZoomLine visualization module extension comprising visualization logic as well as renderer adapter.
* @private
*
* @module fusioncharts.renderer.javascript.charts.zoomline
* @requires fusioncharts.renderer.javascript.charts.common
*/
FusionCharts.register ('module', ['private', 'modules.renderer.js-zoomline', function () {
/*jslint newcap: false */
var global = this,
lib = global.hcLib,
hashify = lib.hashify,
win = global.window,
doc = win.document,
Image = win.Image,
MouseEvent = win.MouseEvent,
userAgent = win.navigator.userAgent,
isIE = /msie/i.test (userAgent) && !win.opera,
chartAPI = lib.chartAPI,
extend2 = lib.extend2,
addEvent = lib.addEvent,
pluck = lib.pluck,
// BLANKSTRING = lib.BLANKSTRING,
// parseTooltext = lib.parseTooltext,
pluckNumber = lib.pluckNumber,
getFirstColor = lib.getFirstColor,
convertColor = lib.graphics.convertColor,
bindSelectionEvent = lib.bindSelectionEvent,
// parseTrendlines = lib.createTrendLine,
parseUnsafeString = lib.parseUnsafeString,
componentDispose = lib.componentDispose,
// regescape = lib.regescape,
R = lib.Raphael,
toRaphaelColor = lib.toRaphaelColor,
hasTouch = lib.hasTouch,
plotEventHandler = lib.plotEventHandler,
getMouseCoordinate = lib.getMouseCoordinate,
UNDEFINED,
creditLabel = false && !lib.CREDIT_REGEX.test(win.location.hostname),
// CONFIGKEY = lib.FC_CONFIG_STRING,
// G = 'g',
PIPE = '|',
// DOT = '.',
BLANK = '',
TOUCH_THRESHOLD_PIXELS = lib.TOUCH_THRESHOLD_PIXELS,
CLICK_THRESHOLD_PIXELS = lib.CLICK_THRESHOLD_PIXELS,
// hot/tracker threshold in pixels
HTP = hasTouch ? TOUCH_THRESHOLD_PIXELS : CLICK_THRESHOLD_PIXELS,
COLOR_WHITE = '#FFFFFF',
COLOR_E3E3E3 = '#E3E3E3',
COLOR_C2C2C2 = '#C2C2C2',
COLOR_EFEFEF = '#EFEFEF',
STR_DEF = 'default',
POINTER = 'pointer',
preDefStr = lib.preDefStr,
DEFAULT_CURSOR = preDefStr.DEFAULT,
DATAPLOTCLICK = 'dataplotclick',
//default cursor type.
// STR_DEF = 'default',
PX = 'px',
TRACKER_FILL = 'rgba(192,192,192,' + (isIE ? 0.002 : 0.000001) + ')', // invisible but clickable
UNDEF,
schedular = lib.schedular,
priorityList = lib.priorityList,
math = Math,
mathCeil = math.ceil,
mathFloor = math.floor,
mathRound = math.round,
mathMax = math.max,
mathMin = math.min,
mathCos = math.cos,
mathSin = math.sin,
// mathAbs = math.abs,
toFloat = win.parseFloat,
toInt = win.parseInt,
MAX_MITER_LINEJOIN = 2,
CrossLine; // function (constructor)
// Add events to the legacy event list
extend2 (lib.eventList, {
zoomed: 'FC_Zoomed',
pinned: 'FC_Pinned',
resetzoomchart: 'FC_ResetZoomChart'
});
chartAPI ('zoomline',{
standaloneInit: true,
canvasborderthickness: 1,
defaultDatasetType: 'zoomline',
applicableDSList: {
'zoomline': true
},
friendlyName: 'Zoomable and Panable Multi-series Line Chart',
creditLabel: creditLabel,
/*
* Only y-axes are being drawn.
* x-axis is drawn after setting the label step value.
*/
_drawAxis : function () {
var iapi = this,
components = iapi.components,
yAxisArr = components.yAxis || [],
i,
len;
for (i = 0, len = yAxisArr.length; i < len; i++) {
yAxisArr[i].draw ();
}
},
_setCategories: function () {
var iapi = this,
config = iapi.config,
components = iapi.components,
dataObj = iapi.jsonData,
xAxis = components.xAxis,
tempArr,
data,
j,
len,
cdm = config.cdm,
cdmchar = config.cdmchar,
category = (dataObj.categories && dataObj.categories[0].category) || [];
if (cdm || typeof category === 'string') {
if (category.split) {
tempArr = category.split(cdmchar);
data = [];
for (j = 0, len = tempArr.length; j < len; j += 1) {
data.push({
label : tempArr[j]
});
}
iapi.config.categories = dataObj.categories[0].category = data;
}
}
xAxis[0].setAxisPadding(0, 0);
xAxis[0].setCategory (data || category);
},
_createDatasets : function () {
var i,
j,
len,
data,
diff,
dsType,
DsClass,
tempArr,
datasetObj,
datasetJSON,
parentyaxis,
datasetStore,
prevDataLength,
datasetStoreLen,
currDataLength,
dsCount = {},
iapi = this,
config = iapi.config,
components = iapi.components,
dataObj = iapi.jsonData,
dataset = dataObj.dataset,
length = dataset && dataset.length,
cdmchar = config.cdmchar,
cdm = config.cdm,
defaultSeriesType = iapi.defaultDatasetType,
applicableDSList = iapi.applicableDSList,
legend = iapi.components.legend,
legendItems = legend.components.items || [],
category = dataObj.categories && dataObj.categories[0].category;
if (!dataset || !category) {
iapi.setChartMessage();
}
iapi.config.categories = category;
datasetStore = components.dataset || (components.dataset = []);
datasetStoreLen = datasetStore.length;
for (i=0; i<length; i++) {
datasetJSON = dataset[i];
// Check for compact data
if (cdm) {
if (datasetJSON.data && datasetJSON.data.split) {
tempArr = datasetJSON.data.split(cdmchar);
data = [];
for (j = 0, len = tempArr.length; j < len; j++) {
data.push({
value : tempArr[j]
});
}
datasetJSON.data = data;
}
}
parentyaxis = datasetJSON.parentyaxis || '';
if (iapi.isDual && parentyaxis.toLowerCase () === 's') {
dsType = pluck (datasetJSON.renderas, iapi.sDefaultDatasetType);
}
else {
dsType = pluck (datasetJSON.renderas, defaultSeriesType);
}
dsType = dsType && dsType.toLowerCase ();
if (!applicableDSList[dsType]) {
dsType = defaultSeriesType;
}
/// get the DsClass
DsClass = FusionCharts.get('component', ['dataset', dsType]);
if (DsClass) {
if (dsCount[dsType] === UNDEFINED) {
dsCount[dsType] = 0;
}
else {
dsCount[dsType]++;
}
// If the dataset does not exists.
if (!(datasetObj = datasetStore[i])) {
// create the dataset Object
datasetObj = new DsClass ();
datasetStore.push (datasetObj);
datasetObj.chart = iapi;
datasetObj.index = i;
datasetObj.init (datasetJSON);
}
// If the dataset exists incase the chart is updated using setChartData() method.
else {
prevDataLength = (datasetObj.JSONData.data || []).length;
currDataLength = (datasetJSON.data || []).length;
// Removing data plots if the number of current data plots is more than the existing ones.
if (prevDataLength > currDataLength) {
datasetObj.removeData(currDataLength, prevDataLength - currDataLength, false);
}
datasetStore[i].JSONData = datasetJSON;
datasetStore[i].configure();
datasetStore[i]._deleteGridImages && datasetStore[i]._deleteGridImages();
}
}
}
// When the number of datasets entered vis setChartData is less than the existing dataset then dispose the
// extra datasets.
if (datasetStoreLen > length) {
diff = datasetStoreLen - length;
for (j = i, length = diff + i; j < length; j ++ ) {
componentDispose.call(datasetStore[j]);
}
datasetStore.splice(i, diff);
legendItems.splice(i, diff);
}
},
isWithinCanvas : function (e, chart) {
var chartConfig = chart.get('config'),
mousePos = lib.getMouseCoordinate(chart.get('linkedItems', 'container'),e, chart),
/*converts the original mouse event toa Fusion Charts event( that has chartX, chartY, pageX and pageY as
its property)*/
chartX = mousePos.chartX,
chartY = mousePos.chartY,
minX = chartConfig.canvasLeft,
minY = chartConfig.canvasTop,
maxX = chartConfig.canvasLeft + chartConfig.canvasWidth,
maxY = chartConfig.canvasHeight + chartConfig.canvasTop;
//default value of the flag.
mousePos.insideCanvas = false;
// store the original event as well
mousePos.originalEvent = e;
//return true if within the canvas
if (chartX > minX && chartX < maxX && chartY > minY && chartY < maxY) {
//set the flag to be TRUE if triggered within the canvas area.
mousePos.insideCanvas = true;
}
return mousePos;
},
highlightPoint: function (showHover,cx,cy,point,index, toolText) {
var chart = this,
chartConfig = chart.config,
chartComponents = chart.components,
chartGraphics = chart.graphics,
paper = chartComponents.paper,
tracker = chartGraphics.tracker,
datasetObj = chartComponents.dataset[index],
datasetConfig = datasetObj && datasetObj.config,
radius = ( datasetObj && datasetConfig.zoomedRadius || 0),
hoverCosmetics = datasetObj && datasetConfig.hoverCosmetics,
fill = hoverCosmetics && hoverCosmetics.fill,
borderColor = hoverCosmetics && hoverCosmetics.borderColor,
borderThickness = hoverCosmetics && hoverCosmetics.borderThickness,
attrObj = {},
//attach the callbacks for the click and hover interactions for the hovering element.
plotEventHandlerCallback = {
'click': function (e){
lib.plotEventHandler.call(this, chart, e);
},
'hoverIn': function (e){
lib.plotEventHandler.call(this, chart, e, 'dataplotRollover');
},
'hoverOut': function (e) {
lib.plotEventHandler.call(this, chart, e, 'dataplotRollout');
}
};
if (!tracker) {
// in case the tracker element is not created. Attach the callbacks for click and hovering effects.
tracker = chartGraphics.tracker = paper.circle(0, 0, 0, chartGraphics.trackerGroup)
.attr({
'clip-rect': chartConfig.canvasLeft + ',' + (chartConfig.canvasTop) + ',' +
chartConfig.canvasWidth + ',' + chartConfig.canvasHeight
})
.click(plotEventHandlerCallback.click)
.trackTooltip(true)
.hover(plotEventHandlerCallback.hoverIn, plotEventHandlerCallback.hoverOut);
}
//Attach the required information for the hovering element.
point && tracker.data('eventArgs', {
x: point.x,
y: point.y,
tooltip: point.tooltip,
link: point.link
});
/* store the hovered point as last visible point. This is required to avoid redaundant calls if the same
point is hovered. */
chartConfig.lastHoveredPoint = point;
// if hover cosmetics then add that in the attrObj
if (Number(showHover)) {
attrObj = {
r: radius,
fill: fill,
stroke: borderColor,
'stroke-width': borderThickness
};
}
else { // make transparent configuration
attrObj = {
r: radius,
fill: TRACKER_FILL,
stroke: TRACKER_FILL,
'stroke-width': 0
};
}
tracker
.attr(attrObj)
.tooltip(toolText)
.transform('t' + (cx + chartConfig.canvasLeft) + ',' + (cy + chartConfig.canvasTop));
/*on first mouse move the element is created and on the next mouse move the tooltip is shown.
In order give the effect of displaying the tooltip once hovered, another mouseMove event is
fired forcefully */
point && chart.fireMouseEvent('mouseover', tracker && tracker.node, chartConfig.lastMouseEvent);
},
fireMouseEvent : function (eventName, domElement, mouseEventInit) {
var event;
if (!domElement || !eventName) {
return;
}
if(!mouseEventInit){
mouseEventInit = {};
}
if (mouseEventInit.originalEvent) {
mouseEventInit = mouseEventInit.originalEvent;
}
// map touch event for touch devices
if (mouseEventInit.touches) {
mouseEventInit = mouseEventInit.touches[0];
}
if (domElement.dispatchEvent) {
if (MouseEvent) {
//for FireFox, chrome and opera. NOT confirmed in Safari
// Creates a MouseEvent object.
event = new MouseEvent(eventName, {
bubbles: !!mouseEventInit.bubbles,
cancelable: !!mouseEventInit.cancelable,
clientX: mouseEventInit.clientX || ( mouseEventInit.pageX && (mouseEventInit.pageX -
doc.body.scrollLeft - doc.documentElement.scrollLeft)) || 0,
clientY: mouseEventInit.clientY || ( mouseEventInit.pageY && (mouseEventInit.pageY -
doc.body.scrollTop - doc.documentElement.scrollTop)) || 0,
screenX: mouseEventInit.screenX || 0,
screenY: mouseEventInit.screenY || 0,
pageX: mouseEventInit.pageX || 0,
pageY: mouseEventInit.pageY || 0
});
}
else if (doc.createEvent) {
//for IE support.
event = doc.createEvent('HTMLEvents');
event.initEvent(eventName, !!mouseEventInit.bubbles, !!mouseEventInit.cancelable);
}
event.eventName = eventName;
event && domElement.dispatchEvent(event);
}
else if(doc.createEventObject && domElement.fireEvent){
event = doc.createEventObject();
event.eventType = eventName;
event.eventName = eventName;
//trigger the event forcefully.
domElement.fireEvent('on' + eventName, event);
}
},
configure: function () {
var style,
iapi = this,
jsonData = iapi.jsonData,
chartDef = jsonData.chart || {},
colorManager = iapi.components.colorManager,
canvasBorderColor = colorManager.getColor('canvasBorderColor'),
config;
// todo code: Need to extend support for these attributes, temporarily hardcoded.
chartDef.animation = 0;
chartDef.showvalues = 0;
chartAPI.msline.configure.call(iapi);
// The base configure fn above creates a dummy chart object if there is no chart object in json data.
config = iapi.config;
style = config.style;
// overwrite the existing chart configurations.
// Copy and prepare some configurations for zoom charts
extend2(config, {
useRoundEdges: pluckNumber (chartDef.useroundedges,0),
animation: false,
zoomType: 'x',
canvasPadding: pluckNumber(chartDef.canvaspadding, 0),
scrollColor: getFirstColor(pluck(chartDef.scrollcolor,
colorManager.getColor('altHGridColor'))),
scrollShowButtons: !!pluckNumber(chartDef.scrollshowbuttons, 1),
scrollHeight: pluckNumber(chartDef.scrollheight, 16) || 16,
scrollBarFlat: pluckNumber (chartDef.flatscrollbars, 0),
allowPinMode: pluckNumber(chartDef.allowpinmode, 1),
skipOverlapPoints: pluckNumber(chartDef.skipoverlappoints, 1),
showToolBarButtonTooltext: pluckNumber(chartDef.showtoolbarbuttontooltext, 1),
btnResetChartTooltext: pluck(chartDef.btnresetcharttooltext, 'Reset Chart'),
btnZoomOutTooltext: pluck(chartDef.btnzoomouttooltext, 'Zoom out one level'),
btnSwitchToZoomModeTooltext: pluck(chartDef.btnswitchtozoommodetooltext,
'<strong>Switch to Zoom Mode</strong><br/>Select a subset of data to zoom ' +
'into it for detailed view'),
btnSwitchToPinModeTooltext: pluck(chartDef.btnswitchtopinmodetooltext,
'<strong>Switch to Pin Mode</strong><br/>Select a subset of data and compare ' +
'with the rest of the view'),
/**
* @note pinPaneStroke related attribute parsing is unused in
* present JS ZoomLine implementation.
pinPaneStrokeWidth: pluckNumber(chartDef.pinpaneborderthickness, 1),
pinPaneStroke: convertColor(pluck(chartDef.pinpanebordercolor,
canvasBorderColor), pluckNumber(chartDef.pinpaneborderalpha, 15)),
*/
pinPaneFill: convertColor(pluck(chartDef.pinpanebgcolor,
canvasBorderColor), pluckNumber(chartDef.pinpanebgalpha, 15)),
zoomPaneFill: convertColor(pluck(chartDef.zoompanebgcolor,
'#b9d5f1'), pluckNumber(chartDef.zoompanebgalpha, 30)),
zoomPaneStroke: convertColor(pluck(chartDef.zoompanebordercolor,
'#3399ff'), pluckNumber(chartDef.zoompaneborderalpha, 80)),
showPeakData: pluckNumber(chartDef.showpeakdata, 0),
maxPeakDataLimit: pluckNumber(chartDef.maxpeakdatalimit, chartDef.maxpeaklimit, null),
minPeakDataLimit: pluckNumber(chartDef.minpeakdatalimit, chartDef.minpeaklimit, null),
crossline: {
enabled: pluckNumber(chartDef.showcrossline, 1),
line: {
'stroke-width': pluckNumber(chartDef.crosslinethickness, 1),
'stroke': getFirstColor(pluck(chartDef.crosslinecolor, '#000000')),
'stroke-opacity': pluckNumber(chartDef.crosslinealpha, 20) / 100
},
labelEnabled: pluckNumber(chartDef.showcrosslinelabel,
chartDef.showcrossline, 1),
labelstyle: {
fontSize: toFloat(chartDef.crosslinelabelsize) ?
toFloat(chartDef.crosslinelabelsize) + PX : style.outCanfontSize,
fontFamily: pluck(chartDef.crosslinelabelfont,
style.outCanfontFamily)
},
valueEnabled: pluckNumber(chartDef.showcrosslinevalues,
chartDef.showcrossline, 1),
valuestyle: {
fontSize: toFloat(chartDef.crosslinevaluesize) ?
toFloat(chartDef.crosslinevaluesize) + PX : style.inCanfontSize,
fontFamily: pluck(chartDef.crosslinevaluefont,
style.inCanvasStyle.fontFamily)
}
},
useCrossline: pluckNumber(chartDef.usecrossline, 1),
tooltipSepChar: pluck(chartDef.tooltipsepchar, ', '),
showTerminalValidData: pluckNumber(chartDef.showterminalvaliddata, 0),
cdmchar: pluck(chartDef.dataseparator, PIPE),
cdm: pluckNumber(chartDef.compactdatamode, 0)
});
},
getValuePixel: function (px) {
var chart = this,
chartConfig = chart.config,
info = chartConfig.viewPortConfig;
return info.ddsi + mathFloor(px / info.ppp);
},
__toolbar: function () {
var toolBoxAPI,
SymbolStore,
ComponentGroup,
HorizontalToolbar,
chart = this,
chartComponents = chart.components,
toolBox = chartComponents.tb = new (FusionCharts.register('component', ['toolbox', 'toolbox']))(),
toolConf = toolBox.getDefaultConfiguration(),
Symbol,
Scroller;
toolBox.init({
iAPI: {
chart: chart
},
graphics: chart.graphics,
chart: chart,
components: chartComponents
});
toolBoxAPI = chartComponents.toolBoxAPI || toolBox.getAPIInstances(toolBox.ALIGNMENT_HORIZONTAL);
SymbolStore = toolBoxAPI.SymbolStore;
ComponentGroup = toolBoxAPI.ComponentGroup;
HorizontalToolbar = toolBoxAPI.Toolbar;
Symbol = toolBoxAPI.Symbol;
Scroller = toolBoxAPI.Scroller;
toolBox.graphics = {};
return {
reInit: function () {
toolBox.init({
iAPI: {
chart: chart
},
graphics: chart.graphics,
chart: chart,
components: chartComponents
});
},
addSymbol: function (symbolName, callBack, options, group) {
var symbol = new Symbol(symbolName);
options && group.setConfiguaration({
buttons : extend2(extend2({},toolConf),options)
});
callBack.tooltext = options.tooltip;
callBack && symbol.attachEventHandlers(callBack);
group.addSymbol(symbol);
return symbol;
},
addScroll: function (options, callBack) {
var scrl = new Scroller(options);
/*options && group.setConfiguaration({
buttons : extend2(extend2({},toolConf),options)
});
callBack.tooltext = options.tooltip;*/
callBack && scrl.attachEventHandlers(callBack);
return scrl;
},
addComponentGroup: function (groupName, options) {
var group;
group = new ComponentGroup();
group.setConfiguaration({
group : {
fill : options ? options.fill : convertColor('EBEBEB', 0),
borderThickness : options ? pluckNumber(options.borderThickness, 0) : 0
}
});
return group;
},
addToolBox: function (componentGroups) {
var i,
toolBox = new HorizontalToolbar();
for (i = 0; i < componentGroups.length; i += 1) {
toolBox.addComponent(componentGroups[i]);
}
return toolBox;
},
setDrawingArea: function (toolBox, drawingArea) {
toolBox.drawingArea = drawingArea;
return toolBox;
},
draw: function (toolBoxes) {
var i,
toolBox,
drawingArea;
for (i = 0; i < toolBoxes.length; i += 1) {
toolBox = toolBoxes[i];
drawingArea = toolBox.drawingArea;
toolBox.draw(drawingArea.x, drawingArea.y);
}
},
registerSymbol: function (symbolName, symbolPathFn) {
SymbolStore.register(symbolName, symbolPathFn);
},
getLogicalSpace: function (toolBox) {
return toolBox.getLogicalSpace();
},
getNode: function (buttonGroup) {
return buttonGroup.node;
}
};
},
__preDraw: function () {
var seriesItemConf,
ppp,
ppl,
visw,
visx,
yminValue,
ymin,
pinclip,
tAtt,
pingroup,
datalayer,
preZoomed,
scrollerLayer,
pinrect,
iapi = this,
iapiComponents = iapi.components,
paper = iapiComponents.paper,
iapiGraphics = iapi.graphics,
//todo: containerelement should be a part of graphics components.
// containerElem = iapiGraphics.container || iapi.container,
imageContainer = iapiGraphics.imageContainer,
iapiConfig = iapi.config,
canvasLeft = iapiConfig.canvasLeft,
canvasWidth = iapiConfig.canvasWidth,
chartDef = iapi.jsonData.chart,
cdm = iapiConfig.cdm,
xAxis = iapiComponents.xAxis[0],
// listeners = iapiComponents.eventListeners || (iapiComponents.eventListeners = []),
// containerElem = iapi.container,
info = iapiConfig.viewPortConfig,
canvasConfig = iapi.components.canvas.config,
canvasPadding = mathMax(canvasConfig.canvasPadding, canvasConfig.canvasPaddingLeft,
canvasConfig.canvasPaddingRight),
yAxis = iapiComponents.yAxis[0],
datasetGroup = iapiGraphics.datasetGroup,
canvasHeight = iapiConfig.canvasHeight,
canvasTop = iapiConfig.canvasTop,
chartAttrs = iapi.jsonData.chart,
borderWidth = iapiConfig.borderWidth || (iapiConfig.borderWidth = pluckNumber (chartAttrs.showborder,
1) ? pluckNumber (chartAttrs.borderthickness, 1) : 0),
allowpin = iapiConfig.allowPinMode,
crosslineOptions = iapiConfig.crossline,
clen = xAxis.getCategoryLen(),
// Validate and parse the data display indices and also calculate
// initial pixels-per-point.
dsi = toInt(pluckNumber(chartDef.displaystartindex, 1), 10) - 1,
dei = toInt(pluckNumber(chartDef.displayendindex, clen || 2), 10) - 1,
overFlowingMarkerWidth = 0,
dataSetArr = iapiComponents.dataset,
j = dataSetArr.length,
crossline = iapiGraphics.crossline;
iapiConfig.updateAnimDuration = 500; // default animation durations.
//the origin of the container element should coincide with the origin(top-left) of the canvas area.
imageContainer.transform('t' + canvasLeft + ',' + canvasTop);
//apply clipping to the container element.
imageContainer.attr({
'clip-rect': canvasLeft + ',' + canvasTop + ',' + canvasWidth + ',' + canvasHeight
});
iapiConfig.status = 'zoom';
//set the maximum scaleX and scaleY.
iapiConfig.maxZoomLimit = pluckNumber(chartDef.maxzoomlimit, 1000);
//stores the different visual configurations for a historical reference.
iapiConfig.viewPortHistory = [];
((ppp = pluckNumber(chartDef.pixelsperpoint, 15)) < 1) && (ppp = 1);
((ppl = pluckNumber(chartDef.pixelsperlabel, chartDef.xaxisminlabelwidth,
xAxis.getAxisConfig('labels').rotation ? 20 : 60)) < ppp) && (ppl = ppp);
// start index must be positive and less than end. last index must
// not be greater than category count.
(dsi < 0 || dsi >= ((clen - 1) || 1)) && (dsi = 0);
(dei <= dsi || dei > ((clen - 1) || 1)) && (dei = (clen - 1) || 1);
// Set initial zoom information
info = (iapiConfig.viewPortConfig = extend2(iapiConfig.viewPortConfig, {
// cnd: pluckNumber(chartDef.connectnulldata, 0), // connectNullData
amrd: pluckNumber(chartDef.anchorminrenderdistance, 20), // anchor render distance
nvl: pluckNumber(chartDef.numvisiblelabels, 0), // num visible labels
cdm: cdm, // compact data mode
oppp: ppp, // original pixels per point
oppl: ppl, // original pixels per label
dsi: dsi, // dislay start index
dei: dei, // display end index
vdl: dei - dsi, // visible display length
// dmax: legacyYAxisConf.max = dmax, // max value of all data
// dmin: legacyYAxisConf.min = dmin, // min value of all data
// dsecondarymax : secondaryYAxisConf.max = dsecondarymax,
// dsecondarymin : secondaryYAxisConf.min = dsecondarymin,
clen: clen, // category length and data length
// Internal variables required for zoom state.
offset: 0, // (internal) drawing offset for smooth scroll,
step: 1, // (internal) default stepping or skipping,
llen: 0, // (internal) number of labels
alen: 0, // (internal) length of anchors already drawn
ddsi: dsi, // (internal) dynamic display start as per scroll
ddei: dei, // (internal) dynamic display end as per scroll
ppc: 0 // (internal) pixels per category
}));
if (!info.clen) {
return;
}
// calculate the overflowing markers width
while (j--) {
seriesItemConf = dataSetArr[j].config;
overFlowingMarkerWidth = mathMax(overFlowingMarkerWidth, ((seriesItemConf.drawanchors &&
((seriesItemConf.anchorradius || 0) + (Number(seriesItemConf.anchorborderthickness) || 0))) || 0));
}
iapiConfig.overFlowingMarkerWidth = overFlowingMarkerWidth;
canvasPadding = (iapiConfig.canvasPadding = mathMax(overFlowingMarkerWidth, canvasPadding));
// Do a check whether user has initially zoomed. That would
// mean to keep zoomOut button visible and also to create a
// fake first zoom level.
preZoomed = iapiConfig._prezoomed = (info.dei - info.dsi < info.clen - 1);
// Set the visual dimensions of plot inside canvas.
visw = iapiConfig._visw = iapiConfig.canvasWidth - canvasPadding * 2;
visx = iapiConfig._visx = iapiConfig.canvasLeft + canvasPadding;
iapiConfig._visout = -(iapiConfig.height + canvasHeight + 1e3);
// Get the y-axis pixel value ratio, other y-axis related values and store for later use.
iapiConfig._ypvr = yAxis && yAxis.getPVR() || 0;
yminValue = iapiConfig._yminValue = yAxis.getLimit().min;
ymin = iapiConfig._ymin = yAxis.getPixel(yminValue);
// Clip the dataset layer to required dimension. This layer will be
// scrolled by the scroller
// Increase the clipping region to acomodate overflowing anchors
datalayer = datasetGroup.attr('clip-rect', [visx - overFlowingMarkerWidth,
canvasTop, visw + (overFlowingMarkerWidth * 2), canvasHeight]);
scrollerLayer = iapiGraphics.scroll || (iapiGraphics.scroll =
paper.group('scroll').insertAfter(iapiGraphics.datasetGroup));
// Create the group to store pinlines. We do it here as it is easier
// to transform that way.
if (allowpin) {
// Create the pin graphics. We create a background that sits atop the
// canvas and is clipped to match size later.
tAtt = R.crispBound(0, canvasTop - ymin, 0, canvasHeight, borderWidth);
/** @todo need to not do renderer based clipping box after raphael's
* clipping on transformed group is addressed.
*/
pinclip = iapiConfig['clip-pinrect'] =
[tAtt.x, canvasTop, tAtt.width, tAtt.height];
pingroup = (iapiGraphics.zoompin = paper.group('zoompin'))
.insertBefore(datalayer)
.transform(iapiConfig._pingrouptransform = ['T', visx, ymin]).hide();
pinrect = iapiGraphics.pinrect = paper.rect(0, canvasTop - ymin,
visw, canvasHeight, pingroup).attr({
'stroke-width': 0,
stroke: 'none',
fill: iapiConfig.pinPaneFill,
'shape-rendering': 'crisp',
ishot: true
});
// draw pin tracker
iapiGraphics.pintracker = paper.rect(iapiGraphics.trackerGroup).attr({
transform: pingroup.transform(),
x: 0,
y: canvasTop - ymin,
width: 0,
height: canvasHeight,
stroke: 'none',
fill: TRACKER_FILL,
ishot: true,
cursor: R.svg && 'ew-resize' || 'e-resize'
})
.hide()
.drag(function (_dx) {
var offset = visx + _dx + this.__pindragdelta,
pbl = this.__pinboundleft,
pbr = this.__pinboundright,
clip = this.data('cliprect').slice(0);
// Restrict to boundaries.
if (offset < pbl) {
offset = pbl;
}
else if (offset > pbr) {
offset = pbr;
}
pingroup.transform(['T', offset, ymin]);
iapiGraphics.pintracker.transform(pingroup.transform());
// reclip the pin area visibility for VML browser
if (!R.svg) {
clip[0] = clip[0] + offset - visx - this.__pindragdelta;
pingroup.attr('clip-rect', clip);
}
this.__pindragoffset = _dx;
}, function () {
// Calculate the pin bounds.
/** @todo remove renderer checks */
this.__pinboundleft = 0 - pinclip[0] + visx + canvasLeft;
this.__pinboundright = this.__pinboundleft + visw - pinclip[2];
// Store the value of clip-rect to be used later while re-clipping
// the pin area for VML browsers, as VML browser doesn't support clipispath
this.data('cliprect', pingroup.attr('clip-rect'));
// temporarily mark the group as if it is a clip path and
// not a clip rect. This is to prevent raphael's internal
// matrix inversion on clipping.
pingroup._.clipispath = true;
}, function () {
pingroup._.clipispath = false; // reset clip behavior
// store the last delta movement so that dragging can be
// resumed on next drag on same pin instance.
this.__pindragdelta += this.__pindragoffset;
// delete unneeded flags and accumulators;
delete this.__pindragoffset;
delete this.__pinboundleft;
delete this.__pinboundright;
});
}
// Draw the scrollbars and perform the initial configuration of
// the scrollbars.
// First crispen the scroller position to keep it in sync with the
// canvas. We temporarily increment plotborderwidth to account for
// the 1 pixel stroke width of scrollbar.
borderWidth++;
tAtt = R.crispBound(canvasLeft - borderWidth, canvasTop + canvasHeight + borderWidth,
canvasWidth + borderWidth + borderWidth, iapiConfig.scrollHeight, borderWidth);
borderWidth--; // revert the temporary increment
// During creation of the scroller element, we check for the
// "useRoundEdges" options and based on that, match the visuals and
// positioning of the scrollbar with the canvas. We shift it up
// slightly when useRoundEdges is on; so that it covers the lower
// round edges of the canvas.
// Bind pin and zoom selection events.
bindSelectionEvent(iapi, {
attr: {
stroke: iapiConfig.zoomPaneStroke,
fill: iapiConfig.zoomPaneFill,
strokeWidth: 0
},
selectionStart: function () {},
selectionEnd: function (o) {
// Calculate the pixel start and pixel end of the selection.
var pxs = o.selectionLeft - canvasLeft,
pxe = pxs + o.selectionWidth;
// In case chart crossline is available and visible, we hide it.
// In all cases, on selection end, the crossline is not needed
// to be visible.
iapiGraphics.crossline && iapiGraphics.crossline.hide();
// Based on the flag status of pin mode, we send the selection
// pixels to either pinRange or zoomRange functions.
iapi[iapiConfig.viewPortConfig.pinned ?
'pinRangePixels' : 'zoomRangePixels'](pxs, pxe);
}
});
// Create the crosshair and its related events. The crossline
// constructor is self aware and independently configures itself
// from the chart options.
if (crosslineOptions && crosslineOptions.enabled !== 0 && iapiConfig.useCrossline === 1) {
if (!crossline) {
crossline = iapiGraphics.crossline = new CrossLine();
}
crossline.configure(iapi, crosslineOptions);
}
else {
crosslineOptions && (crosslineOptions.enabled = 0);
crossline && crossline.hide();
}
},
resetZoom: function () {
var iapi = this,
iapiConfig = iapi.config,
history = iapiConfig.viewPortHistory,
origInfo = history[0];
// cannot reset twice!
if (!history.length) {
return false;
}
history.length = 0; // clear history
if (iapi.zoomTo(origInfo.dsi, origInfo.dei, origInfo)) {
/**
* This event is fired whenever the zoom history is cleared on a ZoomLine chart.
*
* @event FusionCharts#zoomReset
* @group chart-zoomline:zoom
*/
global.raiseEvent('zoomReset', iapi._zoomargs,
iapi.chartInstance, [iapi.chartInstance.id]);
}
return true;
},
eiMethods: /** @lends FusionCharts# */ {
/**
* Zooms ZoomLine chart one level out
* @group chart-zoomline:zoom-1
*
* @fires FusionCharts#zoomed
* @fires FusionCharts#zoomedOut
*/
zoomOut: function (callback) {
var chart = this.apiInstance,
asyncRender = chart.chartInstance.args.asyncRender,
output,
list = chart.getJobList();
if (!chart) {
return;
}
if (asyncRender) {
list.eiMethods.push(schedular.addJob(function () {
output = chart.zoomOut && chart.zoomOut();
if (typeof callback === 'function') {
callback(output);
}
}, chart, [], priorityList.postRender));
}
else {
return chart.zoomOut && chart.zoomOut();
}
},
scrollTo: chartAPI.mscartesian._scrollTo,
/**
* Zooms ZoomLine chart to a range of data.
*
* @group chart-zoomline:zoom-2
*
* @fires FusionCharts#zoomed
* @fires FusionCharts#zoomedIn
*
* @param {number} startIndex The index of the dataset from which it needs to be zoomed into.
* @param {number} endIndex the index of the dataset until which it needs to be zoomed into.
*/
zoomTo: function (startIndex, endIndex, callback) {
var chart = this.apiInstance,
asyncRender = chart.chartInstance.args.asyncRender,
output,
list = chart.getJobList();
if (!chart) {
return;
}
if (asyncRender || callback) {
list.eiMethods.push(schedular.addJob(function () {
output = chart.zoomRange && chart.zoomRange(startIndex, endIndex);
if (typeof callback === 'function') {
callback(output);
}
}, chart, [], priorityList.postRender));
}
else {
return chart.zoomRange && chart.zoomRange(startIndex, endIndex);
}
},
/**
* Reset all zoom, pan and pin actions that has been done on ZoomLine chart.
*
* @group chart-zoomline:zoom-3
*
* @fires FusionCharts#zoomReset
*/
resetChart: function () {
var chart = this.apiInstance,
asyncRender = chart.chartInstance.args.asyncRender,
list = chart.getJobList(),
_helperFn = function () {
chart.pinRangePixels && chart.pinRangePixels();
chart.resetZoom && chart.resetZoom();
};
if (!chart) {
return;
}
if (asyncRender) {
list.eiMethods.push(schedular.addJob(_helperFn, chart, [], priorityList.postRender));
}
else {
_helperFn();
}
},
/**
* Switches between zoom and pin mode. This function does not work when `allowPinMode` is set to `0` in
* chart XML or JSON.
*
* Zoom Line charts can have either a zoom mode or a pin mode. Zoom mode lets you select a section of the
* chart by dragging mouse cursor across the canvas and the chart zooms in on the selected section. In pin
* mode, the selected portion can be dragged around to compare with the rest of the chart. Zoom mode and pin
* mode can be toggled by clicking a button on the top right corner of the chart. This function lets you
* switch between zoom mode and pin mode programmatically.
*
* @group chart-zoomline:zoom-4
*
* @fires FusionCharts#zoomModeChanged
*
* @param {boolean} yes Boolean value to be `true` if zoom mode needs to be activated, `false` to activate
* pin mode.
*/
setZoomMode: function (yes) {
var chart = this.apiInstance,
asyncRender = chart.chartInstance.args.asyncRender,
list = chart.getJobList();
if (!chart) {
return;
}
if (asyncRender) {
list.eiMethods.push(schedular.addJob(function () {
chart.activatePin && chart.activatePin(!yes);
}, chart, [], priorityList.postRender));
}
else {
chart.activatePin && chart.activatePin(!yes);
}
},
/**
* Returns the index of the first visible point on canvas of ZoomLine chart
* @group chart-zoomline:view-1
* @returns {number}
*/
getViewStartIndex: function (callback) {
var chart = this.apiInstance,
asyncRender = chart.chartInstance.args.asyncRender,
zi,
list = chart.getJobList();
if (callback || asyncRender) {
list.eiMethods.push(schedular.addJob(function () {
if (typeof callback === 'function') {
if (!(chart && (zi = chart.config.viewPortConfig))) {
return;
}
callback(zi.ddsi);
}
}, chart, [], priorityList.postRender));
}
else {
if (!(chart && (zi = chart.config.viewPortConfig))) {
return;
}
return zi.ddsi;
}
},
/**
* Returns the index of the last visible point on canvas of ZoomLine chart
* @group chart-zoomline:view-2
* @returns {number}
*/
getViewEndIndex: function (callback) {
var chart = this.apiInstance,
asyncRender = chart.chartInstance.args.asyncRender,
zi,
vei,
list = chart.getJobList();
if (callback || asyncRender) {
list.eiMethods.push(schedular.addJob(function () {
if (typeof callback === 'function') {
if (!(chart && (zi = chart.config.viewPortConfig))) {
return;
}
vei = zi.ddei - 1;
callback((vei >= zi.clen ? zi.clen : vei) - 1);
}
}, chart, [], priorityList.postRender));
}
else {
if (!(chart && (zi = chart.config.viewPortConfig))) {
return;
}
vei = zi.ddei - 1;
return ((vei >= zi.clen ? zi.clen : vei) - 1);
}
}
},
zoomOut: function () {
var lastinfo,
origInfo,
iapi = this,
iapiConfig = iapi.config,
history = iapiConfig.viewPortHistory,
dsi,
dei,
args;
lastinfo = history.pop(); // access the last history
origInfo = history[0] || iapiConfig.viewPortConfig;
if (lastinfo) {
dsi = lastinfo.dsi;
dei = lastinfo.dei;
}
// If zoom level is less than 1, it is equivalent to reset.
else {
// But, in case chart was initially zoomed, we need to zoom out
// to full view.
if (iapiConfig._prezoomed) {
dsi = 0;
dei = origInfo.clen - 1;
}
}
/*info.lskip = lastinfo.lskip;
info.step = lastinfo.step;
iapi.components.xAxis[0].setAxisConfig({
'labelStep': info.lskip + 1
});*/
if (args = iapi.zoomTo(dsi, dei, lastinfo)) {
/**
* This event is fired when user zooms out on a ZoomLine chart.
*
* @event FusionCharts#zoomedOut
* @group chart-zoomline:zoom
*
* @param {number} level - Indicates to which zoom level the user has zoomed out to. `1` indicates that
* the chart has been completely zoomed out.
*
* @param {number} startIndex - The data start index that is in view for the zoomed out level
* @param {string} startLabel - The label of the data of the starting item in view.
* @param {number} endIndex - The data end index that is in view for the zoomed out level
* @param {string} endLabel - The label of the data of the last item in view.
*/
global.raiseEvent('zoomedout', args, iapi.chartInstance);
}
return true;
},
zoomRangePixels: function (pxs, pxe) {
var chart = this,
chartConfig = chart.config,
history = chartConfig.viewPortHistory,
info = chartConfig.viewPortConfig,
ppp = info.ppp,
start = info.ddsi,
args;
history.push(info); // push current state to history
// Peform function equvalent to this.getValuePixel().
// Code repeated here for lesser performance penalty of function
// call.
/*if (args = chart.zoomTo(start + mathFloor(pxs / ppp),
start + mathFloor(pxe / ppp))) {*/
if (args = chart.zoomTo(start + mathFloor(pxs / ppp),
start + mathFloor(pxe / ppp))) {
/**
* This event is fired when user zooms in on a ZoomLine chart.
*
* @event FusionCharts#zoomedIn
* @group chart-zoomline:zoom
*
* @param {number} level - Indicates to which zoom level the user has zoomed out to. `1` indicates that
* the chart has been completely zoomed out. It increments as user zooms in further.
*
* @param {number} startIndex - The data start index that is in view for the zoomed in level
* @param {string} startLabel - The label of the data of the starting item in view.
* @param {number} endIndex - The data end index that is in view for the zoomed in level
* @param {string} endLabel - The label of the data of the last item in view.
*/
global.raiseEvent('zoomedin', args, chart.chartInstance);
}
else {
// If zooming has failed then pop the history state that we
// pushed earlier.
history.pop();
}
},
zoomRange: function (dsi, dei) {
var pxs,
pxe,
iapi = this,
iapiConfig = iapi.config,
info = iapiConfig.viewPortConfig,
lastinfo,
xAxis = iapi.components.xAxis[0],
history = iapiConfig.viewPortHistory,
args;
history.push(info); // push current state to history
lastinfo = info;
info = history[0];
pxs = xAxis.getPixel(dsi);
pxe = xAxis.getPixel(dei);
lastinfo.x = pxs;
lastinfo.scaleX = iapiConfig.canvasWidth / (pxs - pxe);
info = lastinfo;
if (args = iapi.zoomTo(+dsi, +dei)) {
// already described in apidocs insidr zoomRangePixls
global.raiseEvent('zoomedin', args, iapi.chartInstance);
}
else {
// If zooming has failed then pop the history state that we
// pushed earlier.
history.pop();
}
},
zoomTo: function (dsi, dei, lastinfo) {
var pxs,
pxe,
origPos,
chart = this,
chartConfig = chart.config,
components = chart.components,
// labels = chart.xlabels.data,
info = chartConfig.viewPortConfig,
canvasHeight = chartConfig.canvasHeight,
canvasLeft = chartConfig.canvasLeft,
canvasTop = chartConfig.canvasTop,
canvasBottom = chartConfig.canvasBottom,
newinfo,
history = chartConfig.viewPortHistory,
clen = info.clen,
args,
xAxis = chart.components.xAxis[0];
// Detect max zoom and update the zoom history.
// start index must be positive and less than end. last index must
// not be greater than category count.
(dsi < 0) && (dsi = 0);
(dsi >= clen - 1) && (dsi = clen - 1);
(dei <= dsi) && (dei = dsi + 1);
(dei > clen - 1) && (dei = clen - 1);
// Find the final zoom level and bail out.
if (dsi === dei || (dsi === info.dsi && dei === info.dei)) {
return false;
}
// Revert pin mode, in case it is active.
chart.pinRangePixels();
// dsi = Math.floor(xAxis.getValue(pxs));
// dei = Math.ceil(xAxis.getValue(pxe));
newinfo = extend2({}, info);
newinfo.dsi = dsi;
newinfo.dei = dei;
info = (chartConfig.viewPortConfig = newinfo); // set current state
// Derive new zoom state from existing state.
if (!lastinfo) {
pxs = xAxis.getPixel(dsi);
pxe = xAxis.getPixel(dei);
origPos = chart.getOriginalPositions(pxs - canvasLeft,
canvasTop, pxe - canvasLeft, canvasBottom - canvasTop);
chart.zoomSelection(origPos[0], 0, origPos[2], canvasHeight);
}
else {
chart.updateVisual(lastinfo.x, lastinfo.y, lastinfo.scaleX, lastinfo.scaleY);
}
components.scrollBar.node.attr({
'scroll-ratio': info.vdl / (clen - !!clen),
'scroll-position': [info.dsi / (clen - info.vdl - 1), true]
});
args = {
level: history.length + 1,
startIndex: dsi,
startLabel: xAxis.getLabel(dsi).label,
endIndex: dei,
endLabel: xAxis.getLabel(dei).label
};
/**
* This event is fired when user either zooms in or zooms out on a ZoomLine chart.
*
* @event FusionCharts#zoomed
* @group chart-zoomline:zoom
*
* @param {number} level - Indicates to which zoom level the user has zoomed to. `1` indicates that
* the chart has been completely zoomed out. It increments as user zooms in further and decrements when user
* zooms out.
*
* @param {number} startIndex - The data start index that is in view for the zoomed level
* @param {string} startLabel - The label of the data of the starting item in view.
* @param {number} endIndex - The data end index that is in view for the zoomed level
* @param {string} endLabel - The label of the data of the last item in view.
*/
global.raiseEvent('zoomed', args, chart.chartInstance, [chart.chartInstance.id, dsi, dei, args.startLabel,
args.endLabel, args.level]);
return args;
},
activatePin: function (yes) {
var iapi = this,
iapiConfig = iapi.config,
toolBoxGraphics = iapi.components.tb.graphics,
info = iapiConfig.viewPortConfig,
button = toolBoxGraphics.pinButton;
// Checking for button is an indirect way to check if pinning is
// allowed.
/*if (!button) {
return;
}*/
// If pin is already active and "yes" is true then we get lost.
if (!(info.pinned ^ (yes = !!yes))) {
return;
}
if (!yes) {
// Call pin range with no range to deactivate pin.
iapi.pinRangePixels();
}
/**
* This event is fired when user toggles between zoom and pin mode of a zoomline chart.
*
* @event FusionCharts#zoomModeChanged
* @group chart-zoomline:zoom
*
* @param {boolean} pinModeActive - `true` indicates that post the mode change, pin mode is active.
*/
global.raiseEvent('zoomModeChanged', {
pinModeActive: yes
}, iapi.chartInstance, []);
/*iapiConfig.showToolBarButtonTooltext &&
(button.conf.tooltip = iapiConfig[yes &&
'btnSwitchToZoomModeTooltext' ||
'btnSwitchToPinModeTooltext'] || BLANK);*/
iapi.updateButtonVisual(button.node, yes ? 'pressed' : 'enable');
return (info.pinned = yes);
},
updateButtonVisual: function (buttonNode, mode) {
var config = {
disable:{
config: {
hover: {
fill: COLOR_WHITE,
'fill-symbol': COLOR_WHITE,
'stroke-width': 1,
stroke: COLOR_E3E3E3,
cursor: STR_DEF
},
normal: {
fill: COLOR_WHITE,
'fill-symbol': COLOR_WHITE,
stroke: COLOR_E3E3E3,
'stroke-width': 1,
cursor: STR_DEF
},
disable: {
fill: COLOR_WHITE,
'fill-symbol': COLOR_WHITE,
'stroke-width': 1,
stroke: COLOR_E3E3E3,
'stroke-opacity': 1,
cursor: STR_DEF
},
pressed: {
fill: COLOR_WHITE,
'fill-symbol': COLOR_WHITE,
'stroke-width': 1,
stroke: COLOR_E3E3E3,
cursor: STR_DEF
}
},
'button-disabled': false,
'stroke': COLOR_E3E3E3,
'stroke-opacity': 1
} ,
enable: {
config: {
hover: {
fill: COLOR_WHITE,
'fill-symbol': COLOR_WHITE,
'stroke-width': 1,
stroke: '#aaaaaa',
cursor: 'pointer'
},
normal: {
fill: COLOR_WHITE,
'fill-symbol': COLOR_WHITE,
stroke: COLOR_C2C2C2,
'stroke-width': 1,
cursor: 'pointer'
},
disable: {
fill: COLOR_WHITE,
'fill-symbol': COLOR_WHITE,
'stroke-width': 1,
stroke: COLOR_E3E3E3,
'stroke-opacity': 1,
cursor: 'pointer'
},
pressed: {
fill: COLOR_EFEFEF,
'fill-symbol': COLOR_EFEFEF,
'stroke-width': 1,
stroke: COLOR_C2C2C2,
cursor: 'pointer'
}
},
'button-disabled': false,
fill: [COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, COLOR_WHITE, true],
'stroke': COLOR_C2C2C2,
'stroke-opacity': 1
},
pressed: {
config: {
hover: {
fill: '#dcdcdc',
'fill-symbol': COLOR_WHITE,
'stroke-width': 1,
stroke: '#b7b7b7',
cursor: 'pointer'
},
normal: {
fill: '#dcdcdc',
'fill-symbol': COLOR_WHITE,
'stroke-width': 1,
stroke: '#b7b7b7',
cursor: 'pointer'
},
pressed: {
fill: '#dcdcdc',
'fill-symbol': COLOR_WHITE,
'stroke-width': 1,
stroke: '#b7b7b7',
cursor: 'pointer'
}
},
fill: '#dcdcdc',
'fill-symbol': COLOR_WHITE,
'stroke-width': 1,
stroke: '#b7b7b7',
cursor: 'pointer'
}
};
return buttonNode.attr(config[mode]);
},
pinRangePixels: function (pxs, pxe) {
var plotGraphics,
iapi = this,
iapiComponents = iapi.components,
paper = iapiComponents.paper,
iapiGraphics = iapi.graphics,
iapiConfig = iapi.config,
canvasLeft = iapiConfig.canvasLeft,
// elements = chart.elements,
// labels = chart.xlabels.data,
info = iapiConfig.viewPortConfig,
pingroup = iapiGraphics.zoompin,
pinrect = iapiGraphics.pinrect,
pinclip = iapiConfig['clip-pinrect'],
pingrouptransform = iapiConfig._pingrouptransform,
plots = iapiComponents.dataset,
pxw = pxe - pxs,
plot,
pinline,
i,
pintracker = iapiGraphics.pintracker;
// Find the no-pin situation and bailout.
if (!info || !pingroup || !pinrect) {
return;
}
// Hide
if (pxs === pxe) {
pingroup.hide();
pintracker.hide();
// todo code
// chart.pinButton.attr('button-active', false);
return info.pinned = false;
}
// Iterate over plots and copy the lines.
i = plots.length;
while (i--) {
plot = plots[i];
plotGraphics = plot.graphics;
pinline = plotGraphics.pinline;
if (!pinline) {
pinline = plotGraphics.pinline = paper.path(pingroup);
}
pinline.attr({
'path': plotGraphics.lineElement.attrs.path,
'transform': ['T',-iapiConfig._visx, -iapiConfig._ymin]
})
.attr(plot.config.pin);
}
// Adjust the cliprect to the new x amd width.
/** @todo remove renderer checks */
pinclip[0] = pxs + canvasLeft;
pinclip[2] = pxw;
pingroup.attr({
'clip-rect': pinclip,
'transform': pingrouptransform
}).show();
pintracker.__pindragdelta = 0; // dragging helper
pintracker.show().attr({
transform: pingrouptransform,
x: pxs,
width: pxw
});
// store the dsi and dei in px variables for raising in events
pxs = iapi.getValuePixel(pxs);
pxe = iapi.getValuePixel(pxe);
/**
* This event is fired when user switches to pin mode on zoomline chart and then performs a selection on the
* data plot to "pin" a range.
*
* @event FusionCharts#pinned
*
* @group chart-zoomline
*
* @param {number} startIndex - The data start index of the pinned range.
* @param {string} startLabel - The label of the data of the starting item of the pinned range.
* @param {number} endIndex - The data end index that is in view of the pinned range.
* @param {string} endLabel - The label of the data of the last item of the pinned range.
*/
// global.raiseEvent('pinned', {
// startIndex: pxs,
// endIndex: pxe,
// startLabel: labels[pxs],
// endLabel: labels[pxe]
// }, iapi.chartInstance, [iapi.chartInstance.id, pxs, pxe,
// labels[pxs], labels[pxe]]);
// state pin state in meta.
return info.pinned = true;
},
_createLayers: function () {
var iapi = this,
iapiGraphics,
paper = iapi.components.paper;
chartAPI.scatter._createLayers.call(iapi);
iapiGraphics = iapi.graphics;
//create the additional layers.
//create the container element
iapiGraphics.imageContainer = paper.group('dataset-orphan', iapiGraphics.dataSetGroup);
iapi.__preDraw();
iapi.toogleDragPan(true);
},
getValue: function (point) {
var chart = this,
chartConfig = chart.config,
chartComponents = chart.components,
viewPortConfig = chartConfig.viewPortConfig,
//the pixel wrt original canvas size
origpixel = chart.getOriginalPositions(point.x,point.y,point.x,point.y),
origX = origpixel[0],
origY = origpixel[1],
xAxis = chartComponents.xAxis[0],
yAxis = chartComponents.yAxis[0],
xaxisRange = xAxis.config.axisRange,
yaxisRange = yAxis.config.axisRange,
minX = xaxisRange.min,
maxX = xaxisRange.max,
maxY = yaxisRange.max,
minY = yaxisRange.min,
//calcualte the Pixel to Value Ratios.
xPVR = chartConfig.canvasWidth * viewPortConfig.scaleX / (maxX - minX),
yPVR = chartConfig.canvasHeight * viewPortConfig.scaleY / (maxY - minY);
return {
x: minX + ((origX - chartConfig.canvasLeft) / xPVR),
y: maxY - ((origY - chartConfig.canvasTop) / yPVR)
};
},
getOriginalPositions: function (x1,y1,x2,y2) {
var newW,
newH,
newX,
newY,
chart = this,
chartConfig = chart.config,
viewPortConfig = chartConfig.viewPortConfig,
oldScaleX = viewPortConfig.scaleX,
oldScaleY = viewPortConfig.scaleY,
//coodinates of the visual canvas origin wrt to original canvas.
oldX = viewPortConfig.x,
oldY = viewPortConfig.y,
xMin = mathMin(x1, x2),
xMax = mathMax(x1, x2),
yMin = mathMin(y1, y2),
yMax = mathMax(y1, y2);
//Right Bottom limit boundary
xMax = xMax > chartConfig.canvasWidth ? chartConfig.canvasWidth : xMax;
yMax = yMax > chartConfig.canvasHeight ? chartConfig.canvasHeight : yMax;
//Left Top Limit Boundary
xMin = xMin < 0 ? 0 : xMin;
yMin = yMin < 0 ? 0 : yMin;
// update the dimensions wrt to initial viewPort configurations.
newW = (xMax - xMin ) / oldScaleX;
newH = (yMax - yMin ) / oldScaleY;
newX = oldX + (xMin / oldScaleX);
newY = oldY + (yMin / oldScaleY);
//converts to the coordinates wrt original image
return [newX, newY, newW, newH];
},
zoomSelection: function (x, y, w, h) {
var chart = this,
chartConfig = chart.config,
scaleX,
scaleY,
newWidth,
newHeight,
maxX,
maxY,
newOriginX,
newOriginY;
// if the width or height is 0 return it
if (!w || !h) {
return;
}
//scale factors cannnot be negatives.
scaleX = Math.abs(chartConfig.canvasWidth / w);
scaleY = Math.abs(chartConfig.canvasHeight / h);
//total dimensions it would look alike when zoomed with these scale factors
newWidth = chartConfig.canvasWidth * scaleX;
newHeight = chartConfig.canvasHeight * scaleY;
//the amount to be shifted so that the zoom portion falls in the visible area.
newOriginX = x * scaleX;
newOriginY = y * scaleY;
//impose restrictions on the boundaries.
maxX = (newWidth - chartConfig.canvasWidth);
maxY = (newHeight - chartConfig.canvasHeight);
//left top restricition
newOriginX = (newOriginX < 0) ? 0 : newOriginX;
newOriginY = (newOriginY < 0) ? 0 : newOriginY;
//right bottom restriction
newOriginX = (newOriginX > maxX) ? maxX : newOriginX;
newOriginY = (newOriginY > maxY) ? maxY : newOriginY;
//update the final visual(drawing) part.
chart.updateVisual(x, y, scaleX, scaleY);
},
updateVisual: function (zoomX, zoomY, scaleX, scaleY, pixelatedDraw) {
var chart = this,
chartConfig = chart.config,
viewPortConfig = chartConfig.viewPortConfig,
oldCanvasWidth = chartConfig.canvasWidth,
oldCanvasHeight = chartConfig.canvasHeight,
// newCanvasWidth = oldCanvasWidth,
// newCanvasHeight = oldCanvasHeight,
viewPortHistory = chartConfig.viewPortHistory,
lastViewPortConfig = viewPortHistory.slice(-1)[0] || viewPortConfig,
// zoomEvent = [],
// viewPortStatus = viewPortConfig.status,
// xAxis = chartComponents.xAxis[0],
// yAxis = chartComponents.yAxis[0],
maxZoomLimit = chartConfig.maxZoomLimit; //restrictions in the zooming limit.
//check for validity for the input arguments provided.
//Incase invalid, revert to the last viewPort configurations, that is visually there remains no change.
viewPortConfig.x = isNaN(zoomX) ? (zoomX = lastViewPortConfig.x) : zoomX;
viewPortConfig.y = isNaN(zoomY) ? (zoomY = lastViewPortConfig.y) : zoomY;
viewPortConfig.scaleX = scaleX || (scaleX = lastViewPortConfig.scaleX);
viewPortConfig.scaleY = scaleY || (scaleY = lastViewPortConfig.scaleY);
// apply the limit
if (scaleX > maxZoomLimit) {
viewPortConfig.x = zoomX = mathMin(zoomX, (oldCanvasWidth - oldCanvasWidth/maxZoomLimit));
viewPortConfig.scaleX = scaleX = maxZoomLimit;
}
if (scaleY > maxZoomLimit) {
viewPortConfig.y = zoomY = mathMin(zoomY, (oldCanvasHeight - oldCanvasHeight/maxZoomLimit));
viewPortConfig.scaleY = scaleY = maxZoomLimit;
}
chart.updateManager(pixelatedDraw);
},
toogleDragPan: function (isToogle) {
var chart = this,
chartConfig = chart.config,
viewPortConfig = chartConfig.viewPortConfig,
status = viewPortConfig.status;
if (isToogle) {
viewPortConfig.status = (status === 'zoom') ? 'pan': 'zoom';
// global.raiseEvent('zoomModeChanged', {
// panModeActive: chartConfig.viewPortConfig.status
// }, chart.fusionCharts, [chart.fusionCharts.id]);
}
//visual update
//chart.updateButtonVisual();
},
resize: function () {
var chart = this,
chartConfig = chart.config,
chartGraphics = chart.graphics,
canvasComponents = chart.components.canvas,
canvasConfig = canvasComponents.config,
canvasGraphics = canvasComponents.graphics,
canvasBorder = canvasGraphics.canvasBorderElement,
canvas = canvasGraphics.canvasElement,
//bridge code
canvasBorderThickness = canvasConfig.canvasBorderThickness,
canvasHalfThickness = canvasBorderThickness / 2,
newHeight = (chartConfig.canvasHeight -= canvasBorderThickness),
newWidth = (chartConfig.canvasWidth -= (2*canvasBorderThickness)),
left = (chartConfig.canvasLeft += canvasBorderThickness);
chartConfig.canvasBottom -= canvasBorderThickness;
chartConfig.canvasRight -= canvasBorderThickness;
//resize the canvas according to the latest modifications in the canvas dimensions.
if (canvas) {
canvas.attr({
x: left,
y: chartConfig.canvasTop,
height: newHeight,
width: newWidth
});
}
else {
chart.drawCanvas(); // draw the canvas if it ceases to exist.
}
if (canvasBorder) {
//update the dimensions of the border of the canvas
canvasBorder.attr({
x: left - canvasHalfThickness,
y: chartConfig.canvasTop - canvasHalfThickness,
height: newHeight + canvasBorderThickness,
width: newWidth + canvasBorderThickness,
'stroke-width': canvasBorderThickness /* stroke-width is a property being applied both on
outer and inner side, hence canvasHalfThickness is used instead the full of it.*/
});
}
//update the image container wrt modified canvas dimension after resizing.
chartGraphics.imageContainer.attr({
'clip-rect': chartConfig.canvasLeft + ',' + chartConfig.canvasTop + ',' + chartConfig.canvasWidth +
',' + chartConfig.canvasHeight
}).transform('t' + chartConfig.canvasLeft + ',' + chartConfig.canvasTop);
//clipping the hot tracker element.
chartGraphics.trackerElem.attr({
x: chartConfig.canvasLeft,
y: chartConfig.canvasTop,
width: chartConfig.canvasWidth,
height: chartConfig.canvasHeight
});
/*Clipping on the tracker is required when a single plot covers more than the screen and one hover on
it, the hovering circle goes out of the canvas, which needs to be prevented and hence needs to be
clipped. This clipping again needs to be updated with every resizing.*/
chartGraphics.tracker && chartGraphics.tracker.attr({
'clip-rect': chartConfig.canvasLeft + ',' + chartConfig.canvasTop + ',' + chartConfig.canvasWidth +
',' + chartConfig.canvasHeight
});
},
updateManager: function (pos) {
var i,
labelStep,
stepValue,
chart = this,
scaleX,
chartComponents = chart.components,
dataSets = chartComponents.dataset,
len = dataSets.length,
chartConfig = chart.config,
allowPinMode = chartConfig.allowPinMode,
info = chartConfig.viewPortConfig,
ypvr = chartConfig._ypvr,
visW = chartConfig._visw,
xAxis = chart.components.xAxis[0],
getPixelX = function() {
return xAxis.getPixel.apply(xAxis,arguments);
},
// labels = chart.xlabels,
cssLabel = xAxis.getAxisConfig('labels').style,
// labelGroup = labels.group,
// textDirection = chartConfig.textDirection,
oppp, // target pixels-per-point
vdl, // visible display length
ppl, // num visible labels
ppp, // current pixels-per-point
step, // stepping on vdl to reach target ppp
lskip,
norm, // normalizer of vdl to allow smooth scrolling
dsi, // display start index
dei, // display end index
ddsi, // dynamic dsi post normalization
ddei,
nvl,
xAxisAnifConfig,
xAxisNameDrawConfig,
visibleExtremes,
updateButtonVisual = chart.updateButtonVisual,
toolBoxGraphics = chartComponents.tb.graphics,
zoomOutButton = toolBoxGraphics.zoomOutButton,
resetButton = toolBoxGraphics.resetButton,
pinButton = toolBoxGraphics.pinButton,
history = chartConfig.viewPortHistory;
if (chartConfig.legendClicked) {
for (i = 0; i < len; i += 1) {
dataSets[i].draw();
}
return;
}
// Use default config if none has been provided else extend current
// state.
!info && (info = chartConfig.viewPortConfig);
// Calculate stepping values here. This is required so that the
// number of anchors can be recalculated prior to updating plot.
oppp = info.oppp;
nvl = ppl = info.nvl;
dsi = info.dsi;
dei = info.dei;
vdl = info.vdl = dei - dsi;
ppl = info.ppl = nvl ? visW / nvl : info.oppl;
// Calculate label and anchor stepping.
step = info.step = ((ppp = info.ppp = visW / vdl) < oppp) ? mathCeil(oppp / ppp) : 1;
lskip = info.lskip = mathCeil(mathMax(ppl, toFloat(cssLabel.lineHeight)) / ppp / step);
// If scroll position is provided, we recalculate indices and
// ignore what has been sent via zoom info.
// We do not put position calculation in a separate function to
// avoid repeated recalculations
if (pos !== UNDEF) {
ddsi = (info.clen - vdl - 1) * pos ;
info.offset = (ddsi - (ddsi = toInt(ddsi))) * ppp;
ddei = ddsi + vdl;
}
else {
ddsi = info.dsi;
ddei = info.dei;
info.offset = 0;
}
norm = info.norm = ddsi % step;
// normalize the indices
info.ddsi = (ddsi = ddsi - norm);
info.ddei = (ddei = ddei + 2 * step - norm);
info.pvr = ypvr;
info._ymin = chartConfig._ymin;
info._yminValue = chartConfig._yminValue;
info.x = ((getPixelX(ddsi) - getPixelX(xAxis.getLimit().min) + info.offset)/ info.scaleX);
// once the visible labels are exceeding the category length.
if ((ddei - ddsi) > xAxis.getCategoryLen()) {
info.scaleX = 1;
}
else {
// info.scaleX = chartConfig.canvasWidth / (mathRound((ddei - ddsi + 1)/ (step)) * 0.5 * ppl);
info.scaleX = xAxis.getCategoryLen() / Math.abs(ddei - ddsi - step - 0.9);
}
if (pos !== UNDEF){
chartComponents.scrollBar.node && chartComponents.scrollBar.node.attr({
'scroll-position': (info._pos = pos)
});
}
visibleExtremes = xAxis._getVisibleConfig();
stepValue = Math.ceil((visibleExtremes.maxValue - visibleExtremes.minValue + 1) / nvl);
scaleX = chartConfig.viewPortConfig && chartConfig.viewPortConfig.scaleX;
labelStep = Math.max(Math.round(xAxis.getAxisConfig('labelStep')/scaleX), nvl ? stepValue : lskip * step);
xAxis.setLabelConfig({
step: labelStep
});
/*xAxis.setAxisConfig({
'labelStep': labelStep
});*/
xAxisAnifConfig = xAxis.getAxisConfig('animateAxis');
xAxisNameDrawConfig = xAxis.getAxisConfig('drawAxisName');
pos && (xAxis.setAxisConfig({
animateAxis : false,
drawAxisName : false
}));
xAxis.draw();
xAxis.setAxisConfig({
animateAxis : xAxisAnifConfig,
drawAxisName : xAxisNameDrawConfig
});
// Calculate the label length
/*len = labels.show ? mathCeil((ddei - ddsi) / step / lskip) : 0;
oldlen = info.llen - 1;
info.llen = len;
ppc = info.ppc = ppp * lskip * step; // pixels between two x-labels
labelGroup.trackTooltip(true);
// Iterate over the labels and update them.
// Add new anchor elements or if added, show them.
if (len > oldlen) {
for (j = oldlen, jj = len; j < jj; j++) {
(label = labels[j]) && label.show() ||
(labels[j] = paper.text(0, 0, BLANK, labelGroup).css(cssLabel).attr({
direction: textDirection
}));
}
}
// Hide extra label elements.
else {
for (j = len, jj = oldlen + 1; j < jj; j++) {
labels[j].hide();
}
}
// Calculate amount of anchors that will be visible. Until amrd is reached,
// all labels should be hidden. (re-use the previous len variables)
len = (ppp * step < info.amrd) ? 0 : mathCeil((ddei - ddsi) / step);
deltalen = len - info.alen;
oldlen = info.alen - 1;
info.alen = len; // Update anchor length.*/
// redraw the datasets
for (i = 0; i < len; i += 1) {
dataSets[i].draw();
}
// Show reset button when zoom history is long enough and always show
// zoom out button on every zoom-in.
updateButtonVisual(zoomOutButton.node, (info.vdl === info.clen - 1 ? 'disable' : 'enable'));
updateButtonVisual(resetButton.node, (history.length > 0 ? 'enable' : 'disable'));
updateButtonVisual(pinButton.node, (allowPinMode ? 'enable' : 'disable'));
if (win.FC_DEV_ENVIRONMENT && win.jQuery) {
if (FusionCharts['debugger'].enable()) {
this.debug = this.debug || (win.jQuery('#fc-zoominfo').length ||
win.jQuery('body').append('<pre id="fc-zoominfo">'), win.jQuery('#fc-zoominfo').css({
position: 'absolute',
left: '10px',
top: '0',
'pointer-events': 'none',
opacity: 0.7,
width: '250px',
zIndex: '999',
border: '1px solid #cccccc',
'box-shadow': '1px 1px 3px #cccccc',
background: '#ffffff'
}));
this.debug.text(JSON.stringify(info, 0, 2));
}
else {
this.debug && win.jQuery('#fc-zoominfo').remove();
delete this.debug;
}
}
},
_drawDataset: function () {
var iapi = this;
chartAPI.zoomline.updateManager.call(iapi);
},
getParsedLabel: function (index) {
var xlabels = this.xlabels;
return xlabels.parsed[index] ||
(xlabels.parsed[index] =
parseUnsafeString(xlabels.data[index] || BLANK));
},
_createToolBox: function () {
var toolBox,
toolBoxGraphics,
toolBoxAPI,
group,
SymbolStore,
Symbol,
zoomOutButton,
resetButton,
pinButton,
iapi = this,
config = iapi.config,
allowPinMode = config.allowPinMode,
components = iapi.components,
tooltip = config.showToolBarButtonTooltext,
chartMenuBar = components.chartMenuBar,
actionBar = components.actionBar;
/* Do not reconfigure the toolbox if its already drawn. This flag is set falsy on each time configurations
are updated. */
if (chartMenuBar && chartMenuBar.drawn || actionBar && actionBar.drawn) {
return;
}
chartAPI.scrollcolumn2d._createToolBox.call(iapi);
toolBox = components.tb;
toolBoxGraphics = toolBox.graphics || (toolBox.graphics = {});
toolBoxAPI = components.toolBoxAPI || toolBox.getAPIInstances(toolBox.ALIGNMENT_HORIZONTAL);
Symbol = toolBoxAPI.Symbol;
SymbolStore = toolBoxAPI.SymbolStore;
group = (components.chartMenuBar || components.actionBar).componentGroups[0];
// Add the symbols in the chartMenuBar
zoomOutButton = toolBoxGraphics.zoomOutButton = new Symbol('zoomOutIcon', undefined, toolBox.idCount++,
toolBox.pId)
.attachEventHandlers({
'click' : function () {
iapi.zoomOut();
},
'tooltext': tooltip && config.btnZoomOutTooltext || BLANK
});
resetButton = toolBoxGraphics.resetButton = new Symbol('resetIcon', undefined, toolBox.idCount++,
toolBox.pId)
.attachEventHandlers({
'click' : function () {
iapi.resetZoom();
},
'tooltext': tooltip && config.btnResetChartTooltext || BLANK
});
if (allowPinMode) {
pinButton = toolBoxGraphics.pinButton = new Symbol('pinModeIcon', undefined, toolBox.idCount++,
toolBox.pId)
.attachEventHandlers({
'click' : function () {
var info = config.viewPortConfig;
iapi.activatePin(!info.pinned);
},
'tooltext': tooltip && config.btnSwitchToPinModeTooltext || BLANK
});
// append the instance of the buttons to the group
group.addSymbol(pinButton, true);
}
group.addSymbol(resetButton, true);
group.addSymbol(zoomOutButton, true);
},
_scrollBar: chartAPI.scrollcolumn2d,
_manageScrollerPosition: chartAPI.scrollcolumn2d,
draw: function () {
var borderWidth,
roundEdges,
info,
tAtt,
scrollBar,
scrollNode,
canvasLeft,
canvasTop,
canvasHeight,
canvasWidth,
canvasConfig,
canvasBorderWidth,
iapi = this,
config = iapi.config,
graphics = iapi.graphics || (iapi.graphics = {}),
components = iapi.components,
jsonData = iapi.jsonData,
dataSet = jsonData.dataset,
category = jsonData.categories && jsonData.categories[0].category,
toolboxParentGroup;
chartAPI.msline.draw.call (iapi);
canvasLeft = config.canvasLeft;
canvasTop = config.canvasTop;
canvasHeight = config.canvasHeight;
canvasWidth = config.canvasWidth;
canvasConfig = components.canvas.config;
canvasBorderWidth = canvasConfig.canvasBorderWidth;
borderWidth = config.borderWidth;
roundEdges = config.useRoundEdges;
info = config.viewPortConfig;
if (!(toolboxParentGroup = graphics.toolboxParentGroup)) {
toolboxParentGroup = graphics.toolboxParentGroup = components.paper.group('toolbarParentGroup',
graphics.parentGroup);
}
if (dataSet && category) {
// Draw the scrollbars and perform the initial configuration of
// the scrollbars.
// First crispen the scroller position to keep it in sync with the
// canvas. We temporarily increment plotborderwidth to account for
// the 1 pixel stroke width of scrollbar.
borderWidth++;
tAtt = R.crispBound(canvasLeft - borderWidth, canvasTop + canvasHeight + borderWidth,
canvasWidth + borderWidth + borderWidth, config.scrollHeight, borderWidth);
borderWidth--; // revert the temporary increment
// During creation of the scroller element, we check for the
// "useRoundEdges" options and based on that, match the visuals and
// positioning of the scrollbar with the canvas. We shift it up
// slightly when useRoundEdges is on; so that it covers the lower
// round edges of the canvas.
scrollBar = components.scrollBar;
scrollNode = scrollBar && scrollBar.node;
// donot draw the scroller element if there is no dataSet in the JSON data.
scrollBar.draw(tAtt.x + (roundEdges && -1 || (borderWidth % 2)), tAtt.y - (roundEdges && 4 || 2), {
isHorizontal: true,
width: tAtt.width - (!roundEdges && 2 || 0),
height: tAtt.height,
showButtons: config.scrollShowButtons,
scrollRatio: info.vdl / (info.clen - !!info.clen), // standard
scrollPosition: [info.dsi / (info.clen - info.vdl - 1), false],
r: roundEdges && 2 || 0,
parentLayer : toolboxParentGroup.insertBefore(graphics.datalabelsGroup)
});
// attach the callback for raising event only for it is a new scroll node.
!scrollNode && (function () {
var prevPos;
R.eve.on('raphael.scroll.start.' + scrollBar.node.id, function (pos) {
prevPos = pos;
iapi.graphics.crossline && iapi.graphics.crossline.disable(true);
global.raiseEvent('scrollstart', {
scrollPosition: pos
}, iapi.chartInstance);
});
R.eve.on('raphael.scroll.end.' + scrollBar.node.id, function (pos) {
iapi.graphics.crossline && iapi.graphics.crossline.disable(false);
global.raiseEvent('scrollend', {
prevScrollPosition: prevPos,
scrollPosition: pos
}, iapi.chartInstance);
});
}());
}
}
}, chartAPI.msline, {
showValues: 0,
zeroplanethickness: 1,
zeroplanealpha: 40,
showzeroplaneontop: 0,
enablemousetracking: true
});
chartAPI('zoomlinedy',{
standaloneInit: true,
defaultDatasetType: 'zoomline',
applicableDSList: {
'zoomline': true
},
creditLabel: creditLabel,
friendlyName : 'Zoomable and Panable Multi-series Dual-axis Line Chart',
_spaceManager: chartAPI.msdybasecartesian._spaceManager,
_setAxisLimits: chartAPI.msdybasecartesian._setAxisLimits,
_createAxes: chartAPI.msdybasecartesian._createAxes,
_feedAxesRawData : chartAPI.msdybasecartesian._feedAxesRawData
}, chartAPI.zoomline, {
isdual: true
});
FusionCharts.register('component', ['dataset', 'zoomline', {
_setConfigure: function () {
var dataSet = this,
conf = dataSet.config,
cdef = dataSet.chart.jsonData.chart,
JSONData = dataSet.JSONData;
conf.drawanchors = pluckNumber(cdef.drawanchors, cdef.showanchors, 1);
conf.anchorradius = pluckNumber(JSONData.anchorradius, cdef.anchorradius, conf.linethickness + 2);
dataSet.__base__._setConfigure.apply(dataSet, arguments);
},
_firePlotEvent: function (eventType, plotIndex, e, datasetIndex) {
var dataset = this,
chart = dataset.chart,
components = dataset.components,
dataStore = components.dataRT || components.data,
data = dataStore[plotIndex],
setElement = data.graphics.element,
tip = lib.toolTip,
originalEvent = e.originalEvent,
style = chart.components.paper.canvas.style,
noCrossline = !dataset.chart.config.useCrossline,
config,
eventArgs,
setLink;
if (setElement) {
config = data.config;
setLink = config.setLink;
eventArgs = config.eventArgs;
switch (eventType) {
case 'mouseover' :
noCrossline && dataset._decideTooltipType(plotIndex, datasetIndex, e);
dataset._rolloverResponseSetter(chart, data, originalEvent);
setLink && (style.cursor = POINTER);
break;
case 'mouseout' :
tip.hide(chart.chartInstance.id);
dataset._rolloutResponseSetter(chart, data, originalEvent);
setLink && (style.cursor = DEFAULT_CURSOR);
break;
case 'click' :
plotEventHandler.call(setElement, chart, originalEvent, DATAPLOTCLICK, eventArgs);
break;
case 'mousemove' :
noCrossline && dataset._decideTooltipType(plotIndex, datasetIndex, e);
}
}
},
configure: function () {
var pgsw,
conf,
pin = {},
dataSet = this,
cdef = dataSet.chart.jsonData.chart;
//no animation is supported.
cdef.animation = 0;
cdef.showvalues = pluckNumber(cdef.showvalues, 0);
dataSet.__base__.configure.call(dataSet);
conf = dataSet.config;
// pin line graphics is same as main graphics except a few changes
pgsw = conf.linethickness + pluckNumber(cdef.pinlinethicknessdelta, 1);
pin['stroke-width'] = pgsw > 0 && pgsw || 0;
pin['stroke-dasharray'] = [3, 2];
pin.stroke = lib.hashify(conf.linecolor);
pin['stroke-opacity'] = conf.alpha / 100;
pin['stroke-linejoin'] = (conf['stroke-linejoin'] = 'round');
pin['stroke-linecap'] = (conf['stroke-linecap'] = 'round');
conf.pin = pin;
// animation is disabled in zoomline charts.
conf.animation = false;
conf.transposeanimduration = 0;
},
isWithinShape: function (pointObj, pX, x, y) {
if (!pointObj) {
return;
}
var anchorProps = pointObj.config.anchorProps,
config = pointObj.config,
radius,
borderThickness = anchorProps.borderThickness,
dataset = this,
dataStore = dataset.components.data,
dragTolerance = pluckNumber(config.dragTolerance, 0),
xPos,
yPos,
dx,
dy,
diff,
hoverEffects;
xPos = pointObj._xPos;
yPos = pointObj._yPos;
if (yPos !== null) {
hoverEffects = pointObj.config.hoverEffects;
radius = Math.max(anchorProps.radius, hoverEffects && hoverEffects.anchorRadius || 0, HTP) +
borderThickness / 2;
dx = (x - xPos);
dy = (y - yPos);
diff = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
if (diff <= radius || diff <= dragTolerance) {
return {
pointIndex: pX,
hovered: true,
pointObj: dataStore[pX]
};
}
else {
return false;
}
}
},
draw: function () {// retrieve required objects
var //newElem,
graphics,
UNDEF = null,
flStart = false,
flEnd = false,
k,
flag1 = false,
flag2 = false,
dataSet = this,
JSONData = dataSet.JSONData,
chart = dataSet.chart,
chartComponents = chart.components,
conf = dataSet.config,
datasetIndex = dataSet.index || dataSet.positionIndex,
chartConfig = chart.config,
chartAttr = chart.jsonData.chart,
dataSetComponents = dataSet.components,
dataStore = dataSetComponents.data,
dataSetLen = dataStore.length,
len = dataSetLen,
i,
paper = chartComponents.paper,
xAxis = chartComponents.xAxis[0],
yAxis = dataSet.yAxis,
xPos,
yPos,
layers = chart.graphics,
dataLabelsLayer = layers.datalabelsGroup,
parseUnsafeString = lib.parseUnsafeString,
getValidValue = lib.getValidValue,
toolText,
label,
setElement,
//hotElement,
setLink,
setValue,
eventArgs,
displayValue,
groupId,
animationDuration = conf.animation.duration,
dataObj,
setRolloutAttr,
setRolloverAttr,
lineThickness = conf.linethickness,
container = dataSet.graphics.container,
trackerContainer = dataSet.graphics.trackerContainer,
// Called when clicking on the data plot
// @param {Array} setDataArr
clickFunc = function (setDataArr) {
var ele = this;
plotEventHandler.call(ele, chart, setDataArr);
},
/*
Called when hovering over anchor
@param {object} dataObj
@param {boolean} hoverEnabled
*/
// rolloverResponseSetter = function (data) {
// var ele = this,
// dataObj = ele.data('dataObj'),
// hoverEnabled = dataObj.config.hoverEffects;
// // Calling hoverplotanchor if hovereffects are enabled
// if (hoverEnabled && dataObj && dataObj.graphics && dataObj.graphics.element) {
// dataSet._hoverPlotAnchor(dataObj, ROLLOVER, chartAttr);
// }
// plotEventHandler.call(ele, chart, data, ROLLOVER);
// },
/*
Called when hovering out of anchor
@param {object} dataObj
@param {boolean} hoverEnabled
*/
// rolloutResponseSetter = function (data) {
// var ele = this,
// dataObj = ele.data('dataObj'),
// hoverEnabled = dataObj.config.hoverEffects;
// // Calling hoverplotanchor if hovereffects are enabled
// if (hoverEnabled && dataObj && dataObj.graphics && dataObj.graphics.element) {
// dataSet._hoverPlotAnchor(dataObj, ROLLOUT, chartAttr);
// }
// plotEventHandler.call(ele, chart, data, ROLLOUT);
// },
viewPortConfig = chartConfig.viewPortConfig,
showTooltip = chartConfig.showtooltip,
//attr,
group = layers.datasetGroup,
hoverEffects,
shadow = conf.shadow,
anchorShadow,
dataLabelContainer = dataSet.graphics.dataLabelContainer,
anchorProps = {},
imgRef,
addAttr,
symbol,
config,
is3D = chart.is3D,
use3dlineshift = conf.use3dlineshift,
showValue,
/*
Called when transpose animation is completed
for hiding the dataset
*/
/*animCallBack = function() {
lineElement.attr({
path: currentPath.getPathArr()
});
if (dataSet.visible === false && (dataSet._conatinerHidden === false ||
dataSet._conatinerHidden=== undefined)) {
container.lineGroup.hide();
container.lineShadowGroup.hide();
container.anchorShadowGroup.hide();
container.anchorGroup.hide();
trackerContainer.hide();
dataLabelContainer && dataLabelContainer.hide();
// isLineSet ? (dataSet._conatinerHidden = fcJSON.lineset._conatinerHidden) :
// (dataSet._conatinerHidden = true);
dataSet._conatinerHidden = true;
}
},*/
// animCompleteFn = chart.getAnimationCompleteFn(),
/*
Called when the initial animation compeletes
for showing the dataset
*/
initAnimCallBack = function () {
container.lineShadowGroup.show();
container.anchorShadowGroup.show();
container.anchorGroup.show();
trackerContainer.show();
dataLabelContainer && dataLabelContainer.show();
chart._animCallBack();
// animCompleteFn();
},
// animFlag = true,
setTooltext,
connector,
dashStyle,
yBase = yAxis.getAxisBase(),
yBasePos = yAxis.yBasePos = yAxis.getAxisPosition(yBase),
xAxisZeroPos = xAxis.getAxisPosition(0),
xAxisFirstPos = xAxis.getAxisPosition(1),
pointDistance = xAxisFirstPos - xAxisZeroPos,
totalCanvasWidth,
xDepth = is3D ? 10 : 0,
yDepth = (is3D && use3dlineshift) ? 10 : 0,
clipCanvas = [
mathMax(0, chartConfig.canvasLeft - xDepth),
mathMax(0, chartConfig.canvasTop - yDepth),
mathMax(1, chartConfig.canvasWidth + xDepth * 2),
mathMax(1, chartConfig.canvasHeight + yDepth)
],
clipCanvasInit = [
mathMax(0, chartConfig.canvasLeft - xDepth),
mathMax(0, chartConfig.canvasTop - yDepth),
1,
mathMax(1, chartConfig.canvasHeight + yDepth * 2)
],
scroll = {},
isScroll = chart.hasScroll || false,
scrollPosition,
lineDashStyle = conf.lineDashStyle,
lineColorObj = {
color: conf.linecolor,
alpha: conf.alpha
},
lscthash = [toRaphaelColor(lineColorObj), lineDashStyle].join(':'),
setColor,
setDashStyle,
lineSegmentChange,
colorObj,
lineDrawing = 0,
initialAnimation = false,
connectorShadow,
lineElement = dataSet.graphics.lineElement,
visible = dataSet.visible,
x,
dip,
pool = dataSet.pool || (dataSet.pool = {
element: []
}),
removePath = {},
currentPath = {},
connectorPath = {},
anchorRadius = conf.anchorradius,
catLabel,
dataArr = [],
cntr,
j,
dataIndex,
strtIndex,
skippedVals,
endIndex,
showTerminalValidData = chartConfig.showTerminalValidData,
info = chartConfig.viewPortConfig,
showPeakData = chartConfig.showPeakData,
maxPeakDataLimit = chartConfig.maxPeakDataLimit,
minPeakDataLimit = chartConfig.minPeakDataLimit,
useCrossline = pluckNumber(chartConfig.useCrossline, 0),
step = info.step, // stepping that can fit target pixels per point
// minimum distance between two visible anchors
anchorminrenderdistance = xAxis.getPixel(info.step) - xAxisZeroPos,
// make anchors visible only when it is less than amrd.
hideAnchors = anchorminrenderdistance < info.amrd,
addAnchors = function (dataObj, dataIndex) {
var graphics = dataObj.graphics;
config = dataObj.config;
setValue = config.setValue;
setLink = config.setLink;
x = config.x || dataIndex;
setTooltext = getValidValue(parseUnsafeString(pluck(config.setLevelTooltext,
JSONData.plottooltext, chartAttr.plottooltext)));
config.toolTipValue = yAxis.dataLabels(setValue);
showValue = config.showValue;
anchorProps = config.anchorProps;
symbol = anchorProps.symbol;
anchorShadow = anchorProps.shadow;
displayValue = config.displayValue;
dip = config.dip || 0;
// Creating the data object if not created
if (!dataObj) {
dataObj = dataStore[dataIndex] = {
graphics : {}
};
}
// Storing the color Object of this data
colorObj = {
color: config.color,
alpha: config.alpha
};
dashStyle = config.dashStyle;
xPos = config.xPos || (xAxis.getAxisPosition(x) - xDepth);
// On hiding a dataset the y position of the hidden dataset is set to yBasePos
if (!dataSet.visible) {
yPos = yBasePos;
}
else {
yPos = yAxis.getAxisPosition(setValue) + yDepth;
}
hoverEffects = config.hoverEffects;
anchorProps.isAnchorHoverRadius = hoverEffects.anchorRadius;
label = config.label;
catLabel = xAxis.getLabel(dataIndex);
toolText = config.toolText + (setTooltext ? BLANK :
config.toolTipValue);
config.finalTooltext = toolText;
// Storing the event arguments
eventArgs = {
index: dataIndex,
link: setLink,
value: setValue,
displayValue: displayValue,
categoryLabel: catLabel,
toolText: toolText,
id: conf.userID,
datasetIndex: datasetIndex,
datasetName: JSONData.seriesname,
visible: visible
};
// donot create anchor elements if thats a null data.
if (config.setValue !== null && !hideAnchors) {
// If imageurl is present
if (anchorProps.imageUrl) {
imgRef = new Image();
addAttr = {
isTooltip : showTooltip,
setLink : setLink,
hotLayer : trackerContainer,
groupId : groupId,
clickFunc : clickFunc
};
imgRef.onload = dataSet._onAnchorImageLoad(dataSet, dataIndex, eventArgs, xPos, yPos);
imgRef.onerror = dataSet._onErrorSetter(xPos, yPos, dataIndex, dataSet);
imgRef.src = anchorProps.imageUrl;
}
else {
setElement = graphics.element;
if (!setElement) {
// If there is no existing graphics element then create it
if(pool.element && pool.element.length) {
setElement = graphics.element = pool.element.shift();
}
else {
setElement = graphics.element = paper.polypath(container.anchorGroup);
}
}
// Set all attributes that are not related to non position
setElement
.attr({
polypath: [anchorProps.symbol[1] || 2, xPos, yPos,
anchorProps.radius, anchorProps.startAngle, dip],
fill: toRaphaelColor({
color: anchorProps.bgColor,
alpha: anchorProps.bgAlpha
}),
stroke: toRaphaelColor({
color: anchorProps.borderColor,
alpha: anchorProps.borderAlpha
}),
'stroke-width': anchorProps.borderThickness,
'visibility': !anchorProps.radius ? 'hidden' : visible
})
.shadow(anchorShadow, container.anchorShadowGroup)
.data('anchorRadius', anchorProps.radius)
.data('anchorHoverRadius', hoverEffects.anchorRadius)
.data('hoverEnabled', hoverEffects.enabled)
.data('eventArgs',eventArgs);
if (hoverEffects.enabled) {
setRolloverAttr = {
polypath: [hoverEffects.anchorSides || 2,
xPos, yPos,
hoverEffects.anchorRadius,
hoverEffects.startAngle,
hoverEffects.dip
],
fill: toRaphaelColor({
color: hoverEffects.anchorColor,
alpha: hoverEffects.anchorBgAlpha
}),
stroke: toRaphaelColor({
color: hoverEffects.anchorBorderColor,
alpha: hoverEffects.anchorBorderAlpha
}),
'stroke-width': hoverEffects.anchorBorderThickness
};
setRolloutAttr = {
polypath: [anchorProps.sides, xPos, yPos,
anchorProps.radius, anchorProps.startAngle, dip
],
fill: toRaphaelColor({
color: anchorProps.bgColor,
alpha: anchorProps.bgAlpha
}),
stroke: toRaphaelColor({
color: anchorProps.borderColor,
alpha: anchorProps.borderAlpha
}),
'stroke-width': anchorProps.borderThickness
};
setElement && setElement
.data('setRolloverAttr', setRolloverAttr)
.data('setRolloutAttr', setRolloutAttr);
}
// hide the anchor element if the data is that of a null data
setElement[(setValue || (setValue === 0)) ? 'show' : 'hide']();
}
// anchor Radius of hot element is set to maximum of hover radius and anchor radius
anchorRadius = mathMax(anchorProps.radius,
hoverEffects &&
hoverEffects.anchorRadius || 0);
}
dataObj._xPos = xPos;
dataObj._yPos = yPos;
/*
if colorObj and dash style of this data is different from
the previous data then a new line segment is to be created
*/
lineSegmentChange = (lscthash !== [
toRaphaelColor(colorObj || lineColorObj),
dashStyle || lineDashStyle
].join(':'));
connector = dataObj.graphics.connector;
connectorPath = dataSet.getLinePath([dataObj], connectorPath);
setColor = toRaphaelColor(colorObj || lineColorObj);
if (colorObj) {
connectorShadow = {
opacity: colorObj && colorObj.alpha/100
};
}
else {
connectorShadow = shadow;
}
setDashStyle = dashStyle || lineDashStyle;
/*If color,alpha or dashed is not defined for this data
then line drawing is set to 1 so the path of this data is
appended to mainLinePath and not the connector path
*/
if (pluck(config.setColor, config.setAlpha, config.setDashed) === UNDEFINED) {
lineDrawing = 1;
}
else {
lineDrawing = 0;
}
lscthash = [setColor, setDashStyle].join(':');
/*Storing the x position and y position in dataObject for future reference
For example - when drawing labels we need this xPos and yPos
*/
showValue && !anchorProps.imageUrl && dataSet.drawLabel(dataIndex);
dataArr.push(dataObj);
},
renderPeakData = function(cVal, index) {
var numVals = cVal && cVal.length,
orderArr = cVal.slice().sort(function (a, b){
return a.config.setValue - b.config.setValue;
}),
maxVal = orderArr && orderArr.pop().config.setValue,
minVal = (orderArr.length && orderArr.shift().config.setValue) || maxVal,
counter = 0,
origIndex,
val;
//now if any value is above or below of the
//peak limits enter into iteration. This is to maximize
//performance.
if(maxVal > maxPeakDataLimit || minVal < minPeakDataLimit) {
while(counter < numVals) {
setElement = cVal[counter];
val = setElement.config.setValue;
if(val > maxPeakDataLimit || val < minPeakDataLimit) {
origIndex = index + counter;
//we have a sudden peak. need to show this
addAnchors(setElement, origIndex);
}
counter += 1;
}
}
},
reuseFN = function (start, end) {
// todo: Need to re-validate the use of this. Its a safer approach to remove one elements each
// from both sides
start -= 1;
end += 1;
// todo ends.
var prop;
for (i = start; i < end; i += 1) {
graphics = dataStore[i] && dataStore[i].graphics || {};
dataStore[i] && (dataStore[i].config.isRemoving = true);
for (prop in graphics) {
if (!pool[prop]) {
pool[prop] = [];
}
if (graphics[prop]) {
pool[prop].push(graphics[prop].hide());
graphics[prop] = undefined;
}
}
}
},
ddsi = (viewPortConfig.ddsi || 0),
ii = viewPortConfig.ddei || len,
_oldStartIndex = conf._oldStartIndex,
_oldEndIndex = conf._oldEndIndex,
_oldStep = conf._oldStep,
removeDataArr = dataSetComponents.removeDataArr,
removeDataArrLen = removeDataArr && removeDataArr.length;
// Creating the line group and appending it to dataset layer if not created
group.line = group.line ||
paper.group('line', group);
/*
* Creating lineConnector group and appending it to dataset layer if not created
* Lineconnector group has the anchorgroups of all datasets
*/
group.lineConnector = group.lineConnector ||
paper.group('line-connector', group);
// Create dataset container if not created
if (!container) {
container = dataSet.graphics.container = {
lineShadowGroup: paper.group('connector-shadow', group.line),
anchorShadowGroup: paper.group('anchor-shadow', group.lineConnector),
lineGroup: paper.group('line', group.line),
anchorGroup: paper.group('anchors', group.lineConnector)
};
container.lineGroup.trackTooltip(true);
if (!visible) {
container.lineShadowGroup.hide();
container.anchorShadowGroup.hide();
container.lineGroup.hide();
container.anchorGroup.hide();
}
}
// Create tracker container if not created
if (!trackerContainer) {
trackerContainer = dataSet.graphics.trackerContainer = paper.group('line-hot',layers.trackerGroup)
.toBack();
if (!visible) {
trackerContainer.hide();
}
}
if (!dataStore) {
dataStore = dataSet.components.data = [];
}
if (!dataLabelContainer) {
dataLabelContainer = dataSet.graphics.dataLabelContainer = dataSet.graphics.dataLabelContainer ||
paper.group('datalabel', dataLabelsLayer);
if (!visible) {
dataLabelContainer.hide();
}
}
/* Calculating total canvas width
Required for scroll charts
*/
totalCanvasWidth = pointDistance * len;
// if anchors were shown in previous state and now hidden due to amrd constrain, hide them all of the prev.
if (hideAnchors && !conf._oldHideAnchors) {
reuseFN(_oldStartIndex, _oldEndIndex);
}
else {
// If step values are changed while zoom in/out, remove all of the prev anchors.
if (step !== _oldStep) {
reuseFN(_oldStartIndex, _oldEndIndex);
}
// useful in case of scroll. Here the step values are not changed. Hence removing optimally.
else {
if (ddsi > _oldStartIndex) {
reuseFN(_oldStartIndex, (ddsi > _oldEndIndex) ? _oldEndIndex : ddsi);
}
if (ii < _oldEndIndex) {
reuseFN((ii < _oldStartIndex) ? _oldStartIndex : ii, _oldEndIndex);
}
if (ddsi < _oldStartIndex || ii > _oldEndIndex) {
reuseFN(_oldStartIndex, _oldEndIndex);
}
}
}
// caching the present state for future reference.
conf._oldHideAnchors = hideAnchors;
conf._oldEndIndex = ii;
conf._oldStep = step;
dataSet.setVisibility(visible);
//create plot elements
for (i = (conf._oldStartIndex = ddsi); i <= ii; i += (step)) {
dataObj = dataStore[i] || {};
config = dataObj.config || {};
config.isRemoving = false;
setValue = config.setValue || UNDEF;
dataIndex = i;
if (showTerminalValidData) {
// search for first valid data point and first step valid data point
// true if first data value is undefined
if ((i === 0) && (setValue === UNDEF)) {
cntr = 0;
for (j = k = i; j < len;) {
// search for first valid data point and set flag1 true,
// if first valid data point found
if ((dataStore[j].config.setValue === UNDEF) && !flag1) {
j++;
}
else {
flag1 = true;
}
// search for first valid step data point and set flag2 true,
// if first valid data point found
if ((dataStore[k].config.setValue === UNDEF) && (!flag2) && (k <= len)) {
k += step;
cntr++;
}
else {
flag2 = true;
}
// check both flag value
// terminates loop if both first valid data point and first valid step data point found
if (flag1 && flag2) {
flag1 = flag2 = false;
break;
}
}
if ( j % step !== 0) {
flStart = true;
config = dataStore[j].config;
dataIndex = j;
}
}
// search for last valid data point and last step valid data point
// true if i exceeds data length
else if ((i >= len) && (setValue === UNDEF)) {
for (j = k = i; j > 0;) {
// search for last valid data point and set flag1 true,
// if last valid data point found
if ((dataStore[j] === undefined) && !flag1) {
j--;
}
else {
flag1 = true;
}
// search for last valid step data point and set flag2 true,
// if last valid data point found
if (dataStore[k] === undefined && (!flag2) && (k >= 0)) {
k -= step;
}
else {
flag2 = true;
}
// check both flag value
// terminates loop if both last valid data point and last valid step data point found
if (flag1 && flag2) {
break;
}
}
if ( j % step !== 0) {
flEnd = true;
config = dataStore[j].config;
dataIndex = j;
}
}
}
dataObj = dataStore[dataIndex];
if (!dataObj) {
continue;
}
addAnchors(dataObj, dataIndex);
// No point searching for peak when ther is no stepping
if(showPeakData && step > 1) {
strtIndex = mathMin((i + 1), ii);
endIndex = mathMin((strtIndex + step), ii);
skippedVals = (endIndex === ii) ? dataStore.slice(strtIndex) : dataStore.slice(strtIndex,
endIndex);
skippedVals.length && renderPeakData(skippedVals, strtIndex);
}
}
currentPath = dataSet.getLinePath(dataArr, {});
removePath = dataSet.getLinePath(dataArr, removePath);
conf.lastPath = currentPath;
if (!lineElement) {
if (pool.lineElement && pool.lineElement.length) {
lineElement = dataSet.graphics.lineElement = pool.lineElement.shift();
}
else {
lineElement = dataSet.graphics.lineElement = paper.path(container.lineGroup);
}
}
// Show tooltip if crossline is disabled
if (!useCrossline) {
if (!lineElement.tooltipListenerAttached) {
lineElement.tooltipListenerAttached = true;
lineElement.mousemove(function (e) {
info = chartConfig.viewPortConfig;
var plotX = chartConfig._visx,
step = info.step,
stepw = info.ppp * step,
x = getMouseCoordinate(chart.linkedItems.container, e, chart).chartX - plotX,
pos,
parsedLabel,
tooltipText,
tooltipTextSeparator = chartConfig.tooltipSepChar;
x = (x += (stepw / 2) + info.offset) - x % (stepw);
pos = (pos = (chart.getValuePixel(x))) + pos % step;
parsedLabel = xAxis.getLabel(pos).label;
// Initial tooltip text
tooltipText = parsedLabel + tooltipTextSeparator +
dataSet.components.data[pos].config.formatedVal;
// Add plot name if exists in tooltip text
tooltipText = conf.seriesname && (conf.seriesname + tooltipTextSeparator + tooltipText) ||
tooltipText;
lineElement.tooltip(chartConfig.crossline.enabled === 0 ? tooltipText : false);
});
}
}
lineElement
.attr({
path: currentPath.getPathArr(),
'stroke-dasharray': lineDashStyle,
'stroke-width': lineThickness,
stroke: toRaphaelColor(lineColorObj),
'stroke-linecap': 'round',
/* for lines even with thickness as 2 we need to have round line join
otherwise the line join may look like exceeding the correct position
*/
'stroke-linejoin': lineThickness >= MAX_MITER_LINEJOIN ? 'round' : 'miter'
})
.shadow(shadow, container.lineShadowGroup);
// Animation from right when scrollToEnd is 1
if (isScroll) {
scrollPosition = scroll.startPercent;
clipCanvas[2] = totalCanvasWidth + clipCanvasInit[0];
if (scrollPosition === 1) {
clipCanvasInit[0] = clipCanvas[2];
clipCanvas[0] = 0;
}
}
clipCanvas[3] = clipCanvas[3] + yDepth;
if (animationDuration && visible && initialAnimation) {
container.anchorGroup.hide();
container.lineShadowGroup.hide();
container.anchorShadowGroup.hide();
dataLabelContainer.hide();
group.line.attr({
'clip-rect': clipCanvasInit
})
.animate({
'clip-rect': clipCanvas
}, animationDuration, 'normal', initAnimCallBack);
}
dataSet.drawn = true;
removeDataArrLen && dataSet.remove();
},
setVisibility: function (isVisible, isTranspose) {
var dataSet = this,
graphics = dataSet.graphics,
container = graphics && graphics.container,
trackerContainer = graphics && graphics.trackerContainer,
dataLabelContainer = graphics && graphics.dataLabelContainer,
action = isVisible ? 'show' : 'hide';
container.lineGroup[action]();
container.anchorGroup[action]();
container.anchorShadowGroup[action]();
container.lineShadowGroup[action]();
trackerContainer[action]();
dataLabelContainer[action]();
isTranspose && dataSet.transposeLimits(isVisible);
},
transposeLimits: function (isVisible) {
var dataSet = this,
chart = dataSet.chart,
chartConfig = chart.config,
yAxis = dataSet.yAxis;
chart._chartAnimation();
dataSet.visible = isVisible;
dataSet._conatinerHidden = !isVisible;
chart._setAxisLimits();
yAxis.draw();
chartConfig.legendClicked = true;
// Calling the draw function for redrawing the dataset
chart._drawDataset();
delete chartConfig.legendClicked;
},
/*
* Hides when clicked on its respective legend
* Fired every time an activated legend is clicked
*/
hide : function() {
this.setVisibility(false, true);
},
show : function() {
this.setVisibility(true, true);
}
},'Line']);
CrossLine = function () {};
CrossLine.prototype.configure = function (chart, options) {
// Create the tracker for cross-hair. This is needed for mouse
// tracking.
var i,
ii,
plot,
plotColor,
attrObj = {},
crossLine = this,
labelPadding = 2.5,
chartComponents = chart.components,
numberFormatter = chartComponents.numberFormatter,
paper = chartComponents.paper,
chartConfig = chart.config,
chartGraphics = chart.graphics,
plotX = this.left = chartConfig._visx,
plotY = this.top = chartConfig.canvasTop,
plotH = this.height = chartConfig.canvasHeight,
plotO = this._visout = chartConfig._visout,
plots = this.plots = chart.components.dataset,
datalayer = chartGraphics.datalabelsGroup,
group,
line,
labelStyle = options.labelstyle,
valueStyle = options.valuestyle,
pyaxis = chartComponents.yAxis[0],
pYAxisLimits = pyaxis.getLimit(),
syaxis = chartComponents.yAxis[1],
sYAxisLimits = syaxis && syaxis.getLimit(),
tracker = this.tracker,
labels = this.labels,
positionLabel = this.positionLabel,
linkedItems = chart.get('linkedItems'),
containerElem = linkedItems.container,
listeners = linkedItems.eventListeners || (linkedItems.eventListeners = []),
isWithinCanvas = function (e, fn, fnOut) {
// calls the function only if it is within the canvasArea.
if (chart.isWithinCanvas(e, chart).insideCanvas) {
fn.call(crossLine, e);
}
else {
fnOut.call(crossLine, e);
}
};
crossLine.width = chartConfig._visw;
// Create the group inside data layer where the cross-line elements will play around.
group = this.group;
if (!group) {
group = this.group = paper.group ('crossline-labels', datalayer);
// Store chart's container to be use by mouseMove event
// to calculate the mouse coordinates.
this.container = containerElem;
}
group
.attr ( {
transform: ['T', plotX, chartConfig._ymin]
})
.css(valueStyle);
// Cross-line needs a personal tracker to intercept mouse interactions around and over data plots.
if (!tracker) {
tracker = crossLine.tracker = containerElem;
//adds to event stack.
listeners.push(addEvent(containerElem, 'touchstart mousemove', function (e) {
isWithinCanvas(e, crossLine.onMouseMove, crossLine.onMouseOut);
}, crossLine));
//adds to event stack.
listeners.push(addEvent(containerElem, 'mousedown', function () {
crossLine.onMouseDown();
}, crossLine));
//adds to event stack.
listeners.push(addEvent(containerElem, 'mouseup', function () {
crossLine.onMouseUp();
}, crossLine));
//adds to event stack.
listeners.push(addEvent(containerElem, 'mouseout', function () {
crossLine.onMouseOut();
}, crossLine));
}
/*tracker
.attr ({
x: plotX,
y: plotY,
width: plotW,
height: plotH,
stroke: 'none',
'stroke-width': 0,
fill: TRACKER_FILL
});*/
// Cross-line obviously needs a line.
line = this.line;
if (!line) {
line = this.line = paper.path(datalayer)
.toBack ();
}
line
.attr (extend2 ( {
path: ['M', plotX, plotY, 'l', 0, plotH]
}, options.line));
if (!labels) {
labels = this.labels = options.valueEnabled && paper.set ();
}
// add the category label
if (options.labelEnabled) {
attrObj.x = plotO;
attrObj.y = plotY + plotH + (chartConfig.scrollHeight || 0) + labelPadding;
attrObj['vertical-align'] = 'top';
attrObj.direction = chartConfig.textDirection;
attrObj.text = BLANK;
if (!positionLabel) {
positionLabel = this.positionLabel = paper.text(attrObj, labelStyle, chartGraphics.datalabelsGroup)
.insertBefore (chartGraphics.datasetGroup);
}
else {
positionLabel.attr(attrObj);
positionLabel.css(labelStyle);
}
}
else {
positionLabel && positionLabel.remove();
delete this.positionLabel;
}
// initially hidden
this.hide ();
this.ppixelRatio = -pyaxis.getPVR();
this.spixelRatio = syaxis && -syaxis.getPVR();
this.yminValue = chartConfig._yminValue;
this.pyaxisminvalue = pYAxisLimits.min;
this.pyaxismaxvalue = pYAxisLimits.max;
this.syaxisminvalue = sYAxisLimits && sYAxisLimits.min;
this.syaxismaxvalue = sYAxisLimits && sYAxisLimits.max;
this.positionLabels = chartConfig.xlabels || {
data: [],
parsed: []
};
this.chart = chart;
// Open closed function to access chart variables.
this.getZoomInfo = function () {
return chartConfig.viewPortConfig;
};
this.getDataIndexFromPixel = function (px) {
return Math.round(chart.components.xAxis[0].getValue(px));
};
this.getPositionLabel = function (index) {
var text = chart.components.xAxis[0].getLabel(index);
return (text && text.label) || BLANK;
};
if (options.valueEnabled) {
for (i = 0, ii = plots.length; i < ii; i += 1) {
plot = plots[i];
plotColor = hashify(plot.config.linecolor);
attrObj.x = 0;
attrObj.y = plotO;
attrObj.fill = plotColor;
attrObj.direction = chartConfig.textDirection;
attrObj.text = BLANK;
attrObj['text-bound'] = valueStyle['text-bound'];
attrObj.lineHeight = valueStyle.lineHeight;
if (!labels[i]) {
labels[i] = labels.items[i] = paper.text(attrObj, UNDEFINED, group);
}
else {
labels[i]
.attr(attrObj);
// .css (valueStyle);
}
}
for (; i < labels.items.length; i += 1) {
labels[i].remove();
delete labels[i];
labels.items.splice(i,1);
}
this.numberFormatter = numberFormatter;
}
else if (labels.items && labels.items.length) {
for (i = 0; i < labels.items.length; i += 1) {
labels[i].remove();
delete labels[i];
}
labels.length = 0;
}
};
CrossLine.prototype.disable = function (state) {
if (state !== UNDEF) {
this.disabled = !!state;
if (this.disabled && this.visible) {
this.hide ();
}
}
return this.disabled;
};
CrossLine.prototype.onMouseOut = function () {
this.hide ();
this.position = undefined;
};
CrossLine.prototype.onMouseDown = function () {
!hasTouch && this.hide ();
this._mouseIsDown = true;
};
CrossLine.prototype.onMouseUp = function () {
!hasTouch && this.hide ();
delete this._mouseIsDown;
};
CrossLine.prototype.onMouseMove = function (e) {
if (this.disabled || (this._mouseIsDown && !hasTouch)) {
return;
}
var residue,
info = this.getZoomInfo (),
line = this.line,
plotX = this.left,
step = info.step,
chart = this.chart,
xAxis = chart.components.xAxis[0],
chartConfig = chart.get('config'),
canvasLeft = chartConfig.canvasLeft,
axisDimention = xAxis.getAxisConfig('axisDimention'),
// labelStep = xAxis.getAxisConfig('labelStep'),
x = getMouseCoordinate (this.container, e, chart).chartX - plotX,
xAxisVisible = xAxis._getVisibleConfig(),
// cdm = chart.config.cdm,
//bridge code.
/*bridgePixel = cdm ? (xAxis.getPixel(0) - xAxis.getPixel(axisMin)) / info.scaleX :
axisDimention.x - canvasLeft,*/
bridgePixel = axisDimention.x - canvasLeft,
pos;
// x = (x += (stepw / 2) + info.offset) - x % (stepw);
pos = (pos = this.getDataIndexFromPixel (mathRound (x))) + ((((residue =
(pos % step)) > (step / 2)) ? step - residue : -residue));
x = (xAxis.getPixel(pos) - bridgePixel) - canvasLeft;
// x -= info.offset;
line.transform(['T', mathRound(x), 0]);
(this.hidden && (chartConfig.crossline.enabled !== 0)) && this.show ();
// Donot show the crossline out of canvas area.
(pos < xAxisVisible.minValue || pos > xAxisVisible.maxValue) && (this.hide());
if (pos !== this.position || this.hidden) {
this.position = pos;
this.lineX = x;
this.updateLabels ();
}
};
CrossLine.prototype.updateLabels = function () {
var crossline = this,
labelPadding = 2.5,
labels = crossline.labels,
plots = crossline.plots,
visw = crossline.width,
position = crossline.position,
x = crossline.lineX,
flooredX = mathFloor (x),
ppvr = crossline.ppixelRatio,
spvr = crossline.spixelRatio,
yminValue = crossline.yminValue,
plotOut = crossline._visout,
numberFormatter = crossline.numberFormatter,
pYAxisMinValue = crossline.pyaxisminvalue,
pYAxisMaxValue = crossline.pyaxismaxvalue,
sYAxisMinValue = crossline.syaxisminvalue,
sYAxisMaxValue = crossline.syaxismaxvalue,
verticalLimits = (function () {
/*
* This controls the overlapping of the crosshair labels if the plots are close.
* This calculates the space to display the labels at the before it is plotted in DOM.
* The buleprint of the algorithm is like:
* When the labels position is being calculated.
* a) Place the label if the position is not occupied
* b) If the position is occupied find the neares unoccupied space
* c) If the unoccupied space is downwards place it there
* d) If the unoccupied space is upwards move the whole system up and place it in the next available
* down position
*/
var _top = crossline.height * (-1),
_bottom = yminValue * ppvr,
boxHeight = 0, // Height of text
margin = 2,
oMatrix,
result = { },
isSpaceEnough = false,
sections,
abs = Math.abs,
floor = Math.floor,
scale = { };
/*
* Polyfills
* Ofcourse and obviously for IE8
*/
// Pollyfills for Object.create
// Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
if (typeof Object.create != 'function') {
// Production steps of ECMA-262, Edition 5, 15.2.3.5
// Reference: http://es5.github.io/#x15.2.3.5
/*jslint freeze: false */
Object.create = (function () {
// To save on memory, use a shared constructor
function Temp () { }
// make a safe reference to Object.prototype.hasOwnProperty
var hasOwn = Object.prototype.hasOwnProperty;
return function (O) {
var Properties, prop, obj;
// 1. If Type (O) is not Object or Null throw a TypeError exception.
if (typeof O != 'object') {
throw new TypeError ('Object prototype may only be an Object or null');
}
// 2. Let obj be the result of creating a new object as if by the
// expression new Object () where Object is the standard built-in
// constructor with that name
// 3. Set the [[Prototype]] internal property of obj to O.
Temp.prototype = O;
obj = new Temp ();
Temp.prototype = null; // Let's not keep a stray reference to O...
// 4. If the argument Properties is present and not undefined, add
// own properties to obj as if by calling the standard built-in
// function Object.defineProperties with arguments obj and
// Properties.
if (arguments.length > 1) {
// Object.defineProperties does ToObject on its first argument.
Properties = Object (arguments[1]);
for (prop in Properties) {
if (hasOwn.call (Properties, prop)) {
obj[prop] = Properties[prop];
}
}
}
// 5. Return obj
return obj;
};
}) ();
}
// Pollyfills for Array indexOf
// Ref: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
if (!Array.prototype.indexOf) {
/*jslint freeze: false */
Array.prototype.indexOf = function (searchElement, fromIndex) {
var k, O, len, n;
// 1. Let O be the result of calling ToObject passing
// the this value as the argument.
if (this == null) {
throw new TypeError ('"this" is null or not defined');
}
O = Object (this);
// 2. Let lenValue be the result of calling the Get
// internal method of O with the argument "length".
// 3. Let len be ToUint32 (lenValue).
len = O.length >>> 0;
// 4. If len is 0, return -1.
if (len === 0) {
return -1;
}
// 5. If argument fromIndex was passed let n be
// ToInteger (fromIndex); else let n be 0.
n = +fromIndex || 0;
if (Math.abs (n) === Infinity) {
n = 0;
}
// 6. If n >= len, return -1.
if (n >= len) {
return -1;
}
// 7. If n >= 0, then Let k be n.
// 8. Else, n<0, Let k be len - abs (n).
// If k is less than 0, then let k be 0.
k = Math.max (n >= 0 ? n : len - Math.abs (n), 0);
// 9. Repeat, while k < len
while (k < len) {
// a. Let Pk be ToString (k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the
// HasProperty internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
// i. Let elementK be the result of calling the Get
// internal method of O with the argument ToString (k).
// ii. Let same be the result of applying the
// Strict Equality Comparison Algorithm to
// searchElement and elementK.
// iii. If same is true, return k.
if (k in O && O[k] === searchElement) {
return k;
}
k++;
}
return -1;
};
}
// Pollyfills for Array forEach
// Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
if (!Array.prototype.forEach) {
// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
/*jslint freeze: false */
Array.prototype.forEach = function (callback, thisArg) {
var T, k, O, len, kValue;
if (this == null) {
throw new TypeError (' this is null or not defined');
}
// 1. Let O be the result of calling ToObject passing the |this| value as the argument.
O = Object (this);
// 2. Let lenValue be the result of calling the Get internal method of O
// with the argument "length".
// 3. Let len be ToUint32 (lenValue).
len = O.length >>> 0;
// 4. If IsCallable (callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== 'function') {
throw new TypeError (callback + ' is not a function');
}
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 1) {
T = thisArg;
}
// 6. Let k be 0
k = 0;
// 7. Repeat, while k < len
while (k < len) {
// a. Let Pk be ToString (k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty internal method of O
// with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal method of O with argument Pk.
kValue = O[k];
// ii. Call the Call internal method of callback with T as the this value and
// argument list containing kValue, k, and O.
callback.call (T, kValue, k, O);
}
// d. Increase k by 1.
k++;
}
// 8. return undefined
};
}
// Attachable label can be positioned / attached anywhere along the y axis.
// If its position is changed via shifting or providing index it auto calculates the y.
function AttachableLabel () {
this.y = 0; // Absolute position
this.lRef = undefined; // Label object itself
this.__shift = 0; // Inner properties to calculate y. Indicate relative shift.
this.__index = 0; // Inner properties to calculate y. Indicate absolute index.
}
AttachableLabel.prototype.constructor = AttachableLabel;
AttachableLabel.prototype.applyShift = function (shiftApplied) {
this.__shift = shiftApplied;
// Shift applied, now calculate the y
this.lRef.calcY = (this.y += shiftApplied * boxHeight);
};
AttachableLabel.prototype.applyDirectIndex = function (newIndex) {
this.__index = newIndex;
// Index changed along the sections of y axis. now calculate the y
this.lRef.calcY = (this.y = _top - (newIndex * boxHeight * -1));
};
/*
* An enum to determine whether the actions (shift / unoccupied position) is in the left side
* or right side.
* >----------------I----------------<
* I is the reference index from which the actions are calculated.
* Anypoint left to I is scale.NEG otherwise scale.POS
*/
try {
Object.defineProperty (scale, 'POS', {
enumerable : false,
configurable : false,
get : function () { return 1; }
});
Object.defineProperty (scale, 'NEG', {
enumerable : false,
configurable : false,
get : function () { return -1; }
});
}catch (e) {
// For almighty IE8
scale.POS = 1;
scale.NEG = -1;
}
/*
* Get an array, whose value incrementing from 0 to n-1 for index 0, n-1
* @param: n { int} The number till which increment is needed
* @return: { Array} with value populated from 0 to n-1
*/
//Array.prototype.inc = function (n) {
function inc (n) {
var counter = 0;
while (counter < n) {
this.push (counter++);
}
return this;
}
/*
* Calculates the position of nearest undefined (unoccupied) element.
* @param: pos { int} The position from where the reference to be calculated
* @return: { Object} { absValue: { int}, noScaleSide: { scale}}
*/
//Array.prototype.nearestUndefined = function (pos) {
function nearestUndefined (pos) {
var i,
diff,
_scale,
nearest = {},
lowest = Number.POSITIVE_INFINITY;
for (i = 0; i < this.length; i++) {
diff = this[i] - pos;
diff < 0 ? (_scale = scale.NEG) : (_scale = scale.POS);
diff = abs (diff);
if (diff <= lowest) {
lowest = diff;
nearest.absValue = diff;
nearest.noScaleSide = _scale;
}
}
return nearest;
}
/*
* Keeps a matrix of lables plotted virtually alreday, (not actually in DOM) and
* controls the overlapping by shifting or suggesting indexes.
* This class when initialized, is itself an array containg the plotted labels.
* There is an holes array which is of same length and keeps track of the unoccupied position.
* These two are closely related and relate like
* -------------------------
* | | | l1 | | | l2 | Is the occupency matrix itself
* -------------------------
* ---------------
* | 0 | 1 | 3 | 4 | Would be the holes.
* ---------------
* @param: count { int} - size of the matrix. Generally be the size of the sections
*/
function OccupancyMatrix (count) {
this.holes = inc.call ([], count);
}
OccupancyMatrix.prototype = Object.create (Array.prototype);
OccupancyMatrix.prototype.constructor = OccupancyMatrix;
/*
* Recalculate the state of unoccupied positions freshly.
* Removes the state before it was called
*/
OccupancyMatrix.prototype.repositionHoles = function () {
var i,
index = 0,
value;
this.holes.length = 0;
for (i = 0; i < this.length; i++) {
value = this[i];
!value && (this.holes[index++] = i);
}
};
/*
* Possition the labels and attach the shidt information along with calculeatedY on the labels.
* @param: value { int} - value of the labels, inthis case y position of the labels
* @param: index { int} - the section where it would fit normally, calculated based on his value only.
* @param: label { object} - the label object, which is to be attached. label.calcY is injected
* after calculating it correctly.
*/
OccupancyMatrix.prototype.attachShift = function (value, index, label) {
var indexVal,
length = this.length,
nearestUndefIndex,
tempArr,
attachedLabel,
_index,
calibratedIndex,
nearestOffset;
if (value === plotOut) {
// Plot outside if label is not visible
label.calcY = plotOut;
return;
}else {
calibratedIndex = (index > (length - 1)) ? length - 1 : index; // If the plot value is too low
indexVal = this[calibratedIndex];
}
attachedLabel = new AttachableLabel (); // Create a new label which is attachable
attachedLabel.y = value;
attachedLabel.lRef = label;
if (!indexVal) {
// If the position is unoccupied, go ahead and occupy it
attachedLabel.applyDirectIndex (calibratedIndex);
this.splice (calibratedIndex, 1, attachedLabel);
this.holes.splice (this.holes.indexOf (calibratedIndex), 1); // Update the occupancy matrix.
return;
}
// If position is occupied, the label needs to be opsitioned down or up
nearestOffset = nearestUndefined.call (this.holes, calibratedIndex);
// Get the absolute nearest index
nearestUndefIndex = calibratedIndex + (nearestOffset.absValue * nearestOffset.noScaleSide);
if (nearestOffset.noScaleSide === scale.POS) {
// If labels to be attached down the y axis
attachedLabel.applyDirectIndex (nearestUndefIndex);
this.splice (nearestUndefIndex, 1, attachedLabel);
this.holes.splice (this.holes.indexOf (nearestUndefIndex), 1);
return nearestUndefIndex;
}
if (nearestOffset.noScaleSide === scale.NEG) {
// If the labels to be aattached is up the y axis we need the shift of sections and
// then put it down
tempArr = this.splice (nearestUndefIndex + 1, this.length - 1); // Section to be shifted
this.pop ();
tempArr.forEach (function (element) {
// Shift the system of attachable lable
element && (element.applyShift (-1));
});
[].push.apply (this, tempArr);
_index = nearestUndefIndex;
// Find the nearest unoccupied position after shifting the index
while (true) {
if (!this[_index]) {
break;
}
_index++;
}
// This is an dummy insert for recalculation of holes since the whole system has changed.
this.push (0);
this.repositionHoles ();
// After recalculation get the nearest unoocupied position again since there might be better
// place to push the new label
nearestOffset = nearestUndefined.call (this.holes, _index);
_index += (nearestOffset.absValue * nearestOffset.noScaleSide);
attachedLabel.applyDirectIndex (_index);
// Add the label where it was suggested
this.splice (_index, 1, attachedLabel);
this.repositionHoles ();
return this.length - 1;
}
};
try {
Object.defineProperty (result, 'top', {
enumerable : false,
configurable : false,
get : function () { return _top; }
});
Object.defineProperty (result, 'bottom', {
enumerable : false,
configurable : false,
get : function () { return _bottom; }
});
}catch (e) {
// IE-8
result.top = _top;
result.bottom = _bottom;
}
/*
* Initialize the limiting system
* @param height { int} - label height
* @param labelCount { int} - total no of labels
*/
result.init = function (height, labelCount) {
var i,
defaultValue = 0;
boxHeight = height + margin;
_top += (boxHeight / 2);
sections = floor (abs (_top) / boxHeight);
if (sections >= labelCount) {
isSpaceEnough = true;
}
oMatrix = new OccupancyMatrix (sections);
for (i = 0; i < sections ; i++) {
oMatrix.push (defaultValue);
}
};
/*
* Occupy one section in the vertical scale
* @param pos { int} - value from data config
* @param label { object} - label object
*/
result.occupy = function (pos, label) {
var passedSections = floor (abs (_top - pos) / boxHeight);
oMatrix && oMatrix.attachShift (pos, passedSections, label);
};
return result;
}) ();
if (labels) {
// First set text so that we get correct value of boundingBox
labels[0] && labels[0].attr ( {
text: numberFormatter.yAxis ('0')
});
labels[0] && verticalLimits.init (labels[0].getBBox ().height, labels.length);
labels.forEach (function (label, i) {
var plot = plots[i],
value = plot.components.data[position] && plot.components.data[position].config.setValue,
labelYPos,
yAxis = plot.config.parentYAxis;
if (value === undefined || !plot.visible ||
(yAxis ? (value > sYAxisMaxValue || value < sYAxisMinValue):
(value > pYAxisMaxValue || value < pYAxisMinValue))) {
labelYPos = plotOut;
} else {
labelYPos = (yAxis ? (value - sYAxisMinValue) * spvr : (value - pYAxisMinValue) * ppvr ) ;
}
verticalLimits.occupy (labelYPos, label);
});
}
labels && labels.forEach (function (label, i) {
var plot = plots[i],
GUTTER = 10,
value = plot.components.data[position] && plot.components.data[position].config.setValue,
bBox,
labelWidth,
halfLabelWidth,
adjustedHalfLabelWidth,
labelHeight,
_xPos,
_yPos,
text = numberFormatter[plot.config.parentYAxis ? 'sYAxis' : 'yAxis'] (value);
// Only defined and valid texts are shown on the labels.
if (text) {
label.attr({
text: text
});
// Now calculate boundingBox and label positions
bBox = label.getBBox();
labelWidth = bBox && bBox.width;
halfLabelWidth = labelWidth && labelWidth * 0.5;
adjustedHalfLabelWidth = halfLabelWidth && (halfLabelWidth + GUTTER);
labelHeight = bBox && bBox.height;
_yPos = label.calcY;
_xPos = mathMax (0, mathMin (flooredX, visw));
(_yPos !== UNDEFINED && _xPos !== UNDEFINED) && (label.attr ( {
x: _xPos,
y: _yPos,
'text-anchor': (x <= adjustedHalfLabelWidth) && 'start' ||
((x + adjustedHalfLabelWidth) >= visw) && 'end' || 'middle',
'text-bound': ['rgba(255,255,255,0.8)', 'rgba(0,0,0,0.2)', 1, labelPadding]
}));
}
else {
// In case of undefined text labels, the label is transfered to out of visual. One way of hiding it.
label.attr({
x: -visw
});
}
});
crossline.positionLabel && crossline.positionLabel.attr ( {
x: x + crossline.left,
text: crossline.getPositionLabel (position),
'text-bound': ['rgba(255,255,255,1)', 'rgba(0,0,0,1)', 1, labelPadding]
});
};
CrossLine.prototype.show = function () {
if (!this.disabled) {
this.hidden = false;
this.group.attr ('visibility', 'visible');
this.line.attr ('visibility', 'visible');
this.positionLabel && this.positionLabel.attr ('visibility', 'visible');
}
};
CrossLine.prototype.hide = function () {
this.hidden = true;
this.group.attr ('visibility', 'hidden');
this.line.attr ('visibility', 'hidden');
this.positionLabel && this.positionLabel.attr ('visibility', 'hidden');
};
CrossLine.prototype.dispose = function () {
var crossline = this,
key;
// delete all the properties in crossline object
for (key in crossline) {
crossline.hasOwnProperty (key) && (delete crossline[key]);
}
};
R.addSymbol ( {
pinModeIcon: function (posx, posy, rad) {
var x = posx,
y = posy,
r = rad,
r1 = r * 0.5,
r2 = r - r1,
x1 = x - r,
x2 = x + r,
x3 = x - r1,
x4 = x + r1,
x5 = x - 0.5,
x6 = x + 0.5,
x7 = x6 + 1,
x8 = x6 + 1.5,
y1 = y - r,
y2 = y + r1,
y3 = y - r1,
y4 = y + r2,
y5 = y + r + 0.5;
return ['M', x1, y1, 'L', x3, y3, x3, y4, x1, y2, x5, y2, x, y5, x6,
y2, x2, y2, x4, y4, x4, y3, x2, y1, x8, y1, x8, y3, x8, y4, x7,
y4, x7, y3, x8, y3, x8, y1, 'Z'];
},
zoomOutIcon: function (x, y, radius) {
var
icoX = x - radius * 0.2,
icoY = y - radius * 0.2,
rad = radius * 0.8,
startAngle = R.rad (43),
endAngle = R.rad (48), // to prevent cos and sin of start and end from becoming equal on 360 arcs
startX = icoX + rad * mathCos (startAngle),
startY = icoY + rad * mathSin (startAngle),
endX = icoX + rad * mathCos (endAngle),
endY = icoY + rad * mathSin (endAngle),
handleHeight = radius, // the height of the handle
handAngle = R.rad (45),
handX1 = startX + handleHeight * mathCos (handAngle),
handY1 = startY + handleHeight * mathSin (handAngle),
handX2 = endX + handleHeight * mathCos (handAngle),
handY2 = endY + handleHeight * mathSin (handAngle),
semiW = 2;
return ['M', startX , startY,
'A', rad, rad, 0, 1, 0, endX, endY, 'Z', 'M', startX + 1 , startY + 1 , 'L',
handX1, handY1, handX2, handY2, endX + 1,
endY + 1, 'Z', 'M', icoX - semiW, icoY, 'L', icoX + semiW,
icoY, 'Z'];
},
resetIcon: function (x, y, radius) {
var r = radius,
startX = x - r, startY = y,
endAngle = (math.PI / 2 + math.PI) / 2,
endX = x + r * mathCos (endAngle),
endY = y + r * mathSin (endAngle),
arrowLength = r * 2 / 3,
paths = ['M', startX, startY, 'A',
r, r, 0, 1, 1, endX, endY, 'L', endX + arrowLength,
endY - 1, endX + 2, endY + arrowLength - 0.5, endX, endY];
return paths;
}
});
}]);
}));