mirror of
https://github.com/bendtherules/line_chart_demo.git
synced 2026-08-18 13:52:30 +00:00
11130 lines
490 KiB
JavaScript
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;
|
|
}
|
|
});
|
|
|
|
}]);
|
|
|
|
|
|
}));
|