Files
line_chart_demo/fusioncharts.maps.js

8991 lines
338 KiB
JavaScript

(function (factory) {
if (typeof module === 'object' && typeof module.exports !== "undefined") {
module.exports = factory;
} else {
factory(FusionCharts);
}
}(function (FusionCharts) {
/**!
* @license FusionCharts JavaScript Library - KDtree
* Copyright FusionCharts Technologies LLP
* License Information at <http://www.fusioncharts.com/license>
*
* @version 3.12.0
*/
/**
* @private
* @module fusioncharts.renderer.javascript.kdtree
* @export fusioncharts.kdtree.js
*/
FusionCharts.register('module', ['private', 'modules.renderer.js-kdtree', function () {
/*
* compareFunction : An optional argument for Array.Prototype.sort(). Used to change the default sorting order.
* @param a {Object} and @param b {Object} - two elements being compared during the sort.
*/
// sorts the Array w.r.t to 'x' property of the array elements in an ascending order.
var global = this,
lib = global.hcLib,
UNDEFINED,
isWithinCircle = function (x, y, cx, cy, r) {
return (Math.pow(cx - x, 2) + Math.pow(cy - y, 2) <= Math.pow(r, 2));
},
rad = function (deg) {
return deg % 360 * deg2rad;
},
mathPI = Math.PI,
mathCos = Math.cos,
mathSin = Math.sin,
mathMax = Math.max,
mathMin = Math.min,
deg2rad = mathPI / 180,
doIntersect = function (p1x, p1y, q1x, q1y, p2x, p2y, q2x, q2y) {
// Find the four orientations needed for general and
// special cases
var o1 = orientation(p1x, p1y, q1x, q1y, p2x, p2y),
o2 = orientation(p1x, p1y, q1x, q1y, q2x, q2y),
o3 = orientation(p2x, p2y, q2x, q2y, p1x, p1y),
o4 = orientation(p2x, p2y, q2x, q2y, q1x, q1y);
// General case
if (o1 !== o2 && o3 !== o4) {
return true;
}
// Special Cases
// p1, q1 and p2 are colinear and p2 lies on segment p1q1
if (o1 === 0 && onSegment(p1x, p1y, p2x, p2y, q1x, q1y)) {
return true;
}
// p1, q1 and p2 are colinear and q2 lies on segment p1q1
if (o2 === 0 && onSegment(p1x, p1y, q2x, q2y, q1x, q1y)) {
return true;
}
// p2, q2 and p1 are colinear and p1 lies on segment p2q2
if (o3 === 0 && onSegment(p2x, p2y, p1x, p1y, q2x, q2y)) {
return true;
}
// p2, q2 and q1 are colinear and q1 lies on segment p2q2
if (o4 === 0 && onSegment(p2x, p2y, q1x, q1y, q2x, q2y)) {
return true;
}
return false; // Doesn't fall in any of the above cases
},
onSegment = function (p1x, p1y, q1x, q1y, p2x, p2y) {
if (q1x <= mathMax(p1x, p2x) && q1x >= mathMin(p1x, p2x) &&
q1y <= mathMax(p1y, p2y) && q1y >= mathMin(p1y, p2y)) {
return true;
}
return false;
},
orientation = function (p1x, p1y, q1x, q1y, p2x, p2y) {
var A = q1y - p1y,
B = p2x - q1x,
C = q1x - p1x,
D = p2y - q1y,
val1,
val2,
val;
val1 = A * B;
val2 = C * D;
if (isNaN(val1)) {
val1 = 0;
}
if (isNaN(val2)) {
val2 = 0;
}
val = val1 - val2;
if (val === 0) {
return 0; // colinear
}
return (val > 0) ? 1: 2;
},
isWithinPolygon = function (x, y, cx, cy, r, sides, startAngle) {
var i,
extreme = Infinity,
angle,
count = 0,
p1x,
p2x,
p2y,
p1y,
inangle,
inside = false;
if (isWithinCircle(x, y, cx, cy, r) && sides >= 3) {
angle = startAngle === UNDEFINED ? mathPI * 0.5 : rad(startAngle);
inangle = 2 * mathPI / sides;
p1x = cx + r * mathCos(-angle);
p1y = cy + r * mathSin(-angle);
for (i = 0; i < sides; i++) {
angle += inangle;
p2x = cx + r * mathCos(-angle);
p2y = cy + r * mathSin(-angle);
if (doIntersect(p1x, p1y, p2x, p2y, x, y, extreme, y)) {
if (orientation(p1x, p1y, x, y, p2x, p2y) === 0) {
return onSegment(p1x, p1y, x, y, p2x, p2y);
}
count++;
}
p1x = p2x;
p1y = p2y;
}
inside = count % 2 !== 0;
}
return inside;
};
function kdTreeAbs (arr) {
'use strict';
// Max radius will be tolerance
var tolerance = arr && arr[0] && arr[0].r || 5,
i,
max = Math.max,
floor = Math.floor,
sqrt = Math.sqrt,
min = Math.min,
log = Math.log,
exp = Math.exp,
pow = Math.pow;
arr = arr || [];
// Find tolerance as the max radius
// of the element
for (i = arr.length; i--;) {
if (arr[i].r > tolerance) {
tolerance = arr[i].r;
}
// Setting the index
arr[i].i = i;
arr[i].x = +arr[i].x;
arr[i].y = +arr[i].y;
}
// KdTree Definition below
function buildKdTree(arr, left, right, isY){
var ob = {},
mid,
access = isY ? 'y' : 'x';
if(left === right){
ob.point = arr[left];
return ob;
}
if(right - left === 1){
if(arr[left][access] > arr[right][access]){
ob.point = arr[left];
ob.left = {
point: arr[right]
};
} else {
ob.point = arr[right];
ob.left = {
point: arr[left]
};
}
return ob;
}
mid = (left + right) >> 1;
if (isY) {
quickselectY(arr, mid, left, right);
} else {
quickselectX(arr, mid, left, right);
}
ob.point = arr[mid];
ob.left = buildKdTree(arr, left, mid - 1, !isY);
ob.right = buildKdTree(arr, mid + 1, right, !isY);
return ob;
}
function quickselectX(arr, k, left, right) {
var n, m, z, s, sd, newLeft, newRight, t, i, j;
while (right > left) {
if (right - left > 600) {
n = right - left + 1,
m = k - left + 1,
z = log(n),
s = 0.5 * exp(2 * z / 3),
sd = 0.5 * sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1),
newLeft = max(left, floor(k - m * s / n + sd)),
newRight = min(right, floor(k + (n - m) * s / n + sd));
quickselectX(arr, k, newLeft, newRight);
}
t = arr[k];
i = left;
j = right;
swap(arr, left, k);
if (arr[right].x > t.x) {
swap(arr, left, right);
}
while (i < j) {
swap(arr, i, j);
i++;
j--;
while (arr[i].x < t.x) {
i++;
}
while (arr[j].x > t.x) {
j--;
}
}
if (arr[left].x === t.x) {
swap(arr, left, j);
} else {
j++;
swap(arr, j, right);
}
if (j <= k) {
left = j + 1;
}
if (k <= j) {
right = j - 1;
}
}
}
function quickselectY(arr, k, left, right) {
var n, m, z, s, sd, newLeft, newRight, t, i, j;
while (right > left) {
if (right - left > 600) {
n = right - left + 1,
m = k - left + 1,
z = log(n),
s = 0.5 * exp(2 * z / 3),
sd = 0.5 * sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1),
newLeft = max(left, floor(k - m * s / n + sd)),
newRight = min(right, floor(k + (n - m) * s / n + sd));
quickselectY(arr, k, newLeft, newRight);
}
t = arr[k];
i = left;
j = right;
swap(arr, left, k);
if (arr[right].y > t.y) {
swap(arr, left, right);
}
while (i < j) {
swap(arr, i, j);
i++;
j--;
while (arr[i].y < t.y) {
i++;
}
while (arr[j].y > t.y) {
j--;
}
}
if (arr[left].y === t.y) {
swap(arr, left, j);
} else {
j++;
swap(arr, j, right);
}
if (j <= k) {
left = j + 1;
}
if (k <= j) {
right = j - 1;
}
}
}
function swap(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
return {
tree: (arr.length === 0) ? {} : buildKdTree(arr, 0, arr.length - 1, false),
search: function (x, y, type) {
// Check a point is in range w.r.t
// to given range
function inRange (a, b) {
if (type === 'circle') {
return calcDist(a, b, x, y) <= x1;
}
return a >= x1 && a <= x2 && b >= y1 && b <= y2;
}
function inRangeApply (a, r1, r2) {
return a >= r1 && a <= r2;
}
// Helper function for search
// to apply data if found
function apply (ob) {
var currentHovered = inRangeApply(x, ob.x1, ob.x2) && inRangeApply(y, ob.y1, ob.y2),
currentDist = calcDist(x, y, ob.point.x, ob.point.y);
if (!res) {
res = ob;
lastHovered = currentHovered;
lastDist = currentDist;
return;
}
if (currentHovered) {
if (lastHovered) {
if (ob.point.i > res.point.i) {
res = ob;
lastHovered = currentHovered;
lastDist = currentDist;
}
} else {
res = ob;
lastHovered = currentHovered;
lastDist = currentDist;
}
} else {
if (!lastHovered) {
if (currentDist < lastDist) {
res = ob;
lastHovered = currentHovered;
lastDist = currentDist;
}
}
}
}
function defineSearchArea (r) {
if (type === 'circle') {
// Defining box again
x1 = r;
} else {
x1 = x - r || 0;
x2 = x + r || 0;
y1 = y - r || 0;
y2 = y + r || 0;
}
}
// Calculate distance between two points
function calcDist (x, y, p, q) {
return sqrt(pow(x - p, 2) + pow(y - q, 2));
}
// X and Y searching different for
// maintaing performance
function searchX (ob) {
// Not found
if (!ob || !ob.point) {
return;
}
// defining search area
defineSearchArea(ob.point.r);
// If match found return
if (inRange(ob.point.x, ob.point.y)) {
apply(ob);
}
// If smaller x1 go left
if (x1 <= ob.point.x) {
searchY(ob.left);
}
// If bigger x2 goto right
if (x2 >= ob.point.x) {
searchY(ob.right);
}
}
function searchY (ob) {
// Not found
if (!ob || !ob.point) {
return;
}
// defining search area
defineSearchArea(ob.point.r);
// If match found return
if (inRange(ob.point.x, ob.point.y)) {
apply(ob);
}
// If smaller x1 go left
if (y1 <= ob.point.y) {
searchX(ob.left);
}
// If bigger x2 goto right
if (y2 >= ob.point.y) {
searchX(ob.right);
}
}
// Actual search logic
var tree = this.tree,
res,
x1 = x - tolerance,
x2 = x + tolerance,
y1 = y - tolerance,
y2 = y + tolerance,
lastHovered = false,
lastDist = 0;
searchX(tree);
// Return point otherwise undefined value
return res && res.point || res;
}
};
}
/*
* The entire data of the dataset is converted to a tree like structure(namely kdTree).
* This is very useful to traverse and extract details(e.g. for a tooltip) from a million array elements especially
* to extract information of the hovered plot on each mouseMove.
* @constructor
*/
function KdTree (shape) {
var kdTreeObj = this;
kdTreeObj.configure(shape);
}
KdTree.prototype = {
configure: function (shape) {
var kdTreeObj = this;
kdTreeObj.validatorFn = shape ? kdTreeObj.shapeValidator() : kdTreeObj.defaultValidator();
},
/*
* Check Function: To determine if the mouseMove Point and the point to be searched are within limits
* @param mousePoint {Object} - point extracted from the mouseMove.
* @return {Function} - Returns if the setElem is in limit wrt the mouseMoved point.
*/
defaultValidator: function () {
var kdTreeObj = this;
return function (setElem) {
var mousePoint = kdTreeObj.mousePoint;
//Refer to the 2-D distance formula for two points
// @returns {Boolean} - If the calculated distances are within the limits.
return setElem && (Math.pow((setElem.x - mousePoint.x) / kdTreeObj.xLimit, 2) +
Math.pow((setElem.y - mousePoint.y) / kdTreeObj.yLimit, 2) <= 1) ? true : false;
};
},
shapeValidator: function () {
var kdTreeObj = this;
return function (setElem) {
var mousePoint = kdTreeObj.mousePoint,
shapeInfo = setElem && setElem.shapeInfo,
inside = false,
type,
innerradius,
r,
x = mousePoint.x,
y = mousePoint.y,
sides,
startAngle,
x1,
x2,
y1,
y2;
type = shapeInfo && shapeInfo.type;
switch (type) {
case 'circle':
r = shapeInfo.radius;
inside = isWithinCircle(x, y, setElem.x, setElem.y, r);
break;
case 'arc':
innerradius = shapeInfo.innerradius;
r = shapeInfo.radius;
inside = !isWithinCircle(x, y, setElem.x, setElem.y, innerradius) &&
isWithinCircle(x, y, setElem.x, setElem.y, r);
break;
case 'polygon':
r = shapeInfo.radius;
startAngle = shapeInfo.startAngle;
sides = shapeInfo.sides;
inside = isWithinPolygon(x, y, setElem.x, setElem.y, r, sides, startAngle);
break;
case 'rect':
x1 = setElem.x;
y1 = setElem.y;
x2 = x1 + (shapeInfo.width || 0);
y2 = y1 + (shapeInfo.height || 0);
inside = x >= x1 && x <= x2 && y >= y1 && y <= y2;
break;
case 'default':
inside = false;
break;
}
return inside;
};
},
/*
* Builds the tree structure from the data points provided.
* @param points {Array} - The set level elements belonging to a particular dataset
* Updates the tree structure in the tree property of the current instance.
*/
buildKdTree: function (points) {
/*
* _buildKdTree is for the internal use to build the tree recursively.
* @param points {Array} - The set level elements.
* @param isX {Boolean} - isX determines the sorting order. Initially setting the isX = false ensures that
* the initial sorting order of the array is w.r.t to y - axis
*/
this.kdTree = kdTreeAbs(points);
this.tree = this.kdTree.tree;
return this;
},
/*
* Returns the nearest point to the point given.
* @param point {Object} - the point of reference in whose neighbour the point required is to be returned
* @return {Object} - The neighbouring point w.r.t to the point is hunted and returned once within limits
*/
getNeighbour : function (point, basicSearch, type) {
var kDTreeObj = this, // Instance of the kdTree class specific to indivual dataset instance.
tree = kDTreeObj.tree, // refer to the created tree like structure for that dataset instance.
// limitBox - set the limiting boundaries for the neighbour hunt of the given point.
limitBox = {
x1: point.x - kDTreeObj.xLimit,
x2: point.x + kDTreeObj.xLimit,
y1: point.y - kDTreeObj.yLimit,
y2: point.y + kDTreeObj.yLimit
},
//function specific to a paricular point that retruns Boolean if the neighbour searched is within limits
validatorFn = kDTreeObj.validatorFn;
kDTreeObj.mousePoint = point;
// If basic search is on will use the search function from the
// abstract implementation of kdTree
if (basicSearch) {
return this.kdTree && this.kdTree.search(point.x, point.y, type);
}
//When the renderring turns heavy, probably then the tree is not yet build.
// Proceed further only if the tree exists.
if (tree) {
// search the nearest points within limits and return it.
return kDTreeObj._searchBtwnLimit(limitBox, tree, true, validatorFn);
}
},
/*
* If two points are very closely spreaded, the point that was parsed later is given preference and returned
hence.
* This is important when two plots are very near, the tooltip and hover effects of the one that comes avobe is
being displayed
* @param p1 {Object} - The first point being compared.
* @param p2 {Object} - The second point being compared in close vicinity of the first point (p1).
* @return {Object} - The point that occurs later in the JSON data gets the preference.
*/
_compair2closest: function (p1, p2) {
if (!p1 || p1 && p2 && p2.i > p1.i) {
return p2;
}
return p1;
},
/*
* Search and return the nearest neighbouring point in the pre defined limits.
* @param limitBox {Object} - Boundary conditions for the tolerance for the neighbourhood search.
* @param tree {Object} - The tree within which the point is needed to be searched,
* @param isX {Boolean} - Flag representative of the axis. This gets swapped everytime the function is called
recursively
* @param validatorFn {Function} - Returns if the point is within the limits.
* @return returnPoint {Object} - neighbouring point.
*/
_searchBtwnLimit : function(limitBox, tree, isX, validatorFn) {
var point,
returnPoint,
kdTreeObj = this,
axis = isX ? 'x': 'y',
// get the lower and upper limits in accordance to the axis being referenced.
lowerLimit = isX ? limitBox.x1 : limitBox.y1,
upperLimit = isX ? limitBox.x2 : limitBox.y2;
//Proceed further only when tree is defined
point = tree && tree.point && tree.point[axis];
if (point === undefined) {
return;
}
// point1 is within limit;
if (validatorFn(tree.point)) {
//In case of two closely spaced points, they are chosen preferably using _compair2closest()
returnPoint = tree.point;
}
// search the left side if required
if (point >= lowerLimit && tree.left) {
// if left is available then search on left
returnPoint = kdTreeObj._compair2closest(returnPoint,
kdTreeObj._searchBtwnLimit(limitBox, tree.left, !isX, validatorFn));
}
// search the right side if required
if (point <= upperLimit && tree.right) {
// if left is available then search on left
returnPoint = kdTreeObj._compair2closest(returnPoint,
kdTreeObj._searchBtwnLimit(limitBox, tree.right, !isX, validatorFn));
}
return returnPoint;
},
/*
* Sets the tolerance limit for kdTree search after every zoom level
* @param xLimit {Number} - The limiting tolerance in the x-axis
* @param yLimit {Number} - The limiting tolerance in the y-axis
*/
_setSearchLimit : function (xLimit,yLimit) {
var kdTree = this;
//sets the x and y limits.
kdTree.xLimit = xLimit;
kdTree.yLimit = yLimit;
}
};
//reset the constructor
KdTree.prototype.constructor = KdTree;
lib.KdTree = KdTree;
/* jshint ignore:start */
}]);
/**
* @private
* @module fusioncharts.renderer.javascript.legend-gradient
*/
FusionCharts.register('module', ['private', 'modules.renderer.js-gradientlegend', function() {
var global = this,
lib = global.hcLib,
pluckNumber = lib.pluckNumber,
pluck = lib.pluck,
toRaphaelColor = lib.toRaphaelColor,
graphics = lib.graphics,
dehashify = lib.dehashify,
hashify = lib.hashify,
convertColor = graphics.convertColor,
RGBtoHex = graphics.RGBtoHex,
HEXtoRGB = graphics.HEXtoRGB,
getLightColor = graphics.getLightColor,
getValidColor = graphics.getValidColor,
compositionKeys = {},
isIE = lib.isIE,
TRACKER_FILL = 'rgba(192,192,192,' + (isIE ? 0.002 : 0.000001) + ')',
legendManager,
DEF_COLOR = lib.COLOR_BLACK,
FORMER_SLIDER_INDEX = false,
LATER_SLIDER_INDEX = true,
PERCENT_STR = '%',
COMMA_STR = ',',
hasOwnProp = ({}).hasOwnProperty,
M = 'M',
L = 'L',
animType = 'easeIn',
componentPoolFactory,
universalPool = {};
// Utility functions
/*
* Recursively merges two objects.
* Source is object from where the properties will be copied and sink is destination object where the props are to
* be copied. If the property is already present in sink, it wont copy the property.
*
* Example: After merging the sink will be changed and returned
* src = { | sink = { | sink = {
* a: 1, | b: 33, | a: 1,
* b: 2, | c: 44, | b: 33,
* obj1: { | obj2: { | c: 44,
* m: 1, | w: 11, | obj1: {
* n: 2 | x: 22 | m: 1,
* }, | } | n: 2
* obj2: { | | },
* x: 1, | | obj2: {
* y: 2, | | w: 11,
* z: 3 | | x: 22,
* } | | y: 2,
* } | | z: 3
* | | }
* | | }
*
* @param: source {Object} - The object from where the props are to be copied
* @param: sink {Object} - The object where the props are to be merged
* @return {Object} - The reference to the sink object which was passed
*/
function merge (source, sink) {
(function rec (source, sink) {
var sourceVal,
prop;
for (prop in source) {
// Iterates for every property in souce
if (!hasOwnProp.call(source, prop)) {
// Igoners if the property does not belong to the object directly, it might resides on the prototype
// chain
continue;
}
sourceVal = source[prop];
if (sink[prop] === undefined) {
// Assigns the value / ref if the value of the same property is undefined (it's not checked whether
// the key is directly or indirectly present on the object) in the sink.
sink[prop] = sourceVal;
} else if (typeof sourceVal === 'object' && typeof sourceVal !== null) {
// If the value is another object recursively perform the merging.
// @todo check if the value is function, array etc. If it happens this will execute unpredictively,
// Its not implmented because it would more condition.
rec(sourceVal, sink[prop]);
}
}
})(source, sink);
return sink;
}
/*
* Takes care of sanity checking of the color to some extent.
* If no color is passed, ie called as getValidHexColor() it returns the default color. If incorrect color is passed
* it returns false.
*
* @param code {String} - color code in string
* @return {String | Boolean} - false if invalid color is passed, otherwise the same color or default color if no
* no color is passed
*/
function getValidHexColor (code) {
var color = code ? code : DEF_COLOR;
return getValidColor(color) || DEF_COLOR;
}
/*
* Returns the opposite LIGHT color of a color. If the color is alraedy light, wont get much difference.
*
* @param code {String} - color code in string
* @return {String} - Lightest (approx) color code of the color sent
*/
function getOppositeColor (code) {
return getLightColor(code, 1);
}
function getColorBetween(range1, range2, value) {
/*jshint newcap: false */
var value1 = range1.value,
code1 = range1.code,
rgb1 = HEXtoRGB(code1),
value2 = range2.value,
code2 = range2.code,
rgb2 = HEXtoRGB(code2),
diff,
rgb;
diff = value2 - value1;
rgb = [
Math.round(rgb1[0] + (((rgb2[0] - rgb1[0]) / diff) * (value - value1))),
Math.round(rgb1[1] + (((rgb2[1] - rgb1[1]) / diff) * (value - value1))),
Math.round(rgb1[2] + (((rgb2[2] - rgb1[2]) / diff) * (value - value1)))
];
return RGBtoHex(rgb);
/*jshint newcap: true */
}
function normalizeFontSizeAppend(obj) {
var fontSize = obj.fontSize + '',
normalizeFontSize;
if (!fontSize) { return obj; }
normalizeFontSize = fontSize.replace(/(\d+)(px)*/, '$1px');
obj.fontSize = normalizeFontSize;
return obj;
}
function isInvalid(arg){
if (arg === undefined || typeof arg === 'undefined' || arg === null) {
return true;
} else if (arg !== arg) {
return true;
}
return false;
}
compositionKeys.CAPTION = 'CAPTION';
compositionKeys.LEGEND_BODY = 'LEGEND_BODY';
compositionKeys.AXIS_LABEL = 'LEGEND_LABEL';
compositionKeys.LEGEND_AXIS = 'LEGEND_AXIS';
compositionKeys.RANGE = 'RANGE';
compositionKeys.AXIS_VALUE = 'AXIS_VALUE';
legendManager = (function () {
var layers,
components,
chart,
defaultConf = {},
config;
defaultConf.legendCarpetConf = {
spreadFactor : 0.85,
allowDrag: false,
captionAlignment: 'center',
padding: {
v: 3,
h: 3
},
style : {
'fill' : '#e4d9c1',
'stroke' : '#c4b89d'
}
};
defaultConf.legendCaptionConf = {
spreadFactor: 0.2,
padding: {
v: 2,
h: 2
},
style : {
fill: '#786B50',
fontFamily: 'sans-serif',
fontSize: '12px',
fontWeight: 'bold',
fontStyle: 'normal'
},
bound: {
style: {
stroke: 'none'
}
}
};
defaultConf.legendBodyConf = {
spreadFactor: 0.8,
padding: {
v: 2,
h: 2
},
bound: {
style: {
stroke: 'none'
}
}
};
defaultConf.legendAxisConf = {
legendAxisHeight: 11,
spreadFactor: 0.4,
padding: {
v: 1,
h: 1
},
style : {
stroke: 'none',
'stroke-opacity': 0,
'stroke-width': 1
},
line: {
grooveLength: 3,
offset: 8,
style: {
stroke: 'rgba(255, 255, 255, 0.65)',
'stroke-width': 1.5
}
},
shadow : {
style : {
stroke : 'none',
fill : toRaphaelColor({
FCcolor: {
alpha : '25,0,0',
angle : 360,
color : '000000,FFFFFF,FFFFFF',
ratio : '0,30,40'
}
})
}
},
bound: {
style: {
stroke: 'none'
}
}
};
defaultConf.sliderGroupConf = {
showTooltip: 1,
outerCircle : {
rFactor: 1.4,
style: {
fill: TRACKER_FILL,
stroke: '#757575',
'stroke-width': 3
}
},
innerCircle : {
rFactor: 0.65,
style: {
fill: TRACKER_FILL,
stroke: '#FFFFFF'
}
}
};
defaultConf.axisTextItemConf = {
spreadFactor: 0.3,
padding: {
v: 1,
h: 1
},
style : {
fill: '#786B50',
fontFamily: 'sans-serif',
fontSize: '12px',
fontWeight: 'normal',
fontStyle: 'normal'
}
};
function normalizePreprocessedData (confArr) {
var numberFormatter = components.numberFormatter,
index,
length,
rawVal;
for (index = 0, length = confArr.length; index < length; index++) {
rawVal = confArr[index].maxvalue;
if (!rawVal) {
continue;
}
confArr[index].maxvalue = numberFormatter.getCleanValue(rawVal);
}
}
return {
init : function (options) {
chart = options.chart;
layers = chart.graphics;
components = chart.components;
},
setConf : function (conf) {
config = conf;
},
legacyDataParser : function (data, extremes) {
var colormanagerConf = {},
numberFormatter = components.numberFormatter,
colorConfArr,
colorConf,
startColor,
endColor,
index,
validColor,
length,
colorRange,
value,
dispValue,
mapByPercent,
isMaxValPresent,
obj;
if (!data) {
return false;
}
colormanagerConf.mapByPercent = mapByPercent = !!(pluckNumber(data.mapbypercent, 0));
colorConfArr = data.color || [];
if (data.minvalue === undefined) {
data.minvalue = extremes.min !== undefined ? (mapByPercent ? 0 : extremes.min) : 0;
}
if (data.maxvalue === undefined) {
data.maxvalue = extremes.max !== undefined ? (mapByPercent ? 100 : extremes.max) : 100;
}
isMaxValPresent = false;
for (index = 0, length = colorConfArr.length; index < length; index++) {
if (colorConfArr[index].maxvalue) {
isMaxValPresent = true;
break;
}
}
if (!isMaxValPresent) {
colorConfArr = [];
}
startColor = data.code;
colorRange = colormanagerConf.colorRange = [];
colormanagerConf.gradient = !!pluckNumber(data.gradient, 1);
// If no additional color array is provided as part of generating the gradient, creates the start and
// end value to create the gradient
if (!colorConfArr.length) {
if (startColor) {
// If start color is mentioned, create a gradient starting from default color to the mentioned
// color
endColor = getValidHexColor(startColor);
startColor = getValidHexColor();
} else {
// If no color is mentioned, create a gradient of two opposite color
startColor = getValidHexColor();
endColor = getOppositeColor(startColor);
}
colorConfArr.push({
code: endColor,
maxvalue: data.maxvalue,
label: undefined
});
} else {
startColor = getValidHexColor(startColor);
}
normalizePreprocessedData(colorConfArr);
colorConfArr = colorConfArr.sort(function (m, n) {
return m.maxvalue - n.maxvalue;
});
value = dispValue = data.minvalue && numberFormatter.getCleanValue(data.minvalue);
dispValue = (value !== undefined || value !== null) && (mapByPercent ? value + PERCENT_STR :
numberFormatter.legendValue(value));
colorRange.push({
code: dehashify(startColor),
value: value,
displayValue: dispValue,
label: data.startlabel
});
for (index = 0, length = colorConfArr.length; index < length; index++) {
colorConf = colorConfArr[index];
validColor = getValidHexColor(colorConf.code || colorConf.color);
value = dispValue = colorConf.maxvalue;
if(isNaN(parseInt(value, 10))) { continue; }
dispValue = (value !== undefined || value !== null) && (mapByPercent ? value + PERCENT_STR :
numberFormatter.legendValue(value));
colorRange.push(obj = new Object({
code: dehashify(validColor),
value: value,
displayValue: dispValue,
label: colorConf.label || colorConf.displayvalue
}));
}
colorRange[colorRange.length - 1].label = data.endlabel || colorConf.label;
return colormanagerConf;
},
getDefaultConf: function (key) {
return defaultConf[key];
}
};
})();
componentPoolFactory = function (chart) {
var chartId = chart.chartInstance.id,
pool = universalPool[chartId] || (universalPool[chartId] = {});
return (function () {
var elemTypes,
actions = {},
paper;
elemTypes = {
KEY_RECT : 'rect',
KEY_TEXT : 'text',
KEY_GROUP: 'group',
KEY_CIRCLE: 'circle',
KEY_PATH: 'path'
};
actions[elemTypes.KEY_RECT] = function (group) {
return paper.rect(group);
};
actions[elemTypes.KEY_TEXT] = function (attr, group) {
return paper.text(attr, group);
};
actions[elemTypes.KEY_GROUP] = function (groupName, parentGroup) {
return paper.group(groupName, parentGroup);
};
actions[elemTypes.KEY_CIRCLE] = function (group) {
return paper.circle(group);
};
actions[elemTypes.KEY_PATH] = function (pathStr, group) {
return paper.path(pathStr, group);
};
function _hideRecursive () {
var keyLevel1,
keyLevel2,
objLevel1,
val,
index, length;
for (keyLevel1 in pool) {
objLevel1 = pool[keyLevel1];
for (keyLevel2 in objLevel1) {
val = objLevel1[keyLevel2];
if (val instanceof Array) {
for (index = 0, length = val.length; index < length; index++) {
val[index] && val[index].hide();
}
} else {
val.hide();
}
}
}
}
return {
init: function (renderer) {
paper = renderer;
_hideRecursive();
},
emptyPool: function () {
pool = universalPool[chartId] = {};
},
getChart: function () {
return chart;
},
getComponent: function (id, elemType, storeTillReCall) {
var idSpecificPool = pool[id],
multiInstance,
instance,
inst,
instanceRetrieved = 0;
if (!idSpecificPool) {
idSpecificPool = pool[id] = {};
}
instance = idSpecificPool[elemType];
if ((instance && !(instance instanceof Array)) ||
(instance instanceof Array && instance.length > 0)) {
return function () {
if (storeTillReCall) {
inst = instance[instanceRetrieved++];
if (inst) {
return inst.show();
} else {
return instance[instanceRetrieved] = actions[elemType].apply(this, arguments);
}
}
return instance.show();
};
}
return function () {
if (storeTillReCall) {
multiInstance = idSpecificPool[elemType] || (idSpecificPool[elemType] = []);
instance = actions[elemType].apply(this, arguments);
multiInstance.push(instance);
return instance.show();
}
return idSpecificPool[elemType] = actions[elemType].apply(this, arguments);
};
},
hideAll: function () {
_hideRecursive();
},
getKeys: function () {
return elemTypes;
}
};
})();
};
function LegendBase (carpet, componentPool) {
this.carpet = carpet;
this._componentPool = componentPool;
}
LegendBase.prototype.constructor = LegendBase;
LegendBase.prototype.draw = function (options) {
options.componentPool = this._componentPool;
return this.carpet.draw(options);
};
LegendBase.prototype.getLogicalSpace = function (options, recalculate) {
options.componentPool = this._componentPool;
return this.carpet.getLogicalSpace(options, recalculate);
};
LegendBase.prototype.dispose = function () {
this.carpet && this.carpet.group && this.carpet.group.remove();
this._componentPool.emptyPool();
};
function LegendCarpet (conf) {
this.conf = conf;
this._id = 'GL_CARPET';
// Save the components which are the building block of the whole legend. Like: The background rect,
// text, slider, color axis etc.
this.compositionsByCategory = {};
this.node = undefined;
this.group = undefined;
this._lSpace = undefined;
this.autoRecalculate = false;
this.groupName = 'fc-gradient-legend';
this.moveInstructions = {};
}
LegendCarpet.prototype.constructor = LegendCarpet;
LegendCarpet.prototype.addCompositions = function (instance, category) {
this.compositionsByCategory[category] = instance;
};
LegendCarpet.prototype.getBoundingBox = function (options) {
var conf = this.conf,
spreadFactor = conf.spreadFactor,
refSide = options.refSide,
alignment = options.alignment,
refOffset = options.refOffset,
x = options.x,
y = options.y,
lWidth;
lWidth = conf.width = refSide * spreadFactor;
if (alignment && (x === undefined || x === null)) {
x = ((refOffset + refSide) / 2) - lWidth / 2;
}
return {
width: lWidth,
height: options.maxOtherSide,
x: x,
y: y
};
};
LegendCarpet.prototype.getPostCalcDecisions = function (bBox, componentsArea) {
var conf = this.conf,
padding = conf.padding,
cat,
totalHeightTaken = 0;
for (cat in componentsArea) {
totalHeightTaken += componentsArea[cat].height || 0;
}
bBox.height = totalHeightTaken + 2 * padding.v;
};
LegendCarpet.prototype.getLogicalSpace = function (options, recalculate) {
var lSpace = this._lSpace,
conf = this.conf,
padding = conf.padding,
compositionsByCategory = this.compositionsByCategory,
composition,
bBox,
effectivePlotArea,
compositionLSPace,
compositionHeight = 0,
componentsArea = { },
category,
compositionPlotArea,
autoRecalculate,
heightNotUsed = 0;
if (lSpace && !recalculate) {
lSpace.isImpure = true;
// If logical space has already been calculated and recalculate flag is on, return without recalculating.
return lSpace;
} else {
autoRecalculate = false;
}
lSpace = this._lSpace = bBox = this.getBoundingBox(options);
if (isInvalid(lSpace.x) || isInvalid(lSpace.y) || isInvalid(lSpace.height) || isInvalid(lSpace.width)) {
this.autoRecalculate = true;
}
// Copy the props
effectivePlotArea = merge(bBox, {});
// The logical rect inside which the composition are drawn
effectivePlotArea.height -= 2 * padding.v;
effectivePlotArea.width -= 2 * padding.h;
effectivePlotArea.x += padding.h;
effectivePlotArea.y += padding.v;
for (category in compositionsByCategory) {
composition = compositionsByCategory[category];
compositionPlotArea = merge(effectivePlotArea, {});
compositionPlotArea.y += compositionHeight;
compositionHeight = effectivePlotArea.height * composition.conf.spreadFactor;
compositionPlotArea.height = compositionHeight + heightNotUsed;
compositionLSPace = composition.getLogicalSpace(merge(compositionPlotArea, {}), options, recalculate);
heightNotUsed = compositionPlotArea.height - compositionLSPace.height;
componentsArea[category] = compositionLSPace;
compositionHeight = compositionLSPace.height;
}
this.getPostCalcDecisions(bBox, componentsArea);
this._lSpace = bBox;
return bBox;
};
LegendCarpet.prototype.setupDragging = function () {
var group = this. group,
dx = 0,
dy = 0,
idx = 0,
idy = 0;
group.css({cursor: 'move'});
group.drag(function (_dx, _dy) {
dx = _dx;
dy = _dy;
group.attr({
transform: 't' + (idx + dx) + ',' + (idy + dy)
});
}, function () {
idx += dx;
idy += dy;
}, function () { });
};
LegendCarpet.prototype.draw = function (options) {
var conf = this.conf,
compositionsByCategory = this.compositionsByCategory,
paper = options.paper,
parentGroup = options.parentGroup,
componentPool = options.componentPool,
chart = componentPool.getChart(),
group,
node,
category,
composition,
lSpace,
compositionRes,
animDuration = chart.get('config', 'animationObj').duration,
instanceFn,
keys = componentPool.getKeys();
this.getLogicalSpace(options, this.autoRecalculate);
lSpace = this._lSpace;
instanceFn = componentPool.getComponent(this._id, keys.KEY_GROUP);
this.group = group = instanceFn(this.groupName, parentGroup);
group.attr({
opacity: 0
});
group.animate({
opacity: 1
}, animDuration, animType);
// The main rect outside all the composition
instanceFn = componentPool.getComponent(this._id, keys.KEY_RECT);
this.node = node = instanceFn(group).attr(lSpace).css(conf.style);
for (category in compositionsByCategory) {
composition = compositionsByCategory[category];
compositionRes = composition.draw(conf.captionAlignment, lSpace, {
colorRange: options.colorRange,
numberFormatter: options.numberFormatter,
paper: paper,
parentLayer: group,
smartLabel: options.smartLabel,
moveInstructions : this.moveInstructions[category],
componentPool: options.componentPool
});
}
conf.allowDrag && this.setupDragging();
return this.node;
};
function VerticalLegendCarpet () {
LegendCarpet.apply(this, arguments);
}
VerticalLegendCarpet.prototype = Object.create(LegendCarpet.prototype);
VerticalLegendCarpet.prototype.constructor = VerticalLegendCarpet;
VerticalLegendCarpet.prototype.getBoundingBox = function (options) {
var conf = this.conf,
spreadFactor = conf.spreadFactor,
refSide = options.refSide,
alignment = options.alignment,
refOffset = options.refOffset,
x = options.x,
y = options.y,
lHeight;
lHeight = conf.height = refSide * spreadFactor;
if (alignment && (y === undefined || y === null)) {
y = ((refOffset + refSide) / 2) - lHeight / 2;
}
return {
width: options.maxOtherSide,
height: lHeight,
x: x,
y: y
};
};
VerticalLegendCarpet.prototype.getPostCalcDecisions = function (bBox, componentsArea) {
var conf = this.conf,
padding = conf.padding,
maxWidth = Number.NEGATIVE_INFINITY,
width,
cat,
move = this.moveInstructions,
diff;
LegendCarpet.prototype.getPostCalcDecisions.apply(this, arguments);
for (cat in componentsArea) {
width = componentsArea[cat].width;
maxWidth = maxWidth < width ? width : maxWidth;
}
bBox.width = maxWidth + 2 * padding.h;
for (cat in componentsArea) {
width = componentsArea[cat].width;
if (diff = maxWidth - width) {
move[cat] = 't' + diff / 2 + ',0';
}
}
};
function LegendCaption (text, conf) {
this.rawText = text;
this.conf = conf;
this._id = 'GL_CAPTION';
this.node = undefined;
this.bound = undefined;
this._lSpace = undefined;
}
LegendCaption.prototype.constructor = LegendCaption;
LegendCaption.LEFT = {
x: function (smartText, boundingBox) {
return boundingBox.x + smartText.width / 2 + 2;
}
};
LegendCaption.RIGHT = {
x: function (smartText, boundingBox) {
return boundingBox.x + boundingBox.width - smartText.width / 2 - 2;
}
};
LegendCaption.CENTER = {
x: function (smartText, boundingBox) {
return boundingBox.x + (boundingBox.width / 2);
}
};
LegendCaption.prototype.getLogicalSpace = function (bBox, options, recalculate) {
var conf = this.conf,
padding = conf.padding,
lSpace = this._lSpace,
text = this.rawText,
componentPool = options.componentPool,
chart = componentPool.getChart(),
smartLabel,
effectivePlotArea,
smartText,
copyOfStyle;
if (lSpace && !recalculate) {
lSpace.isImpure = true;
// If logical space has already been calculated and recalculate flag is on, return without recalculating.
return lSpace;
}
lSpace = this._lSpace = {
bound: {
height: 0,
width: 0
},
node: {
logicArea: undefined,
smartText: undefined
}
};
smartLabel = options.smartLabel;
if (!text) {
return lSpace.bound;
}
effectivePlotArea = merge(bBox, {});
// The logical rect inside which the composition are drawn
effectivePlotArea.height -= 2 * padding.v;
effectivePlotArea.width -= 2 * padding.h;
effectivePlotArea.x += padding.h;
effectivePlotArea.y += padding.v;
smartLabel.useEllipsesOnOverflow(chart.config.useEllipsesWhenOverflow);
copyOfStyle = merge(this.conf.style, {});
normalizeFontSizeAppend(copyOfStyle);
smartLabel.setStyle(this._metaStyle = copyOfStyle);
smartText = smartLabel.getSmartText(text, effectivePlotArea.width, effectivePlotArea.height);
effectivePlotArea.height = smartText.height;
effectivePlotArea.width = smartText.width;
bBox.height = smartText.height + 2 * padding.v;
bBox.width = smartText.width + 2 * padding.h;
lSpace.node.smartText = smartText;
lSpace.node.logicArea = effectivePlotArea;
lSpace.bound = bBox;
return bBox;
};
LegendCaption.prototype.draw = function () {
var conf = this.conf,
smartLabel,
paper,
layer,
boundAttr = conf.bound || {},
group,
bound,
instanceFn,
boundingArea,
lSpace,
normalizedX,
x,
bBox,
options,
node,
componentPool,
keys;
if (arguments.length >= 3) {
x = arguments[0];
bBox = arguments[1];
options = arguments[2];
} else if (arguments.length >= 2){
x = arguments[0];
options = arguments[1];
}
smartLabel = options.smartLabel;
paper = options.paper;
layer = options.parentLayer;
componentPool = options.componentPool;
keys = componentPool.getKeys();
// Separate group for caption and the bounding rect
instanceFn = componentPool.getComponent(this._id, keys.KEY_GROUP);
this.group = group = instanceFn('legend-caption', layer).css(conf.style);
//group = paper.group('legend-caption', layer);
this.getLogicalSpace(bBox, options);
lSpace = this._lSpace;
node = lSpace.node;
boundingArea = lSpace.bound;
// Bounding rect
instanceFn = componentPool.getComponent(this._id, keys.KEY_RECT);
this.bound = bound = instanceFn(group).attr(boundingArea).css(boundAttr.style);
//bound = this.bound = paper.rect(boundingArea, group).css(boundAttr.style);
normalizedX = typeof x === 'string' ? LegendCaption[x.toUpperCase()].x(node.smartText, bBox || node.logicArea)
: x;
instanceFn = componentPool.getComponent(this._id, keys.KEY_TEXT);
this.node = instanceFn({ }, group).attr({
text: node.smartText.text,
x: normalizedX,
y: node.logicArea.y + (node.smartText.height / 2),
lineHeight: this._metaStyle.lineHeight,
fill: conf.style.fill
});
return {
group: group,
bound: bound,
node: this.node
};
};
function LegendBody (colorRange, conf, childTextConf) {
this.colorRange = colorRange;
this.conf = conf;
this.childTextConf = childTextConf;
this._id = 'GL_BODY';
this.bound = undefined;
this.compositionsByCategory = {};
this._lSpace = undefined;
}
// Space calculation order
LegendBody.SC_STACK = [
compositionKeys.AXIS_LABEL,
compositionKeys.LEGEND_AXIS,
compositionKeys.AXIS_VALUE
];
LegendBody.DARW_STACK = [
compositionKeys.AXIS_VALUE,
compositionKeys.LEGEND_AXIS,
compositionKeys.AXIS_LABEL
];
LegendBody.prototype.constructor = LegendBody;
LegendBody.prototype.addCompositions = function (instance, category) {
this.compositionsByCategory[category] = instance;
};
LegendBody.prototype.getCompositionPlotAreaFor = function (effectivePlotArea) {
var plotArea;
plotArea = merge(effectivePlotArea, {});
return function (compositionAreaOffset, sf) {
compositionAreaOffset = compositionAreaOffset || {};
plotArea.y += compositionAreaOffset.height || 0;
plotArea.height = effectivePlotArea.height * sf;
return plotArea;
};
};
LegendBody.prototype.getSpaceTaken = function (spaceObj) {
return spaceObj.height;
};
LegendBody.prototype.updateEffectivePlotArea = function (bBox, effectivePlotArea, val) {
var conf = this.conf,
padding = conf.padding;
effectivePlotArea.height = val;
bBox.height = val + 2 * padding.v;
};
LegendBody.prototype.getLogicalSpace = function (bBox, options, recalculate) {
var lSpace = this._lSpace,
conf = this.conf,
padding = conf.padding,
compositionsByCategory = this.compositionsByCategory,
composition,
compositionPlotArea,
compositionAreaOffset,
effectivePlotArea,
getCompositionPlotArea,
spaceTaken = 0,
index,
length;
if (lSpace && !recalculate) {
lSpace.isImpure = true;
// If logical space has already been calculated and recalculate flag is on, return without recalculating.
return lSpace;
}
lSpace = this._lSpace = {
bound: {
height: 0,
width: 0
},
node: {
logicArea: undefined
}
};
effectivePlotArea = merge(bBox, {});
// The logical rect inside which the composition are drawn
effectivePlotArea.height -= 2 * padding.v;
effectivePlotArea.width -= 2 * padding.h;
effectivePlotArea.x += padding.h;
effectivePlotArea.y += padding.v;
getCompositionPlotArea = this.getCompositionPlotAreaFor(effectivePlotArea);
options.colorRange = this.colorRange;
for (index = 0, length = LegendBody.SC_STACK.length; index < length; index++) {
if (!(composition = compositionsByCategory[LegendBody.SC_STACK[index]])) {
continue;
}
compositionPlotArea = getCompositionPlotArea(compositionAreaOffset, composition.conf.spreadFactor);
compositionAreaOffset = composition.getLogicalSpace(merge(compositionPlotArea, {}), options, recalculate);
spaceTaken += this.getSpaceTaken(compositionAreaOffset);
}
this.updateEffectivePlotArea(bBox, effectivePlotArea, spaceTaken);
lSpace.node.logicArea = effectivePlotArea;
lSpace.bound = bBox;
return bBox;
};
LegendBody.prototype.draw = function () {
var childTextConf = this.childTextConf,
conf = this.conf,
boundStyle = conf.bound.style || {},
compositionsByCategory = this.compositionsByCategory,
paper,
layer,
bound,
colorRange,
composition,
legendBodyGroup,
bBox,
componentPool,
lSpace,
x,
options,
index,
length,
instanceFn,
keys;
if (arguments.length >= 3) {
x = arguments[0];
bBox = arguments[1];
options = arguments[2];
} else if (arguments.length >= 2){
x = arguments[0];
options = arguments[1];
}
paper = options.paper;
layer = options.parentLayer;
colorRange = options.colorRange;
componentPool = options.componentPool;
keys = componentPool.getKeys();
this.getLogicalSpace(bBox, options);
lSpace = this._lSpace;
// debugger
instanceFn = componentPool.getComponent(this._id, keys.KEY_GROUP);
legendBodyGroup = instanceFn('legend-body', layer).attr({
transform: 't0,0'
}).css(childTextConf.style);
//legendBodyGroup = paper.group('legend-body', layer);
instanceFn = componentPool.getComponent(this._id, keys.KEY_RECT);
this.bound = bound = instanceFn(legendBodyGroup).attr(lSpace.bound).css(boundStyle);
//bound = this.bound = paper.rect(lSpace.bound, legendBodyGroup).css(boundStyle);
options.colorRange = this.colorRange;
options.parentLayer = legendBodyGroup;
for (index = 0, length = LegendBody.DARW_STACK.length; index < length; index++) {
if (!(composition = compositionsByCategory[LegendBody.DARW_STACK[index]])) {
continue;
}
composition.draw(options);
}
if (options.moveInstructions) {
legendBodyGroup.attr({
transform: options.moveInstructions
});
}
return {
bound: bound,
group: legendBodyGroup
};
};
function VerticalLegendBody () {
LegendBody.apply(this, arguments);
}
VerticalLegendBody.prototype = Object.create(LegendBody.prototype);
VerticalLegendBody.prototype.constructor = VerticalLegendBody;
VerticalLegendBody.prototype.getCompositionPlotAreaFor = function (effectivePlotArea) {
var plotArea;
plotArea = merge(effectivePlotArea, {});
return function (compositionAreaOffset, sf) {
compositionAreaOffset = compositionAreaOffset || {};
plotArea.x += compositionAreaOffset.width || 0;
plotArea.width = effectivePlotArea.width * sf;
return plotArea;
};
};
VerticalLegendBody.prototype.updateEffectivePlotArea = function (bBox, effectivePlotArea, val) {
var conf = this.conf,
padding = conf.padding;
effectivePlotArea.width = val;
bBox.width = val + 2 * padding.h;
};
VerticalLegendBody.prototype.getSpaceTaken = function (spaceObj) {
return spaceObj.width;
};
function LegendLabels(conf) {
this.conf = conf;
this._id = 'GL_LABELS';
}
LegendLabels.prototype.constructor = LegendLabels;
LegendLabels.prototype.getEffectivePlotArea = function (area) {
var conf = this.conf,
padding = conf.padding;
// The logical rect inside which the composition are drawn
area.height -= 2 * padding.v;
area.width -= 2 * padding.h;
area.x += padding.h;
area.y += padding.v;
this.node = [];
return area;
};
LegendLabels.prototype.getLogicalSpace = function (bBox, options, recalculate) {
var lSpace = this._lSpace,
conf = this.conf,
padding = conf.padding,
cRange,
smartLabel,
crDataObj,
index,
length,
labelHeights = [],
leftBound,
rightBound,
plotArea,
label,
valueRatio,
stop,
zerothStop,
lsTexts,
maxHeight,
effectivePlotArea,
testSmartLabel,
noOfLabels = 0,
nextRefPoint,
currPoint,
leftStop,
smartText,
ni,
copyOfStyle,
componentPool = options.componentPool,
chart = componentPool.getChart(),
normalizedDataArr = [];
if (lSpace && !recalculate) {
lSpace.isImpure = true;
// If logical space has already been calculated and recalculate flag is on, return without recalculating.
return lSpace;
}
cRange = options.colorRange;
smartLabel = options.smartLabel;
valueRatio = cRange.getCumulativeValueRatio();
crDataObj = cRange.colorRange;
lSpace = this._lSpace = {
bound: {
height: 0,
width: 0
},
node: {
logicArea: undefined,
smartTexts: []
}
};
lsTexts = lSpace.node.smartTexts;
plotArea = merge(bBox, {});
// The logical rect inside which the composition are drawn
effectivePlotArea = this.getEffectivePlotArea(plotArea);
smartLabel.useEllipsesOnOverflow(chart.config.useEllipsesWhenOverflow);
copyOfStyle = merge(conf.style, {});
normalizeFontSizeAppend(this._metaStyle = copyOfStyle);
smartLabel.setStyle(copyOfStyle);
testSmartLabel = smartLabel.getSmartText('W');
for (index = 0, length = crDataObj.length; index < length; index++) {
label = crDataObj[index].label;
if (!label) {
lsTexts[index] = undefined;
continue;
}
noOfLabels++;
normalizedDataArr.push({
oriIndex: index,
label: label
});
}
length = normalizedDataArr.length;
if (length === 0) {
return {
height: 0,
width: 0
};
}
if (length > 1) {
stop = ((valueRatio[normalizedDataArr[length - 1].oriIndex] -
valueRatio[normalizedDataArr[0].oriIndex]) / 2) * effectivePlotArea.width / 100;
} else {
stop = Math.max(valueRatio[normalizedDataArr[0].oriIndex], 100 -
valueRatio[normalizedDataArr[0].oriIndex]) / 2 * effectivePlotArea.width / 100;
}
zerothStop = stop;
// first scale Label
smartText = smartLabel.getSmartText(normalizedDataArr[0].label, zerothStop, effectivePlotArea.height);
smartText.x = valueRatio[normalizedDataArr[0].oriIndex] * effectivePlotArea.width / 100;
leftBound = smartText.x + smartText.width;
labelHeights.push(smartText.height);
lsTexts[normalizedDataArr[0].oriIndex] = smartText;
//last scale label
smartText = smartLabel.getSmartText(normalizedDataArr[length - 1].label,
zerothStop, effectivePlotArea.height);
smartText.x = valueRatio[normalizedDataArr[length - 1].oriIndex] * effectivePlotArea.width / 100;
rightBound = smartText.x - smartText.width;
labelHeights.push(smartText.height);
lsTexts[normalizedDataArr[length - 1].oriIndex] = smartText;
leftStop = leftBound;
for (index = 1; index < length - 1; index++) {
label = normalizedDataArr[index].label;
ni = normalizedDataArr[index].oriIndex;
smartText = undefined;
nextRefPoint = index + 1 === length - 1 ? rightBound : valueRatio[normalizedDataArr[index + 1].oriIndex] *
effectivePlotArea.width / 100;
currPoint = valueRatio[normalizedDataArr[index].oriIndex] * effectivePlotArea.width / 100;
stop = Math.min(currPoint - leftStop, nextRefPoint - currPoint);
if (stop > 2 * testSmartLabel.width) {
smartText = smartLabel.getSmartText(label, stop, effectivePlotArea.height);
smartText.x = valueRatio[ni] * effectivePlotArea.width / 100;
leftStop = stop;
labelHeights.push(smartText.height);
}
lsTexts[normalizedDataArr[index].oriIndex] = smartText;
}
maxHeight = Math.max.apply(Math, labelHeights);
effectivePlotArea.height = maxHeight;
bBox.height = maxHeight + 2 * padding.v;
lSpace.node.logicArea = effectivePlotArea;
lSpace.bound = bBox;
return bBox;
};
LegendLabels.prototype.draw = function () {
var paper,
layer,
bound,
conf = this.conf,
boundStyle = conf.bound && conf.bound.style || {
stroke: 'none'
},
cRange,
componentPool,
legendLabelsGroup,
bBox,
smartText,
lSpace,
crDataObj,
valueRatio,
options,
index,
logicArea,
lsTexts,
length,
pos = {},
instanceFn,
keys;
if (arguments.length >= 2) {
bBox = arguments[0];
options = arguments[1];
} else if (arguments.length >= 1){
options = arguments[0];
}
paper = options.paper;
layer = options.parentLayer;
cRange = options.colorRange;
valueRatio = cRange.getCumulativeValueRatio();
crDataObj = cRange.colorRange;
componentPool = options.componentPool;
keys = componentPool.getKeys();
this.getLogicalSpace(bBox, options);
lSpace = this._lSpace;
logicArea = lSpace.node.logicArea;
lsTexts = lSpace.node.smartTexts;
instanceFn = componentPool.getComponent(this._id, keys.KEY_GROUP);
legendLabelsGroup = instanceFn('legend-labels', layer);
instanceFn = componentPool.getComponent(this._id, keys.KEY_RECT);
this.bound = bound = instanceFn(legendLabelsGroup).attr(lSpace.bound).css(boundStyle);
// legendLabelsGroup.transform('R0');
instanceFn = componentPool.getComponent(this._id, keys.KEY_TEXT, true);
for (index = 0, length = lsTexts.length; index < length; index++) {
smartText = lsTexts[index];
if (!smartText) {
continue;
}
pos.y = logicArea.y + smartText.height / 2;
if (index === length - 1) {
pos.x = logicArea.x + smartText.x - smartText.width / 2;
} else if (index){
pos.x = logicArea.x + smartText.x;
} else {
pos.x = logicArea.x + smartText.x + smartText.width / 2;
}
this.node.push(instanceFn({}, legendLabelsGroup).attr({
text: smartText.text,
x: pos.x,
y: pos.y,
lineHeight: this._metaStyle.lineHeight,
fill: conf.style.fill
}).transform('R0'));
}
return {
group: legendLabelsGroup,
bound: bound,
node: this.node
};
};
function VerticalLegendLabels () {
LegendLabels.apply(this, arguments);
}
VerticalLegendLabels.prototype = Object.create(LegendLabels.prototype);
VerticalLegendLabels.prototype.constructor = VerticalLegendLabels;
VerticalLegendLabels.prototype.getLogicalSpace = function (bBox, options, recalculate) {
var lSpace = this._lSpace,
conf = this.conf,
padding = conf.padding,
cRange,
smartLabel,
crDataObj,
index,
length,
labelHeights = [],
leftBound,
rightBound,
plotArea,
label,
valueRatio,
stop,
zerothStop,
lsTexts,
maxHeight,
effectivePlotArea,
testSmartLabel,
noOfLabels = 0,
nextRefPoint,
currPoint,
leftStop,
smartText,
ni,
copyOfStyle,
componentPool = options.componentPool,
chart = componentPool.getChart(),
normalizedDataArr = [];
if (lSpace && !recalculate) {
lSpace.isImpure = true;
// If logical space has already been calculated and recalculate flag is on, return without recalculating.
return lSpace;
}
cRange = options.colorRange;
smartLabel = options.smartLabel;
valueRatio = cRange.getCumulativeValueRatio();
crDataObj = cRange.colorRange;
lSpace = this._lSpace = {
bound: {
height: 0,
width: 0
},
node: {
logicArea: undefined,
smartTexts: []
}
};
lsTexts = lSpace.node.smartTexts;
plotArea = merge(bBox, {});
// The logical rect inside which the composition are drawn
effectivePlotArea = this.getEffectivePlotArea(plotArea);
smartLabel.useEllipsesOnOverflow(chart.config.useEllipsesWhenOverflow);
copyOfStyle = merge(conf.style, {});
normalizeFontSizeAppend(this._metaStyle = copyOfStyle);
smartLabel.setStyle(copyOfStyle);
testSmartLabel = smartLabel.getSmartText('W');
for (index = 0, length = crDataObj.length; index < length; index++) {
label = crDataObj[index].label;
if (!label) {
lsTexts[index] = undefined;
continue;
}
noOfLabels++;
normalizedDataArr.push({
oriIndex: index,
label: label
});
}
length = normalizedDataArr.length;
if (length === 0) {
return {
height: 0,
width: 0
};
}
if (length > 1) {
stop = ((valueRatio[normalizedDataArr[length - 1].oriIndex] -
valueRatio[normalizedDataArr[0].oriIndex]) / 2) * effectivePlotArea.height / 100;
} else {
stop = Math.max(valueRatio[normalizedDataArr[0].oriIndex], 100 -
valueRatio[normalizedDataArr[0].oriIndex]) / 2 * effectivePlotArea.height / 100;
}
zerothStop = stop;
// first scale Label
smartText = smartLabel.getSmartText(normalizedDataArr[0].label, zerothStop, effectivePlotArea.width);
smartText.y = valueRatio[normalizedDataArr[0].oriIndex] * effectivePlotArea.height / 100;
leftBound = smartText.y + smartText.width;
labelHeights.push(smartText.height);
lsTexts[normalizedDataArr[0].oriIndex] = smartText;
//last scale label
smartText = smartLabel.getSmartText(normalizedDataArr[length - 1].label, zerothStop, effectivePlotArea.width);
smartText.y = valueRatio[normalizedDataArr[length - 1].oriIndex] * effectivePlotArea.height / 100;
rightBound = smartText.y - smartText.width;
labelHeights.push(smartText.height);
lsTexts[normalizedDataArr[length - 1].oriIndex] = smartText;
leftStop = leftBound;
for (index = 1; index < length - 1; index++) {
label = normalizedDataArr[index].label;
ni = normalizedDataArr[index].oriIndex;
smartText = undefined;
nextRefPoint = index + 1 === length - 1 ? rightBound : valueRatio[normalizedDataArr[index + 1].oriIndex] *
effectivePlotArea.height / 100;
currPoint = valueRatio[normalizedDataArr[index].oriIndex] * effectivePlotArea.height / 100;
stop = Math.min(currPoint - leftStop, nextRefPoint - currPoint);
if (stop > 2 * testSmartLabel.width) {
smartText = smartLabel.getSmartText(label, stop, effectivePlotArea.width);
smartText.y = valueRatio[ni] * effectivePlotArea.height / 100;
leftStop = stop;
labelHeights.push(smartText.height);
}
lsTexts[normalizedDataArr[index].oriIndex] = smartText;
}
maxHeight = Math.max.apply(Math, labelHeights);
effectivePlotArea.width = maxHeight;
bBox.width = maxHeight + 2 * padding.v;
lSpace.node.logicArea = effectivePlotArea;
lSpace.bound = bBox;
return bBox;
};
VerticalLegendLabels.prototype.draw = function () {
var paper,
layer,
bound,
conf = this.conf,
boundStyle = conf.bound && conf.bound.style || {
stroke: 'none'
},
cRange,
componentPool,
legendLabelsGroup,
bBox,
smartText,
lSpace,
crDataObj,
valueRatio,
options,
index,
logicArea,
lsTexts,
length,
pos = {},
instanceFn,
keys;
if (arguments.length >= 2) {
bBox = arguments[0];
options = arguments[1];
} else if (arguments.length >= 1){
options = arguments[0];
}
paper = options.paper;
layer = options.parentLayer;
cRange = options.colorRange;
valueRatio = cRange.getCumulativeValueRatio();
crDataObj = cRange.colorRange;
componentPool = options.componentPool;
keys = componentPool.getKeys();
this.getLogicalSpace(bBox, options);
lSpace = this._lSpace;
logicArea = lSpace.node.logicArea;
lsTexts = lSpace.node.smartTexts;
instanceFn = componentPool.getComponent(this._id, keys.KEY_GROUP);
legendLabelsGroup = instanceFn('legend-labels', layer);
instanceFn = componentPool.getComponent(this._id, keys.KEY_RECT);
this.bound = bound = instanceFn(legendLabelsGroup).attr(lSpace.bound).css(boundStyle);
instanceFn = componentPool.getComponent(this._id, keys.KEY_TEXT, true);
for (index = 0, length = lsTexts.length; index < length; index++) {
smartText = lsTexts[index];
if (!smartText) {
continue;
}
pos.x = logicArea.x + smartText.height / 2;
if (index === length - 1) {
pos.y = logicArea.y + smartText.y - smartText.width / 2;
} else if (index){
pos.y = logicArea.y + smartText.y;
} else {
pos.y = logicArea.y + smartText.y + smartText.width / 2;
}
this.node.push(instanceFn({}, legendLabelsGroup).attr({
text: smartText.text,
x: pos.x,
y: pos.y,
lineHeight: this._metaStyle.lineHeight,
fill: conf.style.fill
}).transform('R270,' + pos.x + ',' + pos.y));
}
return {
group: legendLabelsGroup,
bound: bound,
node: this.node
};
};
function LegendValues () {
LegendLabels.apply(this, arguments);
this._id = 'GL_VALUES';
}
LegendValues.prototype = Object.create(LegendLabels.prototype);
LegendValues.prototype.constructor = LegendValues;
LegendValues.prototype.getLogicalSpace = function (bBox, options, recalculate) {
var lSpace = this._lSpace,
conf = this.conf,
padding = conf.padding,
componentPool = options.componentPool,
chart = componentPool.getChart(),
cRange,
smartLabel,
crDataObj,
smartText,
index,
length,
valueRatio,
stop,
nextRefPoint,
currPoint,
zerothStop,
labelHeights = [],
leftBound,
leftStop,
rightBound,
maxHeight,
effectivePlotArea,
val,
dispValue,
testSmartLabel,
copyOfStyle,
lsTexts;
if (lSpace && !recalculate) {
lSpace.isImpure = true;
// If logical space has already been calculated and recalculate flag is on, return without recalculating.
return lSpace;
}
cRange = options.colorRange;
smartLabel = options.smartLabel;
crDataObj = cRange.colorRange;
valueRatio = cRange.getCumulativeValueRatio();
lSpace = this._lSpace = {
bound: {
height: 0,
width: 0
},
node: {
logicArea: undefined,
smartTexts: []
}
};
lsTexts = lSpace.node.smartTexts;
effectivePlotArea = merge(bBox, {});
// The logical rect inside which the composition are drawn
effectivePlotArea.height -= 2 * padding.v;
effectivePlotArea.width -= 2 * padding.h;
effectivePlotArea.x += padding.h;
effectivePlotArea.y += padding.v;
smartLabel.useEllipsesOnOverflow(chart.config.useEllipsesWhenOverflow);
copyOfStyle = merge(conf.style, {});
normalizeFontSizeAppend(this._metaStyle = copyOfStyle);
smartLabel.setStyle(copyOfStyle);
testSmartLabel = smartLabel.getSmartText('W');
length = crDataObj.length;
zerothStop = stop = ((valueRatio[length - 1] - valueRatio[0]) / 2) * effectivePlotArea.width / 100;
// first scale Label
dispValue = crDataObj[0].displayValue;
smartText = smartLabel.getSmartText((typeof dispValue !== 'string' && dispValue !== undefined) &&
dispValue.toString() || dispValue, zerothStop, effectivePlotArea.height);
smartText.x = valueRatio[0] * effectivePlotArea.width / 100;
leftBound = smartText.x + smartText.width;
labelHeights.push(smartText.height);
lsTexts[0] = smartText;
//last scale label
smartText = smartLabel.getSmartText(crDataObj[length - 1].displayValue,
zerothStop, effectivePlotArea.height);
smartText.x = valueRatio[length - 1] * effectivePlotArea.width / 100;
rightBound = smartText.x - smartText.width;
labelHeights.push(smartText.height);
lsTexts[length - 1] = smartText;
leftStop = leftBound;
for (index = 1; index < length - 1; index++) {
smartText = undefined;
val = crDataObj[index].displayValue;
nextRefPoint = index + 1 === length - 1 ? rightBound : valueRatio[index + 1] *
effectivePlotArea.width / 100;
currPoint = valueRatio[index] * effectivePlotArea.width / 100;
stop = Math.min(currPoint - leftStop, nextRefPoint - currPoint);
if (stop > 1.5 * testSmartLabel.width) {
smartText = smartLabel.getSmartText(val, 2 * stop, effectivePlotArea.height);
smartText.x = valueRatio[index] * effectivePlotArea.width / 100;
leftStop = stop;
labelHeights.push(smartText.height);
}
lsTexts[index] = smartText;
}
maxHeight = Math.max.apply(Math, labelHeights);
effectivePlotArea.height = maxHeight;
bBox.height = maxHeight + 2 * padding.v;
lSpace.node.logicArea = effectivePlotArea;
lSpace.bound = bBox;
return bBox;
};
LegendValues.prototype.draw = function () {
var conf = this.conf,
boundStyle = conf.bound && conf.bound.style || {
stroke: 'none'
},
keys,
pos = {},
paper,
layer,
bound,
legendValuesGroup,
componentPool,
bBox,
lSpace,
smartLabel,
logicArea,
options,
cRange,
valueRatio,
index,
length,
smartTexts,
smartText,
instanceFn,
numberFormatter;
if (arguments.length >= 2) {
bBox = arguments[0];
options = arguments[1];
} else if (arguments.length >= 1){
options = arguments[0];
}
paper = options.paper;
layer = options.parentLayer;
smartLabel = options.smartLabel,
cRange = options.colorRange;
numberFormatter = options.numberFormatter,
valueRatio = cRange.getCumulativeValueRatio();
componentPool = options.componentPool;
keys = componentPool.getKeys();
this.getLogicalSpace(bBox, options);
lSpace = this._lSpace;
logicArea = lSpace.node.logicArea;
smartTexts = lSpace.node.smartTexts;
instanceFn = componentPool.getComponent(this._id, keys.KEY_GROUP);
legendValuesGroup = instanceFn('legend-values', layer);
instanceFn = componentPool.getComponent(this._id, keys.KEY_RECT);
this.bound = bound = instanceFn(legendValuesGroup).attr(lSpace.bound).css(boundStyle);
instanceFn = componentPool.getComponent(this._id, keys.KEY_TEXT, true);
for (index = 0, length = valueRatio.length; index < length; index++) {
smartText = smartTexts[index];
if (!smartText) {
continue;
}
pos.y = logicArea.y + smartText.height / 2;
if (index === length - 1) {
pos.x = logicArea.x + smartText.x - smartText.width / 2;
} else if (index){
pos.x = logicArea.x + smartText.x;
} else {
pos.x = logicArea.x + smartText.x + smartText.width / 2;
}
instanceFn({}, legendValuesGroup).attr({
text: smartText.text,
x: pos.x,
y: pos.y,
lineHeight: this._metaStyle.lineHeight,
fill: conf.style.fill
});
}
return {
group: legendValuesGroup,
bound: bound
};
};
function VerticalLegendValues () {
LegendValues.apply(this, arguments);
this._id = 'GL_VALUES';
}
VerticalLegendValues.prototype = Object.create(LegendValues.prototype);
VerticalLegendValues.prototype.constructor = VerticalLegendValues;
VerticalLegendValues.prototype.getLogicalSpace = function (bBox, options, recalculate) {
var lSpace = this._lSpace,
conf = this.conf,
padding = conf.padding,
componentPool = options.componentPool,
chart = componentPool.getChart(),
cRange,
smartLabel,
crDataObj,
smartText,
index,
length,
valueRatio,
stop,
nextRefPoint,
currPoint,
effectivePlotArea,
zerothStop,
labelWidths = [],
topBound,
topStop,
maxWidth,
bottomBound,
val,
copyOfStyle,
testSmartLabel,
lsTexts;
if (lSpace && !recalculate) {
lSpace.isImpure = true;
// If logical space has already been calculated and recalculate flag is on, return without recalculating.
return lSpace;
}
cRange = options.colorRange;
smartLabel = options.smartLabel;
crDataObj = cRange.colorRange;
valueRatio = cRange.getCumulativeValueRatio();
lSpace = this._lSpace = {
bound: {
height: 0,
width: 0
},
node: {
logicArea: undefined,
smartTexts: []
}
};
lsTexts = lSpace.node.smartTexts;
effectivePlotArea = merge(bBox, {});
// The logical rect inside which the composition are drawn
effectivePlotArea.height -= 2 * padding.v;
effectivePlotArea.width -= 2 * padding.h;
effectivePlotArea.x += padding.h;
effectivePlotArea.y += padding.v;
smartLabel.useEllipsesOnOverflow(chart.config.useEllipsesWhenOverflow);
copyOfStyle = merge(conf.style, {});
normalizeFontSizeAppend(copyOfStyle);
smartLabel.setStyle(this._metaStyle = copyOfStyle);
testSmartLabel = smartLabel.getSmartText('W');
length = crDataObj.length;
zerothStop = stop = ((valueRatio[length - 1] - valueRatio[0]) / 2) * effectivePlotArea.height / 100;
// first scale Label
smartText = smartLabel.getSmartText(crDataObj[0].displayValue,
effectivePlotArea.width, zerothStop);
smartText.y = valueRatio[0] * effectivePlotArea.height / 100;
topBound = smartText.y + smartText.width;
labelWidths.push(smartText.width);
lsTexts[0] = smartText;
//last scale label
smartText = smartLabel.getSmartText(crDataObj[length - 1].displayValue,
effectivePlotArea.width, zerothStop);
smartText.y = valueRatio[length - 1] * effectivePlotArea.height / 100;
bottomBound = smartText.y - smartText.height;
labelWidths.push(smartText.width);
lsTexts[length - 1] = smartText;
topStop = topBound;
for (index = 1; index < length - 1; index++) {
smartText = undefined;
val = crDataObj[index].displayValue;
nextRefPoint = index + 1 === length - 1 ? bottomBound : valueRatio[index + 1] *
effectivePlotArea.height / 100;
currPoint = valueRatio[index] * effectivePlotArea.height / 100;
stop = Math.min(currPoint - topStop, nextRefPoint - currPoint);
if (stop > 2 * testSmartLabel.height) {
smartText = smartLabel.getSmartText(val, effectivePlotArea.width, 2 * stop);
smartText.y = valueRatio[index] * effectivePlotArea.height / 100;
topStop = stop;
labelWidths.push(smartText.width);
}
lsTexts[index] = smartText;
}
maxWidth = Math.max.apply(Math, labelWidths);
effectivePlotArea.width = maxWidth;
bBox.width = maxWidth + 2 * padding.h;
lSpace.node.logicArea = effectivePlotArea;
lSpace.bound = bBox;
return bBox;
};
VerticalLegendValues.prototype.draw = function () {
var paper,
layer,
bound,
conf = this.conf,
boundStyle = conf.bound && conf.bound.style || {
stroke: 'none'
},
legendValuesGroup,
bBox,
lSpace,
smartLabel,
logicArea,
options,
cRange,
valueRatio,
smartTexts,
smartText,
index,
length,
pos = {},
componentPool,
instanceFn,
keys;
if (arguments.length >= 2) {
bBox = arguments[0];
options = arguments[1];
} else if (arguments.length >= 1){
options = arguments[0];
}
paper = options.paper;
layer = options.parentLayer;
smartLabel = options.smartLabel,
cRange = options.colorRange;
valueRatio = cRange.getCumulativeValueRatio();
componentPool = options.componentPool;
keys = componentPool.getKeys();
this.getLogicalSpace(bBox, options);
lSpace = this._lSpace;
logicArea = lSpace.node.logicArea;
smartTexts = lSpace.node.smartTexts;
instanceFn = componentPool.getComponent(this._id, keys.KEY_GROUP);
legendValuesGroup = instanceFn('legend-values', layer);
instanceFn = componentPool.getComponent(this._id, keys.KEY_RECT);
this.bound = bound = instanceFn(legendValuesGroup).attr(lSpace.bound).css(boundStyle);
instanceFn = componentPool.getComponent(this._id, keys.KEY_TEXT, true);
for (index = 0, length = valueRatio.length; index < length; index++) {
smartText = smartTexts[index];
if (!smartText) {
continue;
}
pos.x = logicArea.x + smartText.width / 2;
if (index === length - 1) {
pos.y = logicArea.y + smartText.y - smartText.height / 2;
} else if (index){
pos.y = logicArea.y + smartText.y;
} else {
pos.y = logicArea.y + smartText.y + smartText.height / 2;
}
instanceFn({ }, legendValuesGroup).attr({
text: smartText.text,
x: pos.x,
y: pos.y,
lineHeight: this._metaStyle.lineHeight,
fill: conf.style.fill
});
}
return {
group: legendValuesGroup,
bound: bound
};
};
function LegendAxis (conf) {
this.conf = conf;
this._id = 'FL_AXIS';
this.node = undefined;
this.shadow = undefined;
this.markerLine = undefined;
this.compositionsByCategory = {};
}
LegendAxis.prototype.constructor = LegendAxis;
LegendAxis.prototype.addCompositions = function (instance, category) {
this.compositionsByCategory[category] = instance;
};
LegendAxis.prototype.getLogicalSpace = function (bBox, options, recalculate) {
var lSpace = this._lSpace,
conf = this.conf,
padding = conf.padding,
heightTakenLower,
heightTakenUpper,
heightTaken,
axisThickness = conf.legendAxisHeight,
compositionsByCategory = this.compositionsByCategory,
sliderG,
effectivePlotArea,
slider,
sliderSpace,
sliderExtraDiam = 0;
if (lSpace && !recalculate) {
lSpace.isImpure = true;
// If logical space has already been calculated and recalculate flag is on, return without recalculating.
return lSpace;
}
lSpace = this._lSpace = {
bound: {
height: 0,
width: 0
},
node: {
logicArea: undefined
}
};
effectivePlotArea = merge(bBox, {});
// The logical rect inside which the composition are drawn
effectivePlotArea.height -= 2 * padding.v;
effectivePlotArea.width -= 2 * padding.h;
effectivePlotArea.x += padding.h;
effectivePlotArea.y += padding.v;
heightTakenLower = (axisThickness / 2) + conf.line.offset;
heightTakenUpper = axisThickness / 2;
sliderG = compositionsByCategory[compositionKeys.RANGE];
if (sliderG) {
slider = sliderG.sliders[false];
sliderSpace = slider.conf.outerCircle.rFactor * axisThickness;
heightTakenUpper += sliderExtraDiam = Math.max(sliderSpace / 2 - axisThickness / 2, 0);
}
// @todo Change the height and width of bBox
effectivePlotArea.y += sliderExtraDiam;
effectivePlotArea.height = heightTaken = heightTakenUpper + heightTakenLower + sliderExtraDiam;
bBox.height = heightTaken + 2 * padding.v;
lSpace.node.logicArea = effectivePlotArea;
lSpace.bound = bBox;
return bBox;
};
LegendAxis.prototype.getDrawableAxisArea = function (parentBoundingRect) {
var conf = this.conf,
x = parentBoundingRect.x,
y = parentBoundingRect.y,
width = parentBoundingRect.width,
height = conf.legendAxisHeight,
r = conf.legendAxisHeight / 2;
return {
x: x,
y: y,
width: width,
height: height,
r: r
};
};
LegendAxis.prototype.preDrawingRangeParam = function (drawableArea) {
var y = drawableArea.y + (drawableArea.height / 2),
calculationBase = drawableArea.height;
return {
y: y,
calculationBase: calculationBase,
rangeStart: drawableArea.x,
rangeEnd: drawableArea.x + drawableArea.width,
prop: 'y'
};
};
LegendAxis.prototype.getScaleMarkerPathStr = function (oriAxisRect, valueRatio) {
var axisRect = merge(oriAxisRect, {}),
conf = this.conf,
lineAttr = conf.line,
index,
length,
ratio,
covered,
markerStartY,
tickStr = '',
lineStr = '';
axisRect.x += axisRect.r;
axisRect.width -= 2 * axisRect.r;
markerStartY = axisRect.y + axisRect.height;
for (index = 0, length = valueRatio.length; index < length; index++) {
ratio = valueRatio[index];
covered = axisRect.x + (ratio * axisRect.width / 100);
tickStr += M + (covered) + COMMA_STR + (markerStartY - lineAttr.grooveLength) +
L + (covered) + COMMA_STR + (markerStartY + lineAttr.offset);
}
lineStr += M + (axisRect.x) + COMMA_STR + (markerStartY + lineAttr.offset) +
L + (axisRect.x + axisRect.width) + COMMA_STR + (markerStartY + lineAttr.offset);
return tickStr + lineStr;
};
LegendAxis.prototype.getColorGradient = function (colorRange) {
return {
axis: colorRange.getBoxFill(),
shadow: toRaphaelColor({
FCcolor: {
alpha : '25,0,0',
angle : 90,
color : '000000,FFFFFF,FFFFFF',
ratio : '0,30,40'
}
})
};
};
LegendAxis.prototype.draw = function () {
var paper,
layer,
bound,
conf = this.conf,
boundAttr = conf.bound || {},
lineAttr = conf.line,
boundStyle = boundAttr.style || {},
node,
bBox,
category,
compositionsByCategory = this.compositionsByCategory,
cRange,
valueRatio,
composition,
compositionMes,
legendAxisGroup,
rangeParams,
grad,
oriAxisRect,
lSpace,
options,
instanceFn,
keys,
componentPool,
scaleLine;
if (arguments.length >= 2) {
bBox = arguments[0];
options = arguments[1];
} else if (arguments.length >= 1){
options = arguments[0];
}
paper = options.paper;
layer = options.parentLayer;
cRange = options.colorRange;
valueRatio = cRange.getCumulativeValueRatio();
componentPool = options.componentPool;
keys = componentPool.getKeys();
this.getLogicalSpace(bBox, options);
lSpace = this._lSpace;
instanceFn = componentPool.getComponent(this._id, keys.KEY_GROUP);
legendAxisGroup = instanceFn('legend-axis', layer);
//legendAxisGroup = paper.group('legend-axis', layer);
instanceFn = componentPool.getComponent(this._id, keys.KEY_RECT, true);
this.bound = bound = instanceFn(legendAxisGroup).attr(lSpace.bound).css(boundStyle);
//bound = this.bound = paper.rect(lSpace.bound, legendAxisGroup).css(boundStyle);
oriAxisRect = this.getDrawableAxisArea(lSpace.node.logicArea);
grad = this.getColorGradient(cRange);
conf.style.fill = grad.axis;
conf.shadow.style.fill = grad.shadow;
// node = this.node = paper.rect(oriAxisRect, legendAxisGroup).css(conf.style);
// this.shadow = paper.rect(oriAxisRect, legendAxisGroup).css(conf.shadow.style);
node = this.node = instanceFn(legendAxisGroup).attr(oriAxisRect).css(conf.style);
this.shadow = instanceFn(legendAxisGroup).attr(oriAxisRect).css(conf.shadow.style);
scaleLine = this.getScaleMarkerPathStr(oriAxisRect, valueRatio);
instanceFn = componentPool.getComponent(this._id, keys.KEY_PATH);
instanceFn('M0,0', legendAxisGroup)
.attr({ path: scaleLine }).css(lineAttr.style);
for (category in compositionsByCategory) {
composition = compositionsByCategory[category];
switch (category) {
case compositionKeys.RANGE:
rangeParams = this.preDrawingRangeParam(oriAxisRect);
options[rangeParams.prop] = rangeParams[rangeParams.prop];
options.key = rangeParams.prop;
options.rCalcBase = rangeParams.calculationBase;
options.parentLayer = legendAxisGroup;
compositionMes = composition.draw(rangeParams.rangeStart,rangeParams.rangeEnd, options);
}
}
};
function VerticalLegendAxis () {
LegendAxis.apply(this, arguments);
}
VerticalLegendAxis.prototype = Object.create(LegendAxis.prototype);
VerticalLegendAxis.prototype.constructor = VerticalLegendAxis;
VerticalLegendAxis.prototype.getLogicalSpace = function (bBox, options, recalculate) {
var lSpace = this._lSpace,
conf = this.conf,
padding = conf.padding,
widthTakenLower,
widthTakenUpper,
widthTaken,
axisThickness = conf.legendAxisHeight,
compositionsByCategory = this.compositionsByCategory,
sliderG,
slider,
effectivePlotArea,
sliderSpace,
sliderExtraDiam = 0;
if (lSpace && !recalculate) {
lSpace.isImpure = true;
// If logical space has already been calculated and recalculate flag is on, return without recalculating.
return lSpace;
}
lSpace = this._lSpace = {
bound: {
height: 0,
width: 0
},
node: {
logicArea: undefined
}
};
effectivePlotArea = merge(bBox, {});
// The logical rect inside which the composition are drawn
effectivePlotArea.height -= 2 * padding.v;
effectivePlotArea.width -= 2 * padding.h;
effectivePlotArea.x += padding.h;
effectivePlotArea.y += padding.v;
widthTakenLower = (axisThickness / 2) + conf.line.offset;
widthTakenUpper = axisThickness / 2;
sliderG = compositionsByCategory[compositionKeys.RANGE];
if (sliderG) {
slider = sliderG.sliders[false];
sliderSpace = slider.conf.outerCircle.rFactor * axisThickness;
widthTakenUpper += sliderExtraDiam = Math.max(sliderSpace / 2 - axisThickness / 2, 0);
}
effectivePlotArea.x += sliderExtraDiam;
effectivePlotArea.width = widthTaken = widthTakenUpper + widthTakenLower + sliderExtraDiam;
bBox.width = widthTaken + 2 * padding.v;
lSpace.node.logicArea = effectivePlotArea;
lSpace.bound = bBox;
return bBox;
};
VerticalLegendAxis.prototype.getDrawableAxisArea = function (parentBoundingRect) {
var conf = this.conf;
return {
x: parentBoundingRect.x,
y: parentBoundingRect.y,
width: conf.legendAxisHeight,
height: parentBoundingRect.height,
r: conf.legendAxisHeight / 2
};
};
VerticalLegendAxis.prototype.getScaleMarkerPathStr = function (oriAxisRect, valueRatio) {
var axisRect = merge(oriAxisRect, {}),
conf = this.conf,
lineAttr = conf.line,
index,
ratio,
length,
markerStartX,
covered,
tickStr = '',
lineStr = '';
axisRect.y += axisRect.r;
axisRect.height -= 2 * axisRect.r;
markerStartX = axisRect.x + axisRect.width;
for (index = 0, length = valueRatio.length; index < length; index++) {
ratio = valueRatio[index];
covered = axisRect.y + (ratio * axisRect.height / 100);
tickStr += M + (markerStartX - lineAttr.grooveLength) + COMMA_STR + (covered) +
L + (markerStartX + lineAttr.offset) + COMMA_STR + (covered);
}
lineStr += M + (markerStartX + lineAttr.offset) + COMMA_STR + (axisRect.y) +
L + (markerStartX + lineAttr.offset) + COMMA_STR + (axisRect.y + axisRect.height);
return tickStr + lineStr;
};
VerticalLegendAxis.prototype.getColorGradient = function (colorRange) {
return {
axis: colorRange.getBoxFill(true),
shadow: toRaphaelColor({
FCcolor: {
alpha : '25,0,0',
angle : 360,
color : '000000,FFFFFF,FFFFFF',
ratio : '0,30,40'
}
})
};
};
VerticalLegendAxis.prototype.preDrawingRangeParam = function (drawableArea) {
var x = drawableArea.x + (drawableArea.width / 2),
calculationBase = drawableArea.width;
return {
x: x,
calculationBase: calculationBase,
rangeStart: drawableArea.y,
rangeEnd: drawableArea.y + drawableArea.height,
prop: 'x'
};
};
function SliderGroup (conf) {
var sliderConf = conf,
options = {};
this._id = 'GL_SG1';
this.conf = conf;
options.conf = sliderConf;
this.extremes = [];
this.sliders = {};
options.sliderGroup = this;
this.valueRange = [];
this.callbacks = [];
this.sliders[FORMER_SLIDER_INDEX] = new Slider(FORMER_SLIDER_INDEX, options, this._id +
'_' + (+FORMER_SLIDER_INDEX));
this.sliders[LATER_SLIDER_INDEX] = new Slider(LATER_SLIDER_INDEX, options, this._id +
'_' + (+LATER_SLIDER_INDEX));
}
SliderGroup.prototype.constructor = SliderGroup;
SliderGroup.prototype.initRange = function (slider, updatedRange) {
var sliderIndex = slider.sliderIndex;
this.extremes[+sliderIndex] = updatedRange;
};
SliderGroup.prototype.updateRange = function (slider, updatedRange) {
var sliderIndex = slider.sliderIndex,
sliders = this.sliders,
s = sliders[!sliderIndex];
s.updateSwingRange(sliderIndex, updatedRange);
};
SliderGroup.prototype.reset = function () {
var options = {};
options.conf = this.conf;
options.sliderGroup = this;
// @todo do it from a more generalized way
this.sliders[FORMER_SLIDER_INDEX] = new Slider(FORMER_SLIDER_INDEX, options, this._id +
'_' + (+FORMER_SLIDER_INDEX));
this.sliders[LATER_SLIDER_INDEX] = new Slider(LATER_SLIDER_INDEX, options, this._id +
'_' + (+LATER_SLIDER_INDEX));
this.draw.apply(this, this._drawParams);
};
SliderGroup.prototype.clearListeners = function () {
this.callbacks.length = 0;
};
SliderGroup.prototype.draw = function (rangeStart, rangeEnd, options) {
var sliders = this.sliders,
lSlider = sliders[FORMER_SLIDER_INDEX],
rSlider = sliders[LATER_SLIDER_INDEX],
cRange = options.colorRange,
colorRange = cRange.colorRange,
componentPool = options.componentPool,
chart = this._fcChart = componentPool.getChart(),
oneSliderMes;
this.getValueFormPixel = function (valueStart, valueEnd, pixelStart, pixelEnd) {
var unit = (valueEnd - valueStart) / (pixelEnd - pixelStart);
this.getValueFormPixel = function (pixel) {
return valueStart + (unit * pixel);
};
};
this.updateWhenInMove = function (numberFormatter, mapByPercent) {
this.updateWhenInMove = function (slider, val) {
var extremes = this.extremes,
sliderIndex = slider.sliderIndex,
nVal,
value;
if (sliderIndex) {
nVal = extremes[1] - extremes[0] + val;
} else {
nVal = val;
}
value = this.getValueFormPixel(nVal);
if (!mapByPercent) {
value = numberFormatter.legendValue(value);
} else {
value = parseFloat(value).toFixed(2) + PERCENT_STR;
}
return value;
};
};
this._drawParams = [rangeStart, rangeEnd, options];
this.updateWhenInMove(chart.components.numberFormatter, cRange.mapByPercent);
oneSliderMes = lSlider.draw(rangeStart, colorRange[0].displayValue, options[options.key], options);
oneSliderMes = rSlider.draw(rangeEnd, colorRange[colorRange.length - 1].displayValue, options[options.key],
options);
lSlider.swing = this.extremes.slice(0);
rSlider.swing = this.extremes.slice(0);
this.getValueFormPixel(colorRange[0].value, colorRange[colorRange.length - 1].value,
this.extremes[0], this.extremes[1]);
return oneSliderMes;
};
SliderGroup.prototype.registerListener = function (fn, context, params) {
this.callbacks.push({
fn: fn,
context: context,
params: params || []
});
};
SliderGroup.prototype.updateWhenInRest = function (slider, val) {
var sliders = this.sliders,
extremes = this.extremes,
sliderIndex = slider.sliderIndex,
lValue,
rValue,
cIndex,
cLength,
callbacks = this.callbacks,
cb,
params;
if (sliderIndex) {
lValue = sliders[!sliderIndex].currPos;
rValue = extremes[1] - extremes[0] + val;
} else {
lValue = val;
rValue = extremes[1] - extremes[0] + sliders[!sliderIndex].currPos;
}
for (cIndex = 0, cLength = callbacks.length; cIndex < cLength; cIndex++) {
cb = callbacks[cIndex];
params = cb.params.slice(0);
params.unshift(this.getValueFormPixel(rValue));
params.unshift(this.getValueFormPixel(lValue));
cb.fn.apply(cb.context, params);
}
};
SliderGroup.prototype.dragStarted = function (self) {
var sliders = this.sliders,
extremes = this.extremes,
conf = self.conf,
chart = this._fcChart;
global.raiseEvent ('legendpointerdragstart', {
pointerIndex: +self.sliderIndex,
pointers: [{
value: this.getValueFormPixel(sliders[false].currPos)
}, {
value: this.getValueFormPixel(extremes[1] - extremes[0] + sliders[true].currPos)
}],
legendPointerHeight: conf.outerRadius,
legendPointerWidth: conf.innerRadius,
outerRadius: conf.outerRadius,
innerRadius: conf.innerRadius
}, chart.chartInstance, [chart.id]);
};
SliderGroup.prototype.dragCompleted = function (self, isDragged, newVal) {
var sliders = this.sliders,
extremes = this.extremes,
conf = self.conf,
minValue = this.getValueFormPixel(sliders[false].currPos),
maxValue = this.getValueFormPixel(extremes[1] - extremes[0] + sliders[true].currPos),
chart = this._fcChart,
newMinValue,
newMaxValue;
if (!self.sliderIndex) {
newMinValue = this.getValueFormPixel(newVal);
newMaxValue = maxValue;
} else {
newMinValue = minValue;
newMaxValue = this.getValueFormPixel(extremes[1] - extremes[0] + newVal);
}
if (isDragged) {
global.raiseEvent ('legendrangeupdated', {
previousMinValue: minValue,
previousMaxValue: maxValue,
minValue: newMinValue,
maxValue: newMaxValue
}, chart.chartInstance, [chart.id]);
}
global.raiseEvent ('legendpointerdragstop', {
pointerIndex: +self.sliderIndex,
pointers: [{
value: minValue
}, {
value: maxValue
}],
legendPointerHeight: conf.outerRadius,
legendPointerWidth: conf.innerRadius,
outerRadius: conf.outerRadius,
innerRadius: conf.innerRadius
}, chart.chartInstance, [chart.id]);
};
function Slider (sliderIndex, options, id) {
this.conf = options.conf;
this.sliderIndex = sliderIndex;
this.rangeGroup = options.sliderGroup;
this._id = id;
this.node = undefined;
this.tracker = undefined;
this.currPos = 0;
this.swing = [];
}
Slider.prototype.constructor = Slider;
Slider.prototype.updateSwingRange = function (index, value) {
this.swing[+index] = value;
};
// @todo: Who should be responsible for reset, range or individual slider
// Slider.prototype.reset = function () {
// var node = this.node,
// tracker = this.tracker,
// conf = this.conf,
// dragAPI;
// if (!node) {
// return;
// }
// node.attr({
// transform: 't0,0'
// });
// this.currPos = 0;
// this.swing = [];
// };
Slider.prototype.draw = function (rangeStart, scaleVal, position, options) {
var layer = options.parentLayer,
conf = this.conf,
ocConf = conf.outerCircle,
icConf = conf.innerCircle,
ocRadius = Math.ceil((ocConf.rFactor * options.rCalcBase ) / 2),
icRadius = Math.ceil((icConf.rFactor * options.rCalcBase ) / 2),
icThickness = ocRadius - icRadius,
group,
rangeGroup = this.rangeGroup,
sliderIndex = this.sliderIndex,
dragAPI,
strokeWidthOffset,
x,
y,
tracker,
instanceFn,
iniRange,
componentPool = options.componentPool,
keys = componentPool.getKeys();
conf.outerRadius = ocRadius;
conf.innerRadius = icRadius;
this._scaleVal = scaleVal;
icConf.style['stroke-width'] = icThickness;
strokeWidthOffset = Math.ceil(ocConf.style['stroke-width'] / 2);
icRadius += strokeWidthOffset;
instanceFn = componentPool.getComponent(this._id, keys.KEY_GROUP);
group = this.node = instanceFn('fc-gl-slider', layer).attr({
'cursor' : 'pointer',
'transform': 't0,0'
});
if (options.key === 'x') {
x = position;
y = rangeStart;
y += sliderIndex ? -(icRadius) : +icRadius;
iniRange = y;
} else {
x = rangeStart;
y = position;
x += sliderIndex ? -(icRadius) : +icRadius;
iniRange = x;
}
rangeGroup.initRange(this, iniRange);
instanceFn = componentPool.getComponent(this._id, keys.KEY_CIRCLE, true);
instanceFn(group).attr({
cx: x,
cy: y,
r: ocRadius
}).css(ocConf.style);
instanceFn(group).attr({
cx: x,
cy: y,
r: icRadius
}).css(icConf.style);
tracker = this.tracker = instanceFn(group).attr({
cx: x,
cy: y,
r: ocRadius + 5,
ishot: true,
fill: TRACKER_FILL,
stroke: 0,
cursor: 'pointer'
}).trackTooltip(conf.showTooltip ? true : false).tooltip(scaleVal, null, null, true);
this._dragAPI = dragAPI = this.getDragAPI(options.key === 'x');
tracker.undrag();
tracker.drag(dragAPI.dragging, dragAPI.dragStart, dragAPI.dragEnd);
return {
translateAscending: ocRadius + strokeWidthOffset
};
};
Slider.prototype.getDragAPI = function (verticalDragging) {
var self = this,
node = self.node,
index = self.sliderIndex,
range = self.rangeGroup,
swing,
lastDisplacement,
timeoutId,
innerRadius = self.conf.innerRadius,
spaceSaved = innerRadius,
isDragged;
return {
dragging : function () {
var left,
right,
d,
event;
event = arguments[4];
event.stopPropagation();
event.preventDefault();
if (verticalDragging) {
d = arguments[1];
} else {
d = arguments[0];
}
if (index) {
left = swing[0] - swing[1] + spaceSaved;
right = 0;
} else {
left = 0;
right = swing[1] - swing[0] - spaceSaved;
}
if ((self.currPos + d) < left || (self.currPos + d) > right) {
return;
}
node.attr({
transform : verticalDragging ? 't0,' + (self.currPos + d) :
't' + (self.currPos + d) + ',' + 0
});
lastDisplacement = d;
timeoutId && clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
range.updateWhenInRest(self, self.currPos + d);
}, 100);
self.tracker.tooltip(range.updateWhenInMove(self, self.currPos + d), null, null, true);
isDragged = true;
return true;
},
dragStart : function (x, y, event) {
event.stopPropagation();
event.preventDefault();
node.attr({
transform : verticalDragging ? 't0,' + self.currPos :
't' + self.currPos + ',' + 0
});
swing = swing || self.swing;
isDragged = false;
range.dragStarted(self);
},
dragEnd : function () {
var newPos;
range.dragCompleted(self, isDragged, self.currPos + lastDisplacement);
if (!isDragged) {
return;
}
timeoutId && clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
range.updateWhenInRest(self, self.currPos);
}, 100);
self.currPos += lastDisplacement;
newPos = swing[+index] + self.currPos;
range.updateRange(self, newPos);
}
};
};
function ColorRange (data, options, chart) {
var numberFormatter = chart.components.numberFormatter,
index,
length,
range,
min,
max,
minRange,
maxRange,
minValue,
maxValue,
mapByPercent;
this.data = data;
this.options = options || {};
mapByPercent = this.mapByPercent = !!data.mapByPercent;
this.appender = '';
min = this.mapByPercent ? 0 : options.min;
max = this.mapByPercent ? 100 : options.max;
if (data.colorRange.length === 2) {
minRange = data.colorRange[0];
maxRange = data.colorRange[1];
minValue = minRange.value = isInvalid(minRange.value) ? min : minRange.value;
maxValue = maxRange.value = isInvalid(maxRange.value) ? max : maxRange.value;
if (minValue === maxValue) {
minValue = minRange.value = min;
maxValue = maxRange.value = max;
}
minRange.displayValue = mapByPercent ? minValue + PERCENT_STR : numberFormatter.legendValue(minValue);
maxRange.displayValue = mapByPercent ? maxValue + PERCENT_STR : numberFormatter.legendValue(maxValue);
}
if ((isInvalid(min) && isInvalid(minRange.value)) || (isInvalid(max) && isInvalid(minRange.value)) ||
!data.gradient) {
this._preparationGoneWrong = true;
} else {
this._preparationGoneWrong = false;
}
range = this.colorRange = data.colorRange.sort(function (m, n) { return m.value - n.value; });
this.valueRatio = undefined;
this.values = [];
for (index = 0, length = range.length; index < length; index++) {
this.values.push(range[index].value);
}
}
ColorRange.prototype.constructor = ColorRange;
ColorRange.prototype.getValueRatio = function () {
var colorRange = this.colorRange,
currentRange,
index,
length = colorRange.length,
ratio = this.valueRatio,
maxValue = colorRange[length - 1].value,
minValue = colorRange[0].value,
range = maxValue - minValue,
itemValuePercent,
lastValue = 0;
if (ratio) {
return ratio;
}
ratio = this.valueRatio = [];
for (index = 0; index < length; index++) {
currentRange = colorRange[index];
itemValuePercent = (currentRange.value - minValue) / range;
ratio.push((itemValuePercent - lastValue) * 100);
lastValue = itemValuePercent;
}
return ratio;
};
ColorRange.prototype.getCumulativeValueRatio = function () {
var colorRange = this.colorRange,
currentRange,
index,
length = colorRange.length,
firstValue = colorRange[0].value,
lastValue = colorRange[length - 1].value,
ratio = [];
for (index = 0; index < length; index++) {
currentRange = colorRange[index];
ratio.push((currentRange.value - firstValue) / (lastValue - firstValue) * 100);
}
return ratio;
};
ColorRange.prototype.getBoxFill = function (isVertical) {
var colorRange = this.colorRange,
currentRange,
index,
length = colorRange.length,
color = [],
raphColorArg,
angle;
angle = isVertical ? 90 : 0;
for (index = 0; index < length; index++) {
currentRange = colorRange[index];
color.push(currentRange.code);
}
raphColorArg = {
FCcolor: {
alpha: '100,100,100',
angle: angle,
color: color.join(COMMA_STR),
ratio: this.getValueRatio().join(COMMA_STR)
}
};
return toRaphaelColor(raphColorArg);
};
ColorRange.prototype.getColorByValue = function (nVal) {
var valueArr = this.values,
colorRange = this.colorRange,
length,
index,
rangeOutSideColor,
color;
if (nVal === undefined || nVal === null) {
return;
}
for (index = 0, length = valueArr.length; index < length; index++) {
if (nVal === valueArr[index]) {
color = colorRange[index].code;
break;
} else if (!index && nVal < valueArr[index]) {
rangeOutSideColor = true;
break;
} else if (index === length - 1 && nVal > valueArr[index]) {
rangeOutSideColor = true;
break;
}else if (nVal > valueArr[index] && nVal < valueArr[index + 1]) {
color = getColorBetween(colorRange[index], colorRange[index + 1], nVal);
break;
}
}
if (rangeOutSideColor) {
return;
}
return color;
};
function GradientLegend () {
LegendBase.apply(this, arguments);
}
GradientLegend.prototype = Object.create(LegendBase.prototype);
GradientLegend.prototype.constructor = GradientLegend;
FusionCharts.register('component', ['gradientLegend', 'gradientLegend', {
pIndex: 1,
enabled: false,
/*
* Initializes the component.
* Calling this would create instance of colorRange. Which can be used to access the api of the same.
* Configure is called when the second level of init is called. The second level is explained below.
*
*
* Example use
* legend = new (FusionCharts.register('component',['gradientLegend', 'gradientLegend']))();
*
* 1st level - fn = legend.init({chart: chart});
* 2nd level - fn({min: 100, max: 1000, maxOtherSide: 150});
*
* Or putting it together - legend.init({chart: chart, dataExtremes: {min: 100, max: 1000, maxOtherSide: 150}})
* colorRange = legend.colorRange
*
*
* @param options {Object} - A simple key value pair needed for init.
* {
* chart: chartInstance,
* dataExtremes: optional data extreme of the datasets in the form
* {
* max: maximum value,
* min: minimum value,
* maxOtherSide: Optional maximum other side measurement. This takes the maximum allowed height for
* a horizontal legend and width for vertical legend.
* },
* }
*
* @return {Function | undefined} - If the dataExtremes are provided return undefined, else returns a function
* where it is to be fed.
*/
init: function (options) {
var componentAPI = this,
iapi = options.chart,
dataExtremes,
cr,
nData,
fcChart;
/*
* Performs 2nd level of initialization.
* Injects the colorRange in the API object.
*
* @param de {Object} - A simple key value pair needed for 2nd level of init. See the above dataExtremes obj
*/
function continueInit (de) {
// Extracts raw FC Chart data and parse it to prepare data for colorRange. This kind of acts like a
// adapter.
componentAPI.data = options.chart.jsonData.colorrange;
nData = componentAPI.nData = legendManager.legacyDataParser(componentAPI.data, de);
if (!nData) {
// If no valid data is present, sets a flag not to draw the legend . This will be read by subsequent
// calls.
componentAPI._dontPlot = true;
return;
}
// Starts prepaeparing the drawOptions object which is needed during the final drawing and space
// management.
componentAPI.drawOptions = {
smartLabel: iapi.linkedItems.smartLabel,
colorRange: (componentAPI.colorRange = cr = new ColorRange(nData, de, fcChart)),
maxOtherSide: de.maxOtherSide
};
componentAPI._dontPlot = false;
cr && cr._preparationGoneWrong && (componentAPI._dontPlot = true);
componentAPI._recalculateLogicalSpace = true;
// Parse all the legend releted attrs.
componentAPI._configure();
}
// Initiates the legend manager by providing the access to the chart object.
legendManager.init(options);
// Saves the reference of chart for internal use, in case
fcChart = componentAPI._chart = options.chart;
componentAPI._cpool = componentPoolFactory(fcChart);
if (!(dataExtremes = options.dataExtremes)) {
// If the dataExtremes is not provided, return the function for 2nd level of init
return continueInit;
}
// If provided perform the 2nd level of init internally by giving the feel to the user that init happened
// in one single shot
continueInit(dataExtremes);
},
/*
* Parses all the FC attributes for gradient legend
*/
_configure: function () {
var componentAPI = this,
chart = componentAPI._chart,
chartAttrs = chart.jsonData.chart,
conf = componentAPI.conf = {},
outCanvasBaseFont = chartAttrs.outcnvbasefont,
outCanvasBaseFontSize = chartAttrs.outcnvbasefontsize,
outCanvasBaseFontColor = chartAttrs.outcnvbasefontcolor,
labelStyle = chart.config.dataLabelStyle,
fColor,
fFamily,
fSize,
fWeight,
cfColor,
cfFamily,
cfSize,
cfWeight,
axisBorderColor,
axisBorderAlpha;
conf.caption = pluck(chartAttrs.legendcaption);
conf.legendPosition = pluck(chartAttrs.legendposition, 'bottom').toLowerCase();
conf.showLegend = pluckNumber(chartAttrs.showlegend, 1);
conf.interactiveLegend = pluckNumber(chartAttrs.interactivelegend, 1);
conf.showLegendLabels = pluckNumber(chartAttrs.showlegendlabels, 1);
fColor = chartAttrs.legenditemfontcolor || outCanvasBaseFontColor;
fFamily = chartAttrs.legenditemfont || outCanvasBaseFont;
fSize = chartAttrs.legenditemfontsize || outCanvasBaseFontSize;
fWeight = pluckNumber(chartAttrs.legenditemfontbold, 0);
cfColor = chartAttrs.legendcaptionfontcolor || outCanvasBaseFontColor;
cfFamily = chartAttrs.legendcaptionfont || outCanvasBaseFont;
cfSize = chartAttrs.legendcaptionfontsize || outCanvasBaseFontSize;
cfWeight = pluckNumber(chartAttrs.legendcaptionfontbold, 1);
axisBorderColor = chartAttrs.legendaxisbordercolor ?
hashify(dehashify(chartAttrs.legendaxisbordercolor)) : undefined;
axisBorderAlpha = axisBorderColor ? pluckNumber(chartAttrs.legendaxisborderalpha, 100) / 100 :
undefined;
// We do this like this so that, it becomes easy to override the default configuartion. The default conf was
// created in this format.
conf.axisTextItemConf = {
style: {
fill: fColor ? convertColor(pluck(fColor)) : labelStyle.color,
fontFamily: fFamily ? pluck(fFamily) : labelStyle.fontFamily,
fontSize: fSize ? pluckNumber(fSize) : labelStyle.fontSize.match(/\d+/)[0],
fontWeight: fWeight ? 'bold' : labelStyle.fontWeight
}
};
conf.legendCaptionConf = {
style : {
fill: cfColor ? convertColor(pluck(cfColor)) : labelStyle.color,
fontFamily: cfFamily ? pluck(cfFamily) : labelStyle.fontFamily,
fontSize: cfSize ? pluckNumber(cfSize) : labelStyle.fontSize.match(/\d+/)[0],
fontWeight: cfWeight ? 'bold' : labelStyle.fontWeight,
fontStyle: 'normal'
}
};
conf.legendAxisConf = {
legendAxisHeight: 11,
style : {
stroke: axisBorderColor,
'stroke-opacity': axisBorderAlpha
},
line: {
style: {
stroke: convertColor(pluck(chartAttrs.legendscalelinecolor, 'FFF8E9'),
pluckNumber(chartAttrs.legendscalelinealpha, 100)),
'stroke-width': pluckNumber(chartAttrs.legendscalelinethickness)
}
}
};
conf.sliderGroupConf = {
showTooltip: pluckNumber(chartAttrs.showtooltip, 1),
outerCircle : {
rFactor: pluckNumber(chartAttrs.sliderdiameterfactor),
style: {
stroke: convertColor(pluck(chartAttrs.legendpointerbordercolor, '757575'),
pluckNumber(chartAttrs.legendpointerborderalpha, 100))
}
},
innerCircle : {
rFactor: pluckNumber(chartAttrs.sliderholediameterfactor),
style: {
stroke: convertColor(pluck(chartAttrs.legendpointercolor, 'FFFFFF'),
pluckNumber(chartAttrs.legendpointeralpha, 100))
}
}
};
conf.legendCarpetConf = {
spreadFactor : pluckNumber(chartAttrs.legendspreadfactor),
allowDrag: !!pluckNumber(chartAttrs.legendallowdrag, 0),
captionAlignment: pluck(chartAttrs.legendcaptionalignment, 'center'),
style : {
'fill' : convertColor(pluck(chartAttrs.legendbgcolor, 'e4d9c1'),
pluckNumber(chartAttrs.legendbgalpha, 100)),
'stroke' : convertColor(pluck(chartAttrs.legendbordercolor, 'c4b89d'),
pluckNumber(chartAttrs.legendborderalpha, 100)),
'stroke-width': pluckNumber(chartAttrs.legendborderthickness, 1)
}
};
},
/*
* This takes care of the instance creation of various component based on attrs. Dependency injection /
* component injection is managed from here.
* This is not meant to be called from outside. This gets called internally.
*/
postConfigureInit : function () {
var componentAPI = this,
conf = componentAPI.conf,
caption,
carpet,
axis,
sGroup,
gl,
ovrdConf,
ovrdTextConf,
body,
labels,
values;
// Container for the component instances.
componentAPI.elem = {};
// The details of the various components and their relationship is written at the top.
if (conf.caption) {
// If caption is present, create a instance of the same
ovrdConf = merge(legendManager.getDefaultConf('legendCaptionConf'), conf.legendCaptionConf);
caption = new LegendCaption(conf.caption, ovrdConf);
}
if (conf.interactiveLegend) {
// If interactiveLegend is attr is present, creates the sliders of the legend.
// Override the default conf with the user given one.
ovrdConf = merge(legendManager.getDefaultConf('sliderGroupConf'), conf.sliderGroupConf);
componentAPI.elem.sGroup = sGroup = new SliderGroup(ovrdConf);
componentAPI.listeners && componentAPI.listeners.length > 0 &&
sGroup.registerListener.apply(sGroup, componentAPI.listeners);
}
ovrdConf = merge(legendManager.getDefaultConf('legendCarpetConf'), conf.legendCarpetConf);
if (conf.legendPosition === 'bottom') {
// If the legend to be placed at bottom
// The reference side would be width and will be covered the complete canvasWidth.
// The max of other side is given by maxOtherSide.
// The margin is taken from canvasLeft
componentAPI.drawOptions.refSideKey = 'canvasWidth';
componentAPI.drawOptions.refOffsetKey = 'canvasLeft';
// Creates a horizontal capet, axis, labels and values component
carpet = new LegendCarpet(ovrdConf);
ovrdTextConf = merge(legendManager.getDefaultConf('axisTextItemConf'), conf.axisTextItemConf);
body = new LegendBody(componentAPI.drawOptions.colorRange,
legendManager.getDefaultConf('legendBodyConf'), ovrdTextConf);
axis = new LegendAxis(merge(legendManager.getDefaultConf('legendAxisConf'), conf.legendAxisConf));
conf.showLegendLabels && (labels = new LegendLabels(ovrdTextConf));
values = new LegendValues(ovrdTextConf);
} else {
// If the legend to be placed at bottom
// The reference side would be height and will be covered the complete canvasWidth.
// The max of other side is given by maxOtherSide.
// The margin is taken from canvasTop
componentAPI.drawOptions.refSideKey = 'canvasHeight';
componentAPI.drawOptions.refOffsetKey = 'canvasTop';
ovrdTextConf = merge(legendManager.getDefaultConf('axisTextItemConf'), conf.axisTextItemConf);
carpet = new VerticalLegendCarpet(ovrdConf);
body = new VerticalLegendBody(componentAPI.drawOptions.colorRange,
legendManager.getDefaultConf('legendBodyConf'), ovrdTextConf);
axis = new VerticalLegendAxis(merge(legendManager.getDefaultConf('legendAxisConf'),
conf.legendAxisConf));
conf.showLegendLabels && (labels = new VerticalLegendLabels(ovrdTextConf));
values = new VerticalLegendValues(ovrdTextConf);
}
// If the slider group is defined, inject as component. Slider group is component of axis.
sGroup && axis.addCompositions(sGroup, compositionKeys.RANGE);
// Adds component to the body if available
labels && body.addCompositions(labels, compositionKeys.AXIS_LABEL);
body.addCompositions(axis, compositionKeys.LEGEND_AXIS);
body.addCompositions(values, compositionKeys.AXIS_VALUE);
// Adds the body and the caption to the carpet
caption && carpet.addCompositions(caption, compositionKeys.CAPTION);
carpet.addCompositions(body, compositionKeys.LEGEND_BODY);
// Finally creates the gradient legend.
componentAPI.elem.gl = gl = new GradientLegend(carpet, componentAPI._cpool);
},
notifyWhenUpdate: function (fn, context, params) {
var componentAPI = this,
rGroup;
rGroup = componentAPI.elem && componentAPI.elem.sGroup;
if (rGroup) {
rGroup.registerListener(fn, context, params);
} else {
componentAPI.listeners = [fn, context, params];
}
},
dispose: function () {
var componentAPI = this;
componentAPI.elem && componentAPI.elem.gl && componentAPI.elem.gl.dispose();
componentAPI.elem = {};
},
getLogicalSpace: function (maxOtherSide) {
var componentAPI = this,
conf = componentAPI.conf,
zeroArea = {
height: 0,
width: 0
},
drawOptions = componentAPI.drawOptions,
refSideKey,
chart = componentAPI._chart,
refOffsetKey;
// @todo redundancy in calculating the reference side. Make it from a organized procedure call.
if (!componentAPI._recalculateLogicalSpace) {
refSideKey = drawOptions.refSideKey;
refOffsetKey = drawOptions.refOffsetKey;
componentAPI.drawOptions.refSide = chart.config[refSideKey];
componentAPI.drawOptions.refOffset = chart.config[refOffsetKey];
(componentAPI._logicalArea = componentAPI.elem.gl.getLogicalSpace(componentAPI.drawOptions, true));
return componentAPI._logicalArea || zeroArea;
}
if (componentAPI._dontPlot) {
return zeroArea;
}
componentAPI._recalculateLogicalSpace = false;
componentAPI.postConfigureInit();
if (!conf.showLegend) {
return zeroArea;
}
refSideKey = drawOptions.refSideKey;
refOffsetKey = drawOptions.refOffsetKey;
componentAPI.drawOptions.refSide = chart.config[refSideKey];
componentAPI.drawOptions.refOffset = chart.config[refOffsetKey];
componentAPI.drawOptions.maxOtherSide = maxOtherSide || componentAPI.drawOptions.maxOtherSide;
return componentAPI.elem.gl &&
(componentAPI._logicalArea = componentAPI.elem.gl.getLogicalSpace(componentAPI.drawOptions, true));
},
resetLegend: function() {
var componentAPI = this,
rGroup;
rGroup = componentAPI.elem && componentAPI.elem.sGroup;
if (rGroup) {
rGroup.reset();
}
},
clearListeners: function () {
var componentAPI = this,
rGroup;
rGroup = componentAPI.elem && componentAPI.elem.sGroup;
if (rGroup) {
rGroup.clearListeners();
}
},
draw: function (x, y, options) {
var componentAPI = this,
conf = componentAPI.conf,
measurement,
node;
if (componentAPI._dontPlot) {
return;
}
componentAPI._cpool.init(options.paper);
if (!conf.showLegend) {
componentAPI.enabled = false;
return;
}
componentAPI.drawOptions.paper = options.paper;
componentAPI.drawOptions.parentGroup = options.parentGroup;
componentAPI.drawOptions.x = x;
componentAPI.drawOptions.y = y;
componentAPI.drawOptions.maxOtherSide = componentAPI.drawOptions.maxOtherSide || options.maxOtherSide;
node = componentAPI.elem.gl.draw(componentAPI.drawOptions);
measurement = node.getBBox();
conf.xPos = measurement.x;
conf.yPos = measurement.y;
conf.height = measurement.height;
conf.width = measurement.width;
componentAPI.enabled = true;
}
}]);
}]);
/**!
* @license FusionCharts JavaScript Library
* Copyright FusionCharts Technologies LLP
* License Information at <http://www.fusioncharts.com/license>
*
* @version 3.12.0
*/
/**
* @private
* @module fusioncharts.renderer.javascript.maps
* @requires fusioncharts.renderer.javascript.legend-gradient
* @export fusioncharts.maps.js
*/
FusionCharts.register('module', ['private', 'modules.renderer.js-maps', function () {
var global = this,
win = global.window,
lib = global.hcLib,
renderer = lib.chartAPI,
chartAPI = renderer,
addEvent = lib.addEvent,
removeEvent = lib.removeEvent,
// fireMouseEvent = lib.fireMouseEvent,
// doc = win.document,
pluck = lib.pluck,
imprint = lib.imprint,
extend2 = lib.extend2,
parseTooltext = lib.parseTooltext,
pluckNumber = lib.pluckNumber,
COMPONENT = 'component',
getLightColor = lib.graphics.getLightColor,
dropHash = lib.dropHash,
HASHSTRING = lib.HASHSTRING,
parseUnsafeString = lib.parseUnsafeString,
getDashStyle = lib.getDashStyle,
schedular = lib.schedular,
// jobList = lib.jobList,
// pluckFontSize = lib.pluckFontSize,
defaultPaletteOptions = extend2(lib.defaultPaletteOptions, {
foregroundcolor: '333333',
foregroundalpha: '100',
foregrounddarkcolor: '111111',
foregrounddarkalpha: '100',
foregroundlightcolor: '666666',
foregroundlightalpha: '100',
backgroundlightcolor: 'FFFFFF',
backgroundlightalpha: '100',
backgroundlightangle: 90,
backgroundlightratio: '',
backgroundcolor: 'FFFFCC',
backgroundalpha: '100',
backgrounddarkcolor: 'ffcc66',
backgrounddarkalpha: '100',
backgrounddarkangle: 270,
backgrounddarkratio: '',
shadow: 1
}),
PXSTRING = 'px',
setLineHeight = lib.setLineHeight,
// parseUnsafeString = lib.parseUnsafeString,
// getFirstColor = lib.getFirstColor,
convertColor = lib.graphics.convertColor,
// hashify = lib.hashify,
UNDEFINED,
// The default value for stroke-dash attribute.
DASH_DEF = 'none',
userAgent = win.navigator.userAgent,
isIE = /msie/i.test(userAgent) && !win.opera,
/** @todo fix this. */
// isWebKit = /AppleWebKit/.test(userAgent),
// isStrokeReg = /stroke/ig,
hasSVG = lib.hasSVG,
COMMASPACE = ', ',
COMMA = ',',
// QUOTE = '"',
// QUOTECOMMAQUOTE = '","', // CRLFQUOTE = '\r\n"',
BLANK = '',
POSITION_TOP = 'top',
POSITION_BOTTOM = 'bottom',
POSITION_RIGHT = 'right',
POSITION_LEFT = 'left',
POSITION_MIDDLE = 'middle',
POSITION_CENTER = 'center',
POSITION_START = 'start',
POSITION_END = 'end',
SHAPE_CIRCLE = 'circle',
// NONE = 'none',
// TILE = 'tile',
// FILL = 'fill',
// FIT = 'fit',
// BOLD = 'bold',
// NORMAL = 'normal',
CRISP = 'crisp',
GEO = 'geo',
INNERRADIUSFACTOR = 0.6,
math = win.Math,
mathMin = math.min,
mathMax = math.max,
isStrokeReg = /stroke/ig,
isWebKit = /AppleWebKit/.test(userAgent),
mathCeil = math.ceil,
MARKER_ITEM_KEY = 'items',
mathPI = Math.PI,
deg2rad = mathPI / 180,
hasTouch = lib.hasTouch,
CREDITS = false && (!lib.CREDIT_REGEX.test(win.location.hostname)),
toRaphaelColor = lib.toRaphaelColor,
TRACKER_FILL = 'rgba(192,192,192,' + (isIE ? 0.002 : 0.000001) + ')', // invisible but clickable
// TEXT_ANCHOR_MAP = {
// left : 'start',
// right: 'end',
// center: 'middle'
// },
extend = function (a, b) { /** @todo refactor dependency */
var n;
if (!a) {
a = {};
}
for (n in b) {
a[n] = b[n];
}
return a;
},
/* CHeck if the mouseEvent was triggered within the canvas area and update its configurations wrt chart.
* @param e {MouseEvent} - The original mouse event.
* @param chart {Object} - The renderingAPI reference
* @return The sanitized event adding the chartX, chartY property (pixel values wrt to chart specifically) and
add an insideCanvas property to denote if the interaction was within the canvas area
*/
isWithinCanvas = function (e, chart) {
var mousePos = lib.getMouseCoordinate(chart.linkedItems.container,e),
/*converts the original mouse event to a Fusion
Charts event( that has chartX, chartY, pageX and pageY as its property)*/
chartX = mousePos.chartX,
chartY = mousePos.chartY,
chartConfig = chart.config,
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;
},
/*
* Function to check if the element is whose scpoe the function is being
* called is a descendant of the passed element or not.
* ~returns {undefined}
*/
// isDescendant = function (parent) {
// var ele = this,
// currParent = ele.parentNode;
// if (!currParent) {
// return false;
// }
// while (currParent) {
// if (currParent === doc.documentElement) {
// return false;
// }
// else if (currParent === parent) {
// return true;
// }
// else {
// currParent = currParent.parentNode;
// }
// }
// return false;
// },
/**
* Reduces the pain of writing loads of object structures while creating
* FusionCharts specific color configurations
*/
colorize = function (original, obj) {
var col = !obj ? {
FCcolor: original
} : extend(original.FCcolor, obj);
col.toString = toRaphaelColor;
return col;
},
pruneStrokeAttrs = function (obj, thicknessModifier) {
var key,
returnObj = {};
thicknessModifier = thicknessModifier || 1;
if (!obj || typeof obj !== 'object') {
return returnObj;
}
for (key in obj) {
if (!isStrokeReg.test(key)) {
if (key === 'stroke-width') {
returnObj[key] = Number(obj[key]) / thicknessModifier;
if (isWebKit) {
// webkit issue fix
returnObj[key] = (returnObj[key] && mathCeil(returnObj[key])) || 0;
}
}
else {
returnObj[key] = obj[key];
}
}
}
return returnObj;
},
ColorPalette = function (hash, index) {
var subpalette,
key;
this.index = index;
for (key in hash) {
subpalette = defaultPaletteOptions[hash[key]];
this[key] = subpalette instanceof Array ? subpalette[index] : subpalette;
}
},
getTextWrapWidth = {
right: function (w, x) {
return x;
},
left: function (w, x) {
return w - x;
},
center: function (w, x) {
return mathMin(x, w - x) * 2;
}
},
getTextWrapHeight = {
top: function (h, y) {
return y;
},
middle: function (h, y) {
return mathMin(y, h - y) * 2;
},
bottom: function (h, y) {
return h - y;
}
},
convertArrayToIdMap = function (arr) {
var i = (arr && arr.length) || 0,
ret = {},
item;
while (i--) {
item = arr[i];
if (item.id !== UNDEFINED) {
ret[item.id.toLowerCase()] = item;
}
}
return ret;
},
/**
* Converts an array of objects to a hash based on a key present in the objects
* of the array.
*
* @param {array} arr Array containing the objects.
* @param {string} idKey The key in the array objects whose value is used to
* create the keys in the returnObj. The values of this key should be
* unique across all objects in the array.
*
* @returns {object} returnObj Object with the idKey as the key and
* objects as the values.
*/
convertToObj = function (arr, idKey) {
var i = (arr && arr.length) || false,
key = idKey || 'id',
returnObj = {},
item;
if (!arr) {
return arr;
}
while (i--) {
item = arr[i];
(item[key] !== UNDEFINED) && (returnObj[item[key].toLowerCase()] = item);
}
return returnObj;
},
KdTree = lib.KdTree;
// Extend events
extend(lib.eventList, {
entityrollover: 'FC_Event',
entityrollout: 'FC_Event'
});
/**
* Definition of root geo-base. This definition has all the vizualization
* routines required to draw maps (along with all base functionalities).
*
* All maps should inherit from this and simply redefine its data-definition
* like "entities", "width", "height", "name", etc.
*/
chartAPI(GEO, {
name: GEO,
friendlyName: 'Map',
revision: 1,
creditLabel: CREDITS,
hasCanvas: true,
standaloneInit: false, // this map cannot be displayed alone
defaultDatasetType: 'maps',
// Define the charts original (default) width and height. The user x,y
// will be scaled as per this.
baseWidth: 400,
baseHeight: 300,
baseScaleFactor: 1,
defaultSeriesType: GEO,
fireGroupEvent: true,
legendposition: 'right',
hasGradientLegend: true,
isMap: true,
/*
* Definition for geo entities (their labels and entity outlines)
* ~type {object.<string, Object>}
*/
entities: {
/**
* Data-structure definition for map entities.
* @example
"ab": {
outlines: [
// Outline can have more than once closed paths idicating
// distinct and disjoint areas.
[M,188.6,29.4,L,189.8,30.05,189.8,30.1,Z]
],
label: "AB Entity",
shortLabel: "AB",
labelPosition: [158.75,57.9],
labelConnectors: []
}
*/
},
init: function (container, dataObj, chartobj, callBack) {
var iapi = this,
components,
Caption,
chartAttrs;
chartAttrs = dataObj.chart = (dataObj.chart || dataObj.graph ||
dataObj.map || { });
iapi.jsonData = dataObj;
components = iapi.components = iapi.components || (iapi.components = {});
// Map specific component
components.mapAnnotations = components.mapAnnotations || (components.mapAnnotations =
new lib.Annotations());
iapi.components.colorPalette = new ColorPalette (this.colorPaletteMap,
(chartAttrs.palette > 0 && chartAttrs.palette < 6 ?
chartAttrs.palette : pluckNumber(iapi.paletteIndex, 1)) - 1);
Caption = FusionCharts.register (COMPONENT, ['caption', 'MapCaption']);
components.caption || (components.caption = new Caption());
chartAPI.mscartesian.init.call(iapi, container, dataObj, chartobj, callBack);
},
configure: function () {
var iapi = this,
config,
jsonData = iapi.jsonData,
chartAttrs = jsonData.chart || jsonData.map,
markerAttrs = jsonData.markers,
// Select the color palette
palette = new ColorPalette(this.colorPaletteMap,
(chartAttrs.palette > 0 && chartAttrs.palette < 6 ?
chartAttrs.palette : pluckNumber(iapi.paletteIndex, 1)) - 1),
inCancolor,
inCanFontFamily,
inCanFontSize,
style,
entityBorderColor = pluck(chartAttrs.entitybordercolor, chartAttrs.bordercolor,
palette.plotbordercolor),
entityFillColor = pluck(chartAttrs.entityfillcolor, chartAttrs.fillcolor, palette.plotfillcolor),
entityFillAlpha = pluck(chartAttrs.entityfillalpha, chartAttrs.fillalpha, palette.plotfillalpha),
entityFillRatio = pluck(chartAttrs.entityfillratio, chartAttrs.fillratio, palette.plotfillratio),
entityFillAngle = pluck(chartAttrs.entityfillangle, chartAttrs.fillangle, palette.plotfillangle),
nullEntityColor = pluck(chartAttrs.nullentityfillcolor, chartAttrs.nullentitycolor, entityFillColor),
markerDataEnabled = pluckNumber(chartAttrs.usevaluesformarkers,
jsonData.markers && jsonData.markers.items && jsonData.markers.items.length,
!(jsonData.markers &&
(jsonData.markers.application && jsonData.markers.application.length &&
jsonData.markers.definition && jsonData.markers.definition.length)));
iapi.base.base.configure.call(this);
config = iapi.config;
config.origMarginTop = pluckNumber(chartAttrs.charttopmargin, chartAttrs.maptopmargin, 11);
config.origMarginLeft = pluckNumber(chartAttrs.chartleftmargin, chartAttrs.mapleftmargin, 11);
config.origMarginBottom = pluckNumber(chartAttrs.chartbottommargin, chartAttrs.mapbottommargin, 11);
config.origMarginRight = pluckNumber(chartAttrs.chartrightmargin, chartAttrs.maprightmargin, 11);
style = config.style;
inCancolor = style.inCancolor;
inCanFontFamily = style.inCanfontFamily;
inCanFontSize = style.inCanfontSize;
config.entityOpts = {
baseScaleFactor: iapi.baseScaleFactor,
dataLabels: {
style: {
fontFamily: inCanFontFamily,
fontSize: inCanFontSize,
lineHeight: style.inCanLineHeight,
// The color value is also copied back from style to the main
// 'dataLabels' object
color: style.inCancolor
}
},
fillColor: entityFillColor,
fillAlpha: entityFillAlpha,
fillRatio: entityFillRatio,
fillAngle: entityFillAngle,
borderColor: entityBorderColor,
borderAlpha: pluck(chartAttrs.entityborderalpha, chartAttrs.borderalpha, iapi.borderAlpha, '100'),
borderThickness: pluckNumber(chartAttrs.showentityborder, chartAttrs.showborder, 1) ?
pluckNumber(chartAttrs.entityborderthickness,chartAttrs.borderthickness, 1) : 0,
scaleBorder: pluckNumber(chartAttrs.scaleentityborder, chartAttrs.scaleborder, 0),
hoverFillColor: pluck(chartAttrs.entityfillhovercolor, chartAttrs.hoverfillcolor,
chartAttrs.hovercolor, palette.plothoverfillcolor),
hoverFillAlpha: pluck(chartAttrs.entityfillhoveralpha, chartAttrs.hoverfillalpha,
chartAttrs.hoveralpha, palette.plothoverfillalpha),
hoverFillRatio: pluck(chartAttrs.entityfillhoverratio, chartAttrs.hoverfillratio,
chartAttrs.hoverratio, palette.plothoverfillratio),
hoverFillAngle: pluck(chartAttrs.entityfillhoverangle, chartAttrs.hoverfillangle,
chartAttrs.hoverangle, palette.plothoverfillangle),
hoverBorderThickness: pluck(chartAttrs.entityborderhoverthickness, chartAttrs.hoverborderthickness),
hoverBorderColor: pluck(chartAttrs.entityborderhovercolor, palette.plotbordercolor),
hoverBorderAlpha: pluck(chartAttrs.entityborderhoveralpha, palette.plotborderalpha),
nullEntityColor: nullEntityColor,
nullEntityAlpha: pluck(chartAttrs.nullentityfillalpha, chartAttrs.nullentityalpha, entityFillAlpha),
nullEntityRatio: pluck(chartAttrs.nullentityfillratio, chartAttrs.nullentityratio, entityFillRatio),
nullEntityAngle: pluck(chartAttrs.nullentityfillangle, chartAttrs.nullentityangle, entityFillAngle),
connectorColor: pluck(chartAttrs.labelconnectorcolor, chartAttrs.connectorcolor, inCancolor),
connectorAlpha: pluck(chartAttrs.labelconnectoralpha, chartAttrs.connectoralpha, '100'),
connectorThickness: pluckNumber(chartAttrs.labelconnectorthickness, chartAttrs.borderthickness, 1),
showHoverEffect: pluckNumber(chartAttrs.showentityhovereffect, chartAttrs.usehovercolor,
chartAttrs.showhovereffect, 1),
hoverOnNull: pluckNumber(chartAttrs.hoveronnull, chartAttrs.entityhoveronnull, 1),
labelPadding: pluckNumber(chartAttrs.labelpadding, 5),
showLabels: pluckNumber(chartAttrs.showlabels, 1),
labelsOnTop: pluckNumber(chartAttrs.entitylabelsontop, 1),
includeNameInLabels: pluckNumber(chartAttrs.includenameinlabels, 1),
includeValueInLabels: pluckNumber(chartAttrs.includevalueinlabels, 0),
useSNameInTooltip: pluckNumber(chartAttrs.usesnameintooltip, 0),
useShortName: pluckNumber(chartAttrs.usesnameinlabels, 1),
labelSepChar: pluck(chartAttrs.labelsepchar, COMMASPACE),
showTooltip: pluckNumber(chartAttrs.showentitytooltip,
chartAttrs.showtooltip, 1),
tooltipSepChar: pluck(chartAttrs.tooltipsepchar, ', '),
tooltext: chartAttrs.entitytooltext,
hideNullEntities: pluckNumber(chartAttrs.hidenullentities, 0),
showHiddenEntityBorder: pluckNumber(chartAttrs.showhiddenentityborder, 1),
showNullEntityBorder: pluckNumber(chartAttrs.shownullentityborder, 1),
hiddenEntityColor: pluck(chartAttrs.hiddenentitycolor,
chartAttrs.hiddenentityfillcolor,
(chartAttrs.hiddenentityalpha ||
chartAttrs.hiddenentityfillalpha ?
nullEntityColor: 'ffffff')),
hiddenEntityAlpha: pluck(chartAttrs.hiddenentityalpha,
chartAttrs.hiddenentityfillalpha, 0.001),
// Plot shadow effect.
shadow: pluckNumber(chartAttrs.showshadow,
iapi.defaultPlotShadow, palette.shadow)
};
config.markerOpts = {
dataLabels: {
style: {
fontFamily: pluck(chartAttrs.markerfont, inCanFontFamily),
fontSize: pluckNumber(chartAttrs.markerfontsize, parseInt(inCanFontSize, 10)),
fontColor: pluck(chartAttrs.markerfontcolor, inCancolor)
}
},
showTooltip: pluckNumber(chartAttrs.showmarkertooltip, chartAttrs.showtooltip, 1),
showLabels: pluckNumber(chartAttrs.showmarkerlabels,
chartAttrs.showlabels, 1),
showHoverEffect: pluckNumber(chartAttrs.showmarkerhovereffect, 1),
labelPadding: pluck(chartAttrs.markerlabelpadding, '5'),
labelWrapWidth: pluckNumber(chartAttrs.markerlabelwrapwidth, 0),
labelWrapHeight: pluckNumber(chartAttrs.markerlabelwrapheight, 0),
fillColor: pluck(chartAttrs.markerfillcolor, chartAttrs.markerbgcolor,
palette.markerfillcolor), // has a legacy attr
fillAlpha: pluck(chartAttrs.markerfillalpha, palette.markerfillalpha),
fillAngle: pluck(chartAttrs.markerfillangle, palette.markerfillangle),
fillRatio: pluck(chartAttrs.markerfillratio, palette.markerfillratio),
fillPattern: pluck(chartAttrs.markerfillpattern, palette.markerbgpattern),
hoverFillColor: chartAttrs.markerfillhovercolor,
hoverFillAlpha: chartAttrs.markerfillhoveralpha,
hoverFillRatio: chartAttrs.markerfillhoverratio,
hoverFillAngle: chartAttrs.markerfillhoverangle,
borderThickness: pluck(chartAttrs.markerborderthickness, 1),
borderColor: pluck(chartAttrs.markerbordercolor, palette.markerbordercolor),
borderAlpha: pluckNumber(chartAttrs.markerborderalpha, palette.markerborderalpha),
hoverBorderThickness: chartAttrs.markerborderhoverthickness,
hoverBorderColor: chartAttrs.markerborderhovercolor,
hoverBorderAlpha: chartAttrs.markerborderhoveralpha,
radius: pluckNumber((chartAttrs.markerradius && lib.trimString(chartAttrs.markerradius)), 7),
shapeId: pluck(chartAttrs.defaultmarkershape, SHAPE_CIRCLE),
labelSepChar: pluck(chartAttrs.labelsepchar, COMMASPACE),
tooltipSepChar: pluck(chartAttrs.tooltipsepchar, ', '),
autoScale: pluckNumber(chartAttrs.autoscalemarkers, 0),
tooltext: pluck(markerAttrs && markerAttrs.tooltext, chartAttrs.markertooltext),
/* Value related attributes */
dataEnabled: markerDataEnabled,
valueToRadius: pluckNumber(chartAttrs.markerradiusfromvalue, 1),
valueMarkerAlpha: pluck(chartAttrs.valuemarkeralpha, '75'),
hideNull: pluckNumber(chartAttrs.hidenullmarkers, 0),
nullRadius: pluckNumber(chartAttrs.nullmarkerradius, chartAttrs.markerradius, 7),
adjustViewPort: pluckNumber(chartAttrs.adjustviewportformarkers, 0),
startAngle: pluckNumber(chartAttrs.markerstartangle, 90),
maxRadius: pluckNumber(chartAttrs.maxmarkerradius, 0),
minRadius: pluckNumber(chartAttrs.minmarkerradius, 0),
applyAll: pluckNumber(chartAttrs.applyallmarkers, 0),
shadow: pluckNumber(chartAttrs.showmarkershadow,
chartAttrs.showshadow, 0)
};
config.connectorOpts = {
showHoverEffect: pluckNumber(chartAttrs.showconnectorhovereffect, 1),
thickness: pluckNumber(chartAttrs.connectorthickness,
chartAttrs.markerconnthickness, '2'),
color: pluck(chartAttrs.connectorcolor,
chartAttrs.markerconncolor, palette.markerbordercolor),
alpha: pluck(chartAttrs.connectoralpha,
chartAttrs.markerconnalpha, '100'),
hoverThickness: pluckNumber(chartAttrs.connectorhoverthickness,
chartAttrs.connectorthickness,
chartAttrs.markerconnthickness, '2'),
hoverColor: pluck(chartAttrs.connectorhovercolor,
chartAttrs.connectorcolor, chartAttrs.markerconncolor,
palette.markerbordercolor),
hoverAlpha: pluck(chartAttrs.connectorhoveralpha,
chartAttrs.connectoralpha, chartAttrs.markerconnalpha, '100'),
dashed: pluckNumber(chartAttrs.connectordashed,
chartAttrs.markerconndashed, 0),
dashLen: pluckNumber(chartAttrs.connectordashlen,
chartAttrs.markerconndashlen, 3),
dashGap: pluckNumber(chartAttrs.connectordashgap,
chartAttrs.markerconndashgap, 2),
font: pluck(chartAttrs.connectorfont,
chartAttrs.markerconnfont, inCanFontFamily),
fontColor: pluck(chartAttrs.connectorfontcolor,
chartAttrs.markerconnfontcolor, inCancolor),
fontSize: pluckNumber(chartAttrs.connectorfontsize,
chartAttrs.markerconnfontsize,
parseInt(inCanFontSize, 10)),
showLabels: pluckNumber(chartAttrs.showconnectorlabels, chartAttrs.showmarkerlabels,
chartAttrs.showlabels, 1),
labelBgColor: pluck(chartAttrs.connectorlabelbgcolor,
chartAttrs.markerconnlabelbgcolor,
palette.plotfillcolor),
labelBorderColor: pluck(chartAttrs.connectorlabelbordercolor,
chartAttrs.markerconnlabelbordercolor,
palette.markerbordercolor),
shadow: pluckNumber(chartAttrs.showconnectorshadow,
chartAttrs.showmarkershadow,
chartAttrs.showshadow, 0),
showTooltip: pluckNumber(chartAttrs.showconnectortooltip,
chartAttrs.showmarkertooltip,
chartAttrs.showtooltip, 1),
tooltext: pluck(markerAttrs && markerAttrs.connectortooltext, chartAttrs.connectortooltext),
hideOpen: pluckNumber(chartAttrs.hideopenconnectors, 1)
};
config.adjustViewPortForMarkers = pluckNumber(chartAttrs.adjustviewportformarkers, markerDataEnabled);
},
_createLayers: function () {
var iapi =this,
graphics = iapi.graphics || (iapi.graphics = { }),
components = iapi.components,
paper = components.paper;
//***** Create elements or components required for drawing
// create elements if they are not available
graphics = iapi.graphics;
graphics.backgroundGroup = graphics.backgroundGroup || paper.group ('background');
graphics.canvasGroup = graphics.canvasGroup || paper.group ('canvas');
graphics.shadowGroup = graphics.shadowGroup || paper.group ('shadow');
graphics.datasetGroup = graphics.datasetGroup || paper.group ('dataset');
graphics.datalabelsGroup = graphics.datalabelsGroup || paper.group ('datalabel');
graphics.legendGroup = graphics.legendGroup || paper.group ('legend');
graphics.captionGroup = graphics.captionGroup || paper.group ('caption');
graphics.captionGroup.trackTooltip(true);
graphics.datasetGroup.trackTooltip(true);
graphics.buttonGroup = graphics.buttonGroup || paper.group ('buttons');
iapi._attachMouseEvents();
},
_attachMouseEvents: function () {
var iapi = this,
linkedItems = iapi.linkedItems,
listeners = linkedItems.eventListeners || (linkedItems.eventListeners = []),
containerElem = iapi.linkedItems.container;
//remove any existing events if any
removeEvent(containerElem, hasTouch ? 'touchstart' : 'mousemove', iapi.searchMouseMove);
//adds to event stack.
listeners.push(addEvent(containerElem, 'touchstart mousemove', iapi.searchMouseMove, iapi));
},
searchMouseMove: function (e) {
var mousePos,
chart = e.data,
chartConfig = chart.config,
lastMouseCoordinate = {};
if (!chart.linkedItems.container) {
return;
}
//check if the event is fired within the canvas region.
if ((mousePos = isWithinCanvas(e, chart)) && mousePos.insideCanvas) {
//store the evnt object. This will be used to generate event on same coordinate
chartConfig.lastMouseEvent = e;
lastMouseCoordinate = {
x: mousePos.chartX,
y: mousePos.chartY
};
//search the best neighbouring point of the mouse moved point.
chart._searchNearestNeighbour(lastMouseCoordinate);
}
},
_searchNearestNeighbour: function (point) {
var iapi = this,
datasets = iapi.components.dataset,
markers = datasets[1],
kdPoint;
if (markers) {
if (!markers.components.kDTree) {
return;
}
//searches the nearest neighbouring point of the input point.
kdPoint = markers.getElement(point);
if (kdPoint) {
iapi.config.lastHoveredPoint !== kdPoint && markers.highlightPoint(kdPoint);
}
else {
markers.highlightPoint(false);
}
}
},
_createDatasets: function () {
var iapi = this,
components = iapi.components,
dataObj = iapi.jsonData,
i,
datasetObj,
dsType = iapi.defaultDatasetType,
entityJSONData = dataObj.data || {},
markerJSONData = dataObj.markers,
count = 0,
type,
datasetStore = iapi.components.dataset,
DsClass;
datasetStore = components.dataset || (components.dataset = []);
dsType = dsType && dsType.toLowerCase ();
/// get the DsClass
DsClass = FusionCharts.get('component', ['dataset', 'Entities']);
if (DsClass) {
if (!datasetStore[count]) {
// create the dataset Object
datasetObj = new DsClass ();
datasetStore.push (datasetObj);
datasetObj.chart = iapi;
datasetObj.index = i;
datasetObj.init (entityJSONData);
}
else {
datasetStore[count].JSONData = entityJSONData;
datasetStore[count].configure();
}
count++;
}
DsClass = FusionCharts.get('component', ['dataset', 'Markers']);
datasetObj = datasetStore[count];
if (DsClass && markerJSONData) {
if (!datasetStore[count]) {
// create the dataset Object
datasetObj = new DsClass ();
datasetStore.push (datasetObj);
datasetObj.chart = iapi;
datasetObj.index = i;
datasetObj.init (markerJSONData);
}
else {
datasetObj.index = i;
datasetObj.init (markerJSONData);
datasetStore[count].configure();
}
}
else {
type = datasetObj && datasetObj.type;
if (type === 'markers') {
datasetStore.splice(count, 1);
}
}
},
_parseBackgroundCosmetics: function () {
var iapi = this,
components = iapi.components,
background = components.background,
config = background.config,
palette = iapi.components.colorPalette,
chartAttrs = iapi.jsonData.chart,
showBorder,
chartBorderWidth;
showBorder = config.showBorder = pluckNumber (chartAttrs.showcanvasborder, 1);
config.borderWidth = chartBorderWidth = showBorder ? pluckNumber (chartAttrs.canvasborderthickness, 1) : 0;
config.borderRadius = config.borderRadius = pluckNumber (chartAttrs.canvasborderradius, 0);
config.borderDashStyle = config.borderDashStyle = pluckNumber (chartAttrs.borderdashed, 0) ?
getDashStyle (pluckNumber (chartAttrs.borderdashlen, 4),
pluckNumber (chartAttrs.borderdashgap, 2), chartBorderWidth) : DASH_DEF,
config.borderColor = config.borderColor = convertColor (pluck (chartAttrs.canvasbordercolor,
palette && palette.borderColor));
config.borderAlpha = pluck (chartAttrs.canvasborderalpha,
palette.borderAlpha);
},
_getBackgroundCosmetics: function () {
var iapi = this,
components = iapi.components,
chartAttrs = iapi.jsonData.chart || iapi.jsonData.map,
palette = components.colorPalette;
return {
FCcolor : {
color : pluck (chartAttrs.bgcolor, chartAttrs.canvasbgcolor,
palette.bgcolor),
alpha : pluck (chartAttrs.bgalpha, chartAttrs.canvasbgalpha,
palette.bgalpha),
angle : pluck (chartAttrs.bgangle, chartAttrs.canvasbgangle,
palette.bgangle),
ratio : pluck (chartAttrs.bgratio, chartAttrs.canvasbgratio,
palette.bgratio)
}
};
},
_parseCanvasCosmetics: function () {
var iapi = this,
config = iapi.config,
chartAttrs = iapi.jsonData.chart || iapi.jsonData.map,
components = iapi.components,
canvasConfig = components.canvas.config;
// borderThickness = pluckNumber(chartAttrs.showborder, 1) ? pluckNumber(chartAttrs.borderthickness, 1) : 0;
// chart margins
config.origMarginTop = pluckNumber(chartAttrs.maptopmargin, 11);
config.origMarginLeft = pluckNumber(chartAttrs.mapleftmargin, 11);
config.origMarginBottom = pluckNumber(chartAttrs.mapbottommargin, 11);
config.origMarginRight = pluckNumber(chartAttrs.maprightmargin, 11);
config.origCanvasLeftMargin = pluckNumber(chartAttrs.canvasleftmargin, 0);
config.origCanvasRightMargin = pluckNumber(chartAttrs.canvasrightmargin, 0);
config.origCanvasTopMargin = pluckNumber(chartAttrs.canvastopmargin, 0);
config.origCanvasBottomMargin = pluckNumber(chartAttrs.canvasbottommargin, 0);
canvasConfig.canvasBorderRadius = pluckNumber(chartAttrs.canvasborderradius, 0);
// canvas padding
canvasConfig.origCanvasTopPad = pluckNumber(chartAttrs.canvastoppadding, 0);
canvasConfig.origCanvasBottomPad = pluckNumber(chartAttrs.canvasbottompadding, 0);
canvasConfig.origCanvasLeftPad = pluckNumber(chartAttrs.canvasleftpadding, 0);
canvasConfig.origCanvasRightPad = pluckNumber(chartAttrs.canvasrightpadding, 0);
},
draw: function () {
var iapi = this;
iapi.config.entitiesDrawn = false;
if (iapi.config.hasChartMessage) {
iapi._hide();
iapi.drawChartMessage();
}
else {
//show the chart if hidden
iapi._show();
iapi.chartMenuTools.reset(iapi.components.tb, iapi);
//create any scroll elements if any.
iapi._createToolBox ();
// allocate space to all components
iapi._manageSpace();
// draw elements finally
iapi._updateVisuals();
iapi.inited = true;
iapi.chartInstance.jsVars.drawCount += 1;
iapi.chartInstance.__state.dataReady = true;
iapi.chartInstance.jsVars.hasNativeMessage = false;
}
},
_drawDataset : function () {
var iapi = this,
chartInstance = iapi.chartInstance,
dataset = iapi.components.dataset,
layers = iapi.graphics,
length = dataset.length,
scalingParams = iapi.config.scalingParams,
transformGroup = function () {
return function (event) {
event.detachHandler(); // one time
if (hasSVG) {
layers.datasetGroup && layers.datasetGroup.attr({
transform: scalingParams.transformStr
});
layers.shadowGroup && layers.shadowGroup.attr({
transform: scalingParams.transformStr
});
}
// display the layers for both VML and SVG
layers.datasetGroup && layers.datasetGroup.show();
layers.shadowGroup && layers.shadowGroup.show();
};
},
i;
layers.datasetGroup.hide();
layers.shadowGroup.hide();
iapi.config.entitiesReady = false;
chartInstance.addEventListener('internal.mapdrawingcomplete', transformGroup());
for (i = 0; i < length; i++) {
dataset[i].draw();
}
iapi.checkComplete();
},
/*
* This method to determine if the dimensions of the graphic (map) need to
* be altered to accomodate markers within them.
*
* Only the x and y are used here as this calculation is primarily for the
* properties that will be scaled up and down with the graphic (map).
*
* ~param {object} hc
* ~param {object} fc
*
* ~return {object} The min and max co-ordinates of the markers,
* excluding the space needed for the radius which will be accounted for later.
*/
preliminaryScaling: function () {
var iapi = this,
jsonData = iapi.jsonData,
markerArray = (jsonData.markers && jsonData.markers.items) || [],
i = (markerArray && markerArray.length) || 0,
minX = Infinity,
minY = Infinity,
maxX = -Infinity,
maxY = -Infinity,
x,
y,
item;
while (i--) {
item = markerArray[i];
x = Number(item.x);
y = Number(item.y);
minX = mathMin(minX, x);
minY = mathMin(minY, y);
maxX = mathMax(maxX, x);
maxY = mathMax(maxY, y);
}
return {
x: minX,
y: minY,
x1: maxX,
y1: maxY
};
},
/*
* This method returns the scale factor and translate factors when a graphic
* of provided dimensions has to be scaled to best fit into a view port of
* provided dimensions.
*
* ~param {number} wg, width of the graphic
* ~param {number} hg, height of the graphic
* ~param {number} wv, width of the viewport
* ~param {number} hv, height of the viewport
*
* ~returns {object}
*/
getScalingParameters: function (wg, hg, wv, hv) {
var iapi = this,
aspR = wg / hg,
widthScaleR = (wv / (wg * iapi.baseScaleFactor)),
heightScaleR = (hv / (hg * iapi.baseScaleFactor)),
translateX = 0,
translateY = 0,
scaleFactor,
strokeWidth;
if (widthScaleR > heightScaleR) {
scaleFactor = heightScaleR;
translateX += (wv - (hv * aspR)) / 2;
strokeWidth = (200 / (hg * scaleFactor));
}
else {
scaleFactor = widthScaleR;
translateY += (hv - (wv / aspR)) / 2;
strokeWidth = (200 / (wg * scaleFactor));
}
return {
scaleFactor: scaleFactor,
strokeWidth: strokeWidth,
translateX: translateX,
translateY: translateY
};
},
/*
* Assigns to markers radii normalized using that value of the marker. If
* marker does not have a value then user provided radius or default radius
* is assumed. The bounding box for each marker is then obtained using the
* radius and total overflow on each side of the map canvas is returned in
* an object.
*
* ~param {object} hc
* ~param {object} fc
* ~param {number} scaleFactor, the scaleFactor applied to the map
* ~param {number} xOffset, if a markers co-ordinates are given in -ve
* co-ordinates then an offset needs to be added to the marker position
* to account for it.
* ~param {number} yOffset
*
* ~return {object}
*/
// @todo Need to transfer this function to a more appropriate place
calculateMarkerBounds: function (scaleFactor, xOffset, yOffset) {
var iapi = this,
config = iapi.config,
markerOptions = config.markerOpts,
datasets = iapi.components.dataset,
limits = iapi.getDataLimits(),
dataMin = limits.dataMin,
dataMax = limits.dataMax,
hideNull = markerOptions.hideNull,
nullRadius = markerOptions.nullRadius,
v2r = markerOptions.valueToRadius,
markerDataset,
markerConf,
minR,
maxR,
markerArray,
i,
type,
dataset,
len,
minX = Infinity,
minY = Infinity,
maxX = -Infinity,
maxY = -Infinity,
x,
y,
r,
definition,
item;
for (i = 0, len = datasets.length; i < len; i++) {
dataset = datasets[i];
type = dataset.type;
if (type === 'markers') {
markerDataset = dataset;
}
}
if (markerDataset) {
markerDataset.calculateMarkerRadiusLimits();
markerConf = markerDataset.conf || {};
minR = markerConf.minRadius,
maxR = markerConf.maxRadius;
markerArray = (markerDataset.components && markerDataset.components.markerObjs) || {};
for (i in markerArray) {
item = markerArray[i];
config = item.config;
definition = config.definition || {};
if (config.cleanValue !== null) {
if (v2r && definition.radius === UNDEFINED) {
config.radius = minR + ((maxR - minR) * (config.cleanValue - dataMin) /
(dataMax - dataMin));
}
}
else {
if (hideNull) {
config.__hideMarker = true;
}
else if (config.radius === null) {
config.radius = nullRadius;
}
continue;
}
r = Number(config.radius);
x = (Number(definition.x) + xOffset) * scaleFactor;
y = (Number(definition.y) + yOffset) * scaleFactor;
// These values will be scaled along with the graphic
minX = mathMin(minX, x - r);
minY = mathMin(minY, y - r);
maxX = mathMax(maxX, x + r);
maxY = mathMax(maxY, y + r);
}
}
return {
x: minX,
y: minY,
x1: maxX,
y1: maxY
};
},
_spaceManager: function () {
// todo marge _allocateSpace and _spacemanager
var availableHeight,
iapi = this,
config = iapi.config,
components = iapi.components,
legend = components.legend,
legendPosition = legend.config.legendPos,
chartAttrs = iapi.jsonData.chart,
showBorder = config.showBorder,
translateX = config.origMarginLeft,
translateY = config.origMarginTop,
wg = iapi.baseWidth,
hg = iapi.baseHeight,
scalingParams,
wv,
hv,
sFactor,
extraMarkerSpace = {},
xDifference = 0,
yDifference = 0,
initHv,
initWv,
allottedSpace,
topSpace,
markerOptions = config.markerOpts,
chartBorderWidth =
config.borderWidth = showBorder ? pluckNumber (chartAttrs.borderthickness, 1) : 0;
iapi._allocateSpace ( {
top : chartBorderWidth,
bottom : chartBorderWidth,
left : chartBorderWidth,
right : chartBorderWidth
});
iapi._allocateSpace(iapi._manageActionBarSpace &&
iapi._manageActionBarSpace(config.availableHeight * 0.225) || {});
if (legendPosition === 'right') {
allottedSpace = config.canvasWidth * 0.3;
}
else {
allottedSpace = config.canvasHeight * 0.3;
}
iapi._manageLegendSpace(allottedSpace);
availableHeight = (legendPosition === POSITION_BOTTOM) ? config.canvasHeight * 0.225 :
config.canvasWidth * 0.225;
// a space manager that manages the space for the tools as well as the captions.
topSpace = iapi._manageChartMenuBar(availableHeight);
wv = config.canvasWidth;
hv = config.canvasHeight;
if (markerOptions.dataEnabled) {
if (config.adjustViewPortForMarkers) {
// Calculate the overflow of the scaled properties (x, y)
extraMarkerSpace = iapi.preliminaryScaling();
if (extraMarkerSpace.x1 > wg) {
wg = extraMarkerSpace.x1;
}
if (extraMarkerSpace.x < 0) {
wg += (-extraMarkerSpace.x);
xDifference = (-extraMarkerSpace.x);
}
if (extraMarkerSpace.y1 > hg) {
hg = extraMarkerSpace.y1;
}
if (extraMarkerSpace.y < 0) {
hg += (-extraMarkerSpace.y);
yDifference = (-extraMarkerSpace.y);
}
// Get the scale factor and translate factors
scalingParams = iapi.getScalingParameters(wg, hg, wv, hv);
// Assign radii to the markers that have a value but no radius.
// Calculate the overflow of the radius (unscaled property).
extraMarkerSpace = iapi.calculateMarkerBounds(
scalingParams.scaleFactor * iapi.baseScaleFactor, xDifference, yDifference);
/**
* @todo: Check if the wv and hv become less than a certain limit.
* If they do reduce the radii of the markers.
*/
initHv = hv;
initWv = wv;
if (extraMarkerSpace.x < 0) {
translateX += (-extraMarkerSpace.x);
wv += extraMarkerSpace.x;
}
if (extraMarkerSpace.y < 0) {
translateY += (-extraMarkerSpace.y);
hv += extraMarkerSpace.y;
}
if (extraMarkerSpace.x1 > initWv) {
wv -= (extraMarkerSpace.x1 - initWv);
}
if (extraMarkerSpace.y1 > initHv) {
hv -= (extraMarkerSpace.y1 - initHv);
}
}
else {
// Get the scale factor and translate factors
scalingParams = iapi.getScalingParameters(wg, hg, wv, hv);
// Assign radii to the markers that have a value but no radius.
// Calculate the overflow of the radius (unscaled property).
iapi.calculateMarkerBounds(scalingParams.scaleFactor * iapi.baseScaleFactor,
xDifference, yDifference);
}
// Recalculate the scale factor after accounting for radii.
scalingParams = iapi.getScalingParameters(wg, hg, wv, hv);
translateX += (xDifference * scalingParams.scaleFactor * iapi.baseScaleFactor);
translateY += (yDifference * scalingParams.scaleFactor * iapi.baseScaleFactor);
}
else {
scalingParams = iapi.getScalingParameters(wg, hg, wv, hv);
}
iapi.config.scalingParams = scalingParams;
sFactor = scalingParams.scaleFactor;
scalingParams.translateX = scalingParams.translateX + translateX;
scalingParams.translateY = scalingParams.translateY + translateY + topSpace.top || 0;
scalingParams.sFactor = (sFactor * iapi.baseScaleFactor * 100) / 100;
scalingParams.transformStr = ['t', scalingParams.translateX, ',', scalingParams.translateY,
's', sFactor, ',', sFactor, ',0,0'].join('');
iapi.config.annotationConfig = {
id: GEO,
showbelow: 0,
autoscale: 0,
grpxshift: (scalingParams.translateX ? scalingParams.translateX : 0),
grpyshift: (scalingParams.translateY ? scalingParams.translateY : 0),
xscale: (sFactor ? sFactor * iapi.baseScaleFactor : 1) * 100,
yscale: (sFactor ? sFactor * iapi.baseScaleFactor : 1) * 100,
options: { useTracker: true }
};
iapi.components.mapAnnotations.reset(null, iapi.config.annotationConfig, iapi);
iapi.components.mapAnnotations._renderer && (iapi.components.mapAnnotations._renderer = null);
},
getDataLimits: function () {
var iapi = this,
datasets = iapi.components.dataset,
length = datasets.length,
dataset,
dataMin = +Infinity,
dataMax = -Infinity,
limits,
i;
for (i = 0; i < length; i++) {
dataset = datasets[i];
limits = dataset.getDataLimits();
dataMin = mathMin(dataMin, limits.min);
dataMax = mathMax(dataMax, limits.max);
}
return {
dataMin: dataMin,
dataMax: dataMax
};
},
getEntityPaths: function (copy) {
var returnObj = {},
ents = this.entities,
id;
if (copy) {
for (id in ents) {
returnObj[id] = ents[id];
}
return returnObj;
}
else {
return ents;
}
},
checkComplete: function () {
var iapi = this,
jobList = iapi.getJobList(),
mapAnnotations = iapi.components.mapAnnotations,
mapLabelAnnotations = iapi.components.mapLabelAnnotations || [],
len = mapLabelAnnotations.length,
annotations,
i,
entityLabelsOnTop = iapi.config.entityOpts.labelsOnTop,
annotationGroup,
labelAnnotationGroup,
callback = function () {
iapi.config.labelDrawCount++;
if (iapi.config.labelDrawCount === len && entityLabelsOnTop) {
annotationGroup = mapAnnotations.groups && mapAnnotations.groups[0] &&
mapAnnotations.groups[0].wrapper;
if (annotationGroup) {
for (i = 0; i < len; i++) {
labelAnnotationGroup = mapLabelAnnotations[i].groups[0] &&
mapLabelAnnotations[i].groups[0].wrapper;
labelAnnotationGroup && labelAnnotationGroup.insertAfter(annotationGroup);
}
}
}
},
drawLabelAnnotations = function () {
for (i = 0; i < len; i++) {
annotations = mapLabelAnnotations[i];
jobList.labelDrawID.push(schedular.addJob(annotations.draw, annotations,
[iapi, true], lib.priorityList.label, true, callback));
}
};
iapi.config.labelDrawCount = 0;
if (iapi.config.entityFlag && iapi.config.entitiesReady) {
iapi.config.entityFlag = false;
drawLabelAnnotations();
mapAnnotations.draw(iapi);
iapi.config.markersDrawn = true;
// jobList.labelDrawID.push(schedular.addJob(mapAnnotations.draw, mapAnnotations,
// [iapi], lib.priorityList.label));
global.raiseEvent('internal.mapdrawingcomplete', {
renderer: iapi
}, iapi.chartInstance);
}
},
colorPaletteMap: {
basefontcolor: 'foregroundcolor',
bordercolor: 'foregrounddarkcolor',
borderalpha: 'foregrounddarkalpha',
bgcolor: 'backgroundlightcolor',
bgalpha: 'backgroundlightalpha',
bgangle: 'backgroundlightangle',
bgratio: 'backgroundlightratio',
canvasbordercolor: 'foregrounddarkcolor',
canvasborderalpha: 'foregrounddarkalpha',
canvasbgcolor: 'backgroundlightcolor',
canvasbgalpha: 'backgroundlightalpha',
canvasbgangle: 'backgroundlightangle',
canvasbgratio: 'backgroundlightratio',
tooltipbordercolor: 'foregrounddarkcolor',
tooltipborderalpha: 'foregrounddarkalpha',
tooltipbgcolor: 'backgroundlightcolor',
tooltipbgalpha: 'backgroundlightalpha',
tooltipfontcolor: 'foregroundcolor',
legendbordercolor: 'foregrounddarkcolor',
legendborderalpha: 'foregrounddarkalpha',
markerbordercolor: 'foregroundlightcolor',
markerborderalpha: 'foregroundlightalpha',
markerfillcolor: 'backgrounddarkcolor',
markerfillalpha: 'backgrounddarkalpha',
markerfillangle: 'backgrounddarkangle',
markerfillratio: 'backgrounddarkratio',
plotfillcolor: 'backgroundcolor',
plotfillalpha: 'backgroundalpha',
plotfillangle: 'backgroundangle',
plotfillratio: 'backgroundratio',
plothoverfillcolor: 'backgrounddarkcolor',
plothoverfillalpha: 'backgrounddarkalpha',
plothoverfillangle: 'backgrounddarkangle',
plothoverfillratio: 'backgrounddarkratio',
plotbordercolor: 'foregroundcolor',
plotborderalpha: 'foregroundalpha',
shadow: 'shadow'
},
eiMethods: {
getMapName: function () {
var chart = this,
iapi = chart.jsVars.instanceAPI,
mapName = typeof iapi.name === 'string' && iapi.name.toLowerCase();
return mapName;
},
getEntityList: function () {
var chart = this,
iapi = chart.jsVars.instanceAPI,
datasets = iapi.components.dataset || [],
i,
entities,
dataset,
len = datasets.length,
entityList = [],
data,
entityObj,
config,
type;
for (i = 0; i < len; i++) {
dataset = datasets[i] || [];
type = dataset.type;
if (type === 'entity') {
entities = dataset;
break;
}
}
data = entities.components.data;
len = data.length;
for (i in data) {
if (data.hasOwnProperty(i)) {
entityObj = data[i] || {};
config = entityObj.config || {};
entityList.push({
id: config.id,
originalId: (config.originalId || config.id),
label: config.label,
shortlabel: config.shortLabel,
value: config.value,
formattedValue: config.formattedValue,
toolText: config.toolText
});
}
}
return entityList;
},
getMapAttribute: function () {
var chartObj = this;
global.raiseWarning(this, '12061210581', 'run', 'JavaScriptRenderer~getMapAttribute()',
'Use of deprecated "getMapAttribute()". Replace with "getChartAttribute()".');
return chartObj.getChartAttribute.apply(chartObj, arguments);
},
exportMap: function () {
var chartObj = this;
global.raiseWarning(this, '12061210581', 'run', 'JavaScriptRenderer~exportMap()',
'Use of deprecated "exportMap()". Replace with "exportChart()".');
return chartObj.exportChart &&
chartObj.exportChart.apply(chartObj, arguments);
},
addMarker: function (options) {
var iapi = this.jsVars.instanceAPI,
datasets = iapi.components.dataset || [],
len = datasets.length,
i,
dataset,
markers,
type;
for (i = 0; i < len; i++) {
dataset = datasets[i] || [];
type = dataset.type;
if (type === 'markers') {
markers = dataset;
break;
}
}
if (markers && !markers.addMarkerItem(options)) {
global.raiseWarning(this, '1309264086', 'run', 'MapsRenderer~addMarker()',
'Failed to add marker. Check the options and try again.');
}
},
updateMarker: function (id, options) {
var iapi = this.jsVars.instanceAPI,
datasets = iapi.components.dataset || [],
len = datasets.length,
i,
dataset,
markers,
type;
for (i = 0; i < len; i++) {
dataset = datasets[i] || [];
type = dataset.type;
if (type === 'markers') {
markers = dataset;
break;
}
}
if (markers && id) {
id = (id + BLANK).toLowerCase();
markers.updateMarkerItem(id, options);
}
},
removeMarker: function (id) {
var iapi = this.jsVars.instanceAPI,
datasets = iapi.components.dataset || [],
len = datasets.length,
i,
dataset,
markers,
type;
for (i = 0; i < len; i++) {
dataset = datasets[i] || [];
type = dataset.type;
if (type === 'markers') {
markers = dataset;
break;
}
}
if (id) {
id = (id + BLANK).toLowerCase();
markers._removeMarkerItem(id);
}
}
},
_createAxes: function () {
}
}, chartAPI.mscartesian);
FusionCharts.register(COMPONENT, ['caption', 'MapCaption', {
configure: function () {
var iapi = this.chart,
chartConfig = iapi.config,
chartAttrs = iapi.jsonData.chart,
components = iapi.components,
caption = components.caption,
captionConfig = caption.config,
style = chartConfig.style,
outCanfontFamily = style.outCanfontFamily,
outCancolor = style.outCancolor,
fontSize = style.fontSize,
align = ['top', 'center'],
subCaptionConfig = components.subCaption.config;
//subCaptionStyle = subCaptionConfig.style;
captionConfig.style = {
fontFamily: pluck (chartAttrs.captionfont, outCanfontFamily),
color: convertColor(pluck (chartAttrs.captionfontcolor, outCancolor).
replace (/^#? ([a-f0-9]+)/ig, '#$1')),
fontSize: pluckNumber (chartAttrs.captionfontsize, (fontSize + 3)) + PXSTRING,
fontWeight: pluckNumber (chartAttrs.captionfontbold) === 0 ? 'normal' : 'bold'
};
captionConfig.align = subCaptionConfig.align = pluck (chartAttrs.captionposition, POSITION_CENTER);
if (captionConfig.align) {
align = captionConfig.align.split('-');
align[0] && (align[0] = align[0].toLowerCase());
align[1] && (align[1] = align[1].toLowerCase());
}
if (align[0] === 'bottom') {
captionConfig.isOnTop = subCaptionConfig.isOnTop = 0;
}
else {
captionConfig.isOnTop = subCaptionConfig.isOnTop = 1;
}
switch (align[1]) {
case POSITION_RIGHT :
captionConfig.align = POSITION_END;
break;
case POSITION_LEFT :
captionConfig.align = POSITION_START;
break;
default :
captionConfig.align = POSITION_MIDDLE;
}
captionConfig.alignWithCanvas = subCaptionConfig.alignWithCanvas =
pluckNumber (chartAttrs.aligncaptionwithcanvas, 1);
captionConfig.horizontalPadding = subCaptionConfig.horizontalPadding = pluckNumber (
chartAttrs.captionhorizontalpadding, (captionConfig.alignWithCanvas ? 0 : 15));
captionConfig.drawCaption = true;
setLineHeight (captionConfig.style);
subCaptionConfig.style = {
fontFamily: pluck (chartAttrs.subcaptionfont, chartAttrs.captionfont, outCanfontFamily),
color: convertColor(pluck (chartAttrs.subcaptionfontcolor, chartAttrs.captionfontcolor, outCancolor).
replace (/^#? ([a-f0-9]+)/ig, '#$1')),
fontSize: (pluckNumber (
chartAttrs.subcaptionfontsize,
(pluckNumber (mathMax (pluckNumber (chartAttrs.captionfontsize) - 3, -1), fontSize) +
pluckNumber (this.subTitleFontSizeExtender, 1))
) + PXSTRING),
fontWeight: pluckNumber (chartAttrs.subcaptionfontbold, this.subTitleFontWeight,
chartAttrs.captionfontbold) === 0 ? 'normal' : 'bold'
};
setLineHeight (subCaptionConfig.style);
}
}, 'Caption']);
FusionCharts.register('component', ['dataset', 'Entities', {
customConfigFn : '_createDatasets',
type: 'entity',
pIndex : 2,
configure: function () {
var dataset = this,
chart = dataset.chart,
rawData = chart.jsonData,
conf = dataset.conf,
components = chart.components,
mapAttrs = rawData.map || rawData.chart,
entityJSONData,
colorRangeObj = rawData.colorrange || {},
ColorRangeManager = lib.nonGradientColorRange,
isGradient = colorRangeObj.gradient,
postLegendInitFn = components.postLegendInitFn,
numberFormatter = components.numberFormatter,
entities,
dataItem,
entityItem,
item,
limits = {},
entityDef = rawData.entitydef || [];
if (rawData.data && rawData.data[0] && rawData.data[0].data) {
entityJSONData = dataset.JSONData = rawData.data[0].data || [];
}
else {
entityJSONData = dataset.JSONData = rawData.data || [];
}
conf.useSNameAsId = pluckNumber(mapAttrs.usesnameasid, 0);
this._redefineEntities(entityDef);
entities = dataset.components.data;
conf.showTooltip = pluckNumber(mapAttrs.showtooltip, 1);
conf.showHoverEffect = pluckNumber(mapAttrs.showhovereffect, 0);
entityJSONData = convertArrayToIdMap(entityJSONData);
dataset.calculateDataLimits();
limits = dataset.getDataLimits();
if (isGradient) {
postLegendInitFn({
min: limits.min,
max: limits.max
});
}
else {
dataset.components.colorRange = new ColorRangeManager({
colorRange: rawData.colorrange,
dataMin: limits.min,
dataMax: limits.max,
defaultColor: '#00ff00',
numberFormatter: numberFormatter
});
}
for (item in entities) {
dataItem = entityJSONData[item];
entityItem = entities[item];
entityItem.dataset = dataset;
if (dataItem) {
// Work on a copy of dataItem
this._configureEntity(item, entityItem,
imprint(dataset._sanitizeEntityOptions(extend2({},dataItem)), entityItem.config));
}
else {
this._configureEntity(item, entityItem, entityItem.config);
}
}
dataset._addLegend();
},
init: function (entityJSONData) {
var dataSet = this;
dataSet.JSONData = {
data: entityJSONData
};
dataSet.components = {
};
dataSet.conf = {
};
dataSet.graphics = {
};
dataSet.configure();
},
updateEntityColors: function () {
var dataset = this,
minValue = arguments[0],
maxValue = arguments[1],
components = dataset.components,
entities = components.data,
chart = dataset.chart,
entityObj,
config,
value,
name,
alphaArr,
visibleEntityAttr,
hiddenAttr = {
'fill-opacity': 0
};
for (name in entities) {
entityObj = entities[name];
config = entityObj.config;
value = config.cleanValue;
alphaArr = config.alphaArr || [];
visibleEntityAttr = {
'fill-opacity': (alphaArr[0] / 100) || 1
};
// Value outside range
if (value < minValue || value > maxValue) {
dataset.setCustomAttrs(entityObj, hiddenAttr, chart);
entityObj.hidden = true;
}
// Value within range
else {
// // In normal legend visibility is passed as false for hiding entities within range
// // In gradient legend visiblity is always undefined so it goes to else part and always
// // shows all entities outside range
// if (visibility === false) {
// appliedAttrs = hiddenAttr;
// entityObj.hidden = true;
// }
// else {
// appliedAttrs = visibleEntityAttr;
// entityObj.hidden = false;
// }
entityObj.hidden = false;
dataset.setCustomAttrs(entityObj, visibleEntityAttr, chart);
}
}
},
_addLegend: function () {
var dataset = this,
chart = dataset.chart,
chartComponents = chart.components,
legend = chartComponents.legend,
colorRangeObj = dataset.components.colorRange || {},
colorRangeArr = colorRangeObj.colorArr || [],
entityColorMap = dataset.components.entityColorMap = dataset.components.entityColorMap ||
(dataset.components.entityColorMap = []),
colorObj,
i,
len = colorRangeArr.length,
config,
color,
strokeColor,
entityColorObj,
lightColor,
fillColor;
legend.emptyItems();
entityColorMap.length = 0;
for (i = 0, len = colorRangeArr.length; i < len; i++) {
colorObj = colorRangeArr[i];
color = pluck(colorObj.code, colorObj.color);
strokeColor = getLightColor(color, 60).replace(dropHash, HASHSTRING);
lightColor = getLightColor(color, 40);
fillColor = {
FCcolor: {
color: color + ',' + color + ',' + lightColor + ',' + color + ',' + color,
ratio: '0,70,30',
angle: 270,
alpha: '100,100,100,100,100'
}
};
config = {
fillColor: toRaphaelColor(fillColor),
label: pluck(colorObj.label, colorObj.displayvalue),
strokeColor: toRaphaelColor(strokeColor),
legendItemId: colorObj.legendItemId
};
entityColorObj = entityColorMap[i];
if (!entityColorObj) {
entityColorObj = entityColorMap[i] = {
config: {}
};
}
entityColorObj.config = colorObj;
entityColorObj.visible = true;
entityColorObj.dataset = dataset;
entityColorObj.legendItemId = legend.addItems(entityColorObj, dataset.legendInteractivity, config);
}
},
legendInteractivity: function (entityColorObj, legendItem) {
var legend = this,
legendConfig = legend.config,
entityColorConfig = entityColorObj.config,
visible = entityColorObj.visible,
dataset = entityColorObj.dataset,
chart = dataset.chart,
entities = dataset.components.data,
i,
entity,
config = legendItem.config,
legendGraphics = legendItem.graphics,
cleanValue,
itemHiddenStyle = legendConfig.itemHiddenStyle,
colorRange = dataset.components.colorRange,
hiddenColor = itemHiddenStyle.color,
itemStyle = legendConfig.itemStyle,
itemTextColor = itemStyle.color,
color = config.fillColor,
attrObj = {},
type,
element,
entityAttrs,
visibleEntityAttr = {},
applyAttr,
hiddenAttr = {
'fill-opacity': 0
},
colorObj,
stroke = config.strokeColor;
entityColorObj.visible = !visible;
for (i in entities) {
if (entities.hasOwnProperty(i)) {
entity = entities[i];
config = entity.config;
cleanValue = config.cleanValue;
colorObj = colorRange.getColorObj(cleanValue);
entityAttrs = config.visibleEntityAttr;
visibleEntityAttr['fill-opacity'] = entityAttrs['fill-opacity'];
applyAttr = visible ? hiddenAttr : visibleEntityAttr;
if (entityColorConfig.code === colorObj.code) {
entity.hidden = visible ? true : false;
dataset.setCustomAttrs(entity, applyAttr, chart);
}
}
}
attrObj = {
'legendItemSymbol': {
fill: visible ? hiddenColor : color,
'stroke': visible ? hiddenColor : stroke
},
legendItemText: {
fill: visible ? hiddenColor : itemTextColor
},
legendIconLine: {
'stroke': visible ? hiddenColor : color
}
};
for (type in legendGraphics) {
element = legendGraphics[type];
element && element.attr && element.attr(attrObj[type]);
}
},
setCustomAttrs: function (entityObj, attrs, chart) {
var animationObj = chart.get('config', 'animationObj'),
animationDuration = animationObj.transposeAnimDuration,
animType = animationObj.animType,
elements,
graphic;
if (entityObj) {
elements = entityObj.graphics;
graphic = elements.outlines;
graphic.animate(attrs, animationDuration, animType);
}
},
calculateDataLimits: function () {
var dataset = this,
chart = dataset.chart,
conf = dataset.conf,
jsonData = chart.jsonData,
dataArr = jsonData.data || [],
numberFormatter = chart.components.numberFormatter,
cleanValue,
value,
minValue = +Infinity,
maxValue = -Infinity,
len,
i;
for (i = 0,len = dataArr.length; i < len; i++) {
value = dataArr[i].value;
cleanValue = numberFormatter.getCleanValue(value);
minValue = mathMin(minValue, cleanValue);
maxValue = mathMax(maxValue, cleanValue);
}
conf.max = maxValue;
conf.min = minValue;
},
/*
This function configures various properties of an entity item
@param - id - id of the entityItem
@param - entityItem - entityObject
@param - entityJSON - json data of entity item
*/
_configureEntity: function (id, entityItem, entityJSON) {
var dataset = this,
chart = dataset.chart,
conf = dataset.conf,
jsonData = chart.jsonData,
components = chart.components,
numberFormatter = components.numberFormatter,
chartConf = chart.config,
entityOpts = chartConf.entityOpts,
entityConf = entityItem.config,
labelConfig = entityItem.labelConfig,
value = entityJSON.value,
isGradient = jsonData.colorrange && jsonData.colorrange.gradient,
gradientLegend = components.gradientLegend,
gColorRange = gradientLegend && gradientLegend.colorRange,
colorRange = dataset.components.colorRange,
cleanValue = entityConf.cleanValue = numberFormatter.getCleanValue(value),
formattedValue = entityConf.formattedValue =
(cleanValue !== UNDEFINED) ? numberFormatter.dataLabels(cleanValue) : UNDEFINED,
showTooltip = pluckNumber(entityJSON.showtooltip, entityOpts.showTooltip),
defaultTooltip = this._getDefaultTooltip.call(entityItem, entityJSON, dataset),
tooltextMacroObj = {
formattedValue: formattedValue,
sName: entityJSON.shortLabel,
lName: entityJSON.label
},
styleObj = entityOpts.dataLabels.style,
toolText = entityConf.toolText = showTooltip ?
parseUnsafeString(pluck(parseTooltext(
pluck(entityJSON.tooltext, entityOpts.tooltext, defaultTooltip),
[1, 2, 7, 38, 39],
tooltextMacroObj,
entityJSON)))
: BLANK,
borderColor = entityConf.borderColor = pluck(entityJSON.bordercolor, entityOpts.borderColor),
borderAlpha = entityConf.borderAlpha = pluck(entityJSON.borderalpha, entityOpts.borderAlpha),
borderThickness = entityConf.borderThickness =
pluckNumber(entityJSON.borderthickness, entityOpts.borderThickness),
hoverOnNull = entityOpts.hoverOnNull,
useHoverColor = entityConf.useHoverColor = pluckNumber(entityJSON.showhovereffect,
entityJSON.usehovercolor,
(hoverOnNull ? entityOpts.showHoverEffect : isNaN(value) ? 0 : entityOpts.showHoverEffect)),
labelAlignment = entityConf.labelAlignment,
colorObj,
link,
align,
valign,
color,
alpha,
angle,
ratio,
fillColor,
fontColor,
fontFamily,
fontBold,
emptyColorObject,
fillColorObject,
hoverColor,
bgColor,
labels,
labelObj,
oriLabels,
i,
alphaArr;
// Reset the hidden flag so that on data update entity item gets visible
entityItem.hidden = false;
entityConf.showLabel = pluckNumber(entityJSON.showlabel, entityOpts.showLabels);
entityConf.labelPadding = pluckNumber(entityJSON.labelpadding,
entityOpts.labelPadding);
entityConf.fontFamily = pluck(entityJSON.font, styleObj.fontFamily);
entityConf.fontSize = pluckNumber(parseInt(entityJSON.fontsize, 10),
parseInt(styleObj.fontSize, 10));
entityConf.fontBold = pluckNumber(entityJSON.fontbold, 0);
entityConf.fontColor = pluck(entityJSON.fontcolor, styleObj.color);
entityConf.connectorColor = pluck(entityJSON.labelconnectorcolor,
entityOpts.connectorColor);
entityConf.connectorAlpha = pluck(entityJSON.labelconnectoralpha,
entityOpts.connectorAlpha);
entityConf.hoverBorderThickness = pluckNumber(entityJSON.borderhoverthickness,
entityJSON.hoverborderthickness, entityOpts.hoverBorderThickness);
entityConf.hoverBorderColor = pluck(entityJSON.borderhovercolor, entityJSON.hoverbordercolor,
entityOpts.hoverBorderColor, entityConf.borderColor);
entityConf.hoverBorderAlpha = pluck(entityJSON.borderhoveralpha, entityJSON.hoverborderalpha,
entityOpts.hoverBorderAlpha, entityConf.borderAlpha);
entityConf.connectorThickness = pluckNumber(entityJSON.labelconnectorthickness,
entityOpts.connectorThickness);
entityConf.origConnectorThickness = entityConf.connectorThickness;
entityConf.borderThickness = borderThickness;
entityConf.link = entityJSON.link;
entityConf.isVisible = true;
entityConf.id = id;
entityConf.originalId = entityJSON.origId;
if (cleanValue !== null) {
if (gColorRange && isGradient) {
bgColor = gColorRange.getColorByValue(cleanValue);
}
else if (colorRange) {
colorObj = colorRange.getColorObj(cleanValue);
bgColor = pluck(colorObj.color, colorObj.code);
}
}
if (pluck(entityJSON.color, entityJSON.alpha,
entityJSON.angle, entityJSON.ratio) !== UNDEFINED) {
color = pluck(entityJSON.color, bgColor, entityOpts.fillColor);
alpha = pluck(entityJSON.alpha, entityOpts.fillAlpha);
angle = pluck(entityJSON.angle, entityOpts.fillAngle);
ratio = pluck(entityJSON.ratio, entityOpts.fillRatio);
fillColor = colorize({
color: color,
alpha: alpha,
angle: angle,
ratio: ratio
});
}
else {
fillColorObject = colorize({
color: pluck(bgColor, entityOpts.fillColor),
alpha: pluck(entityOpts.fillAlpha),
angle: pluck(entityOpts.fillAngle),
ratio: pluck(entityOpts.fillRatio)
});
emptyColorObject = colorize({
color: pluck(entityOpts.nullEntityColor),
alpha: pluck(entityOpts.nullEntityAlpha),
angle: pluck(entityOpts.nullEntityAngle),
ratio: pluck(entityOpts.nullEntityRatio)
});
// If value is null then set empty color for entity
fillColor = cleanValue === null ? emptyColorObject : fillColorObject;
color = fillColor.FCcolor.color;
alpha = fillColor.FCcolor.alpha;
angle = fillColor.FCcolor.angle;
ratio = fillColor.FCcolor.ratio;
}
// Need to re-check whether tooltip is to be shown for blank
// tooltext
if (toolText === BLANK) {
conf.showTooltip = 0;
}
entityConf.visibleEntityAttr = {
stroke: convertColor(borderColor, borderAlpha),
fill: (entityConf.fillColor = fillColor).toString(),
'fill-opacity': alpha / 100
};
alphaArr = alpha.split(COMMA);
// if (borderThickness) {
// alphaArr.push(borderAlpha);
// }
entityConf.alphaArr = alphaArr;
if (useHoverColor) {
if (pluck(entityJSON.fillhovercolor, entityJSON.fillhoveralpha, entityJSON.fillhoverangle,
entityJSON.fillhoverratio, entityJSON.hoverfillcolor, entityJSON.hoverfillalpha,
entityJSON.hoverfillratio, entityJSON.hoverfillangle) !== UNDEFINED) {
color = pluck(entityJSON.fillhovercolor, entityJSON.hoverfillcolor, entityOpts.hoverFillColor);
alpha = pluck(entityJSON.fillhoveralpha, entityJSON.hoverfillalpha, entityOpts.hoverFillAlpha);
angle = pluck(entityJSON.fillhoverangle, entityJSON.hoverfillangle, entityOpts.hoverFillAngle);
ratio = pluck(entityJSON.fillhoverratio, entityJSON.hoverfillratio, entityOpts.hoverFillRatio);
hoverColor = colorize({
color: color,
alpha: alpha,
angle: angle,
ratio: ratio
});
}
else {
if (!entityOpts.hoverColorObject) {
entityOpts.hoverColorObject = colorize({
color: entityOpts.hoverFillColor,
alpha: entityOpts.hoverFillAlpha,
angle: entityOpts.hoverFillAngle,
ratio: entityOpts.hoverFillRatio
});
}
hoverColor = entityOpts.hoverColorObject;
}
entityConf.hoverColor = hoverColor;
}
// Entity Label configurations
!labelConfig && (labelConfig = entityItem.labelConfig = {});
fontColor = entityConf.fontColor;
fontFamily = entityConf.fontFamily;
fontBold = entityConf.fontBold;
toolText = entityConf.toolText;
link = entityConf.link;
if (labelAlignment) {
// labelPadding neednt be scaleFactored.
align = labelAlignment[0];
valign = labelAlignment[1];
}
else {
align = POSITION_CENTER,
valign = POSITION_MIDDLE;
}
labelConfig.align = align;
labelConfig.vAlign = valign;
labelConfig.bgColor = BLANK;
labelConfig.borderColor = BLANK;
labelConfig.fontColor = fontColor;
labelConfig.fontFamily = fontFamily;
labelConfig.fontBold = fontBold;
labelConfig.toolText = toolText;
labelConfig.link = link;
if (typeof entityConf.options === 'object') {
labels = entityConf.entityLabels = entityConf.entityLabels || [];
oriLabels = entityConf.labels || [];
i = oriLabels.length;
while (i--) {
if (!labels[i]) {
labels[i] = {
config: {}
};
}
labelConfig = labels[i].config;
labelAlignment = oriLabels[i].labelAlignment;
if (labelAlignment) {
// labelPadding neednt be scaleFactored.
align = labelAlignment[0];
valign = labelAlignment[1];
}
else {
align = POSITION_CENTER,
valign = POSITION_MIDDLE;
}
labelConfig.align = align;
labelConfig.vAlign = valign;
labelConfig.displayValue = dataset.getDisplayValue.call(entityItem, oriLabels[i],
entityConf.options.isDataEnabled, !i, entityJSON);
labelConfig.bgColor = BLANK;
labelConfig.borderColor = BLANK;
labelConfig.toolText = toolText;
labelConfig.align = align;
labelConfig.vAlign = valign;
labelConfig.bgColor = BLANK;
labelConfig.borderColor = BLANK;
labelConfig.fontColor = fontColor;
labelConfig.fontFamily = fontFamily;
labelConfig.fontBold = fontBold;
labelConfig.toolText = toolText;
}
}
else {
labelObj = {
shortText: entityConf.shortLabel,
text: entityConf.label
};
labelConfig.displayValue = dataset.getDisplayValue.call(entityItem, labelObj, true, true, entityJSON);
}
},
getDisplayValue: function (labelObj, userValue, userDV, entityJSON) {
var entity = this,
chart = entity.dataset.chart,
entityOpts = chart.config.entityOpts,
entityConf = entity.config,
cleanValue = entityConf.cleanValue,
formattedValue = entityConf.formattedValue,
labelSepChar = entityOpts.labelSepChar,
displayValue;
// Parsing display Value
if (userValue) {
if (userDV && typeof entityJSON.displayvalue !== 'undefined') {
displayValue = entityJSON.displayvalue;
} else {
displayValue = pluck((entityOpts.includeNameInLabels ?
(entityOpts.useShortName ?
labelObj.shortText : labelObj.text) : ''));
if (entityOpts.includeValueInLabels && cleanValue !== null) {
displayValue = (displayValue === UNDEFINED) ? formattedValue :
(displayValue + labelSepChar + formattedValue);
}
}
}
else {
displayValue = entityConf.label;
}
return displayValue;
},
_sanitizeEntityOptions: function(options) {
// The entity object cannot have the following properties.
delete options.outlines;
delete options.label;
delete options.shortlabel;
delete options.labelposition;
delete options.labelalignment;
delete options.labelconnectors;
return options;
},
_redefineEntities: function (entityDef) {
var dataset = this,
conf = dataset.conf,
chart = dataset.chart,
redefinedEntities = {},
processedIds = {},
entities = chart.entities,
useSNameAsId = conf.useSNameAsId,
i,
defObj,
oldId,
newId,
sName,
lName,
id,
newObj,
entityCount = 0,
entityStore,
entityDataStore,
item,
entityObj;
i = entityDef.length;
while (i--) {
defObj = entityDef[i],
oldId = defObj.internalid,
newId = (defObj.newid ? defObj.newid : oldId);
sName = defObj.sname;
lName = defObj.lname;
entityObj = entities[oldId];
/**
* Handling the exception when the entity ids in the map js have an
* extra space (leading or trailing)
*/
oldId = lib.trimString(oldId);
newId = lib.trimString(newId);
newId = newId && newId.toLowerCase();
if (entityObj) {
redefinedEntities[newId] = newObj = { origId: oldId };
// processedIds is needed to keep track of all the entities
// that have been redefined using the entitiydef block.
processedIds[oldId] = true;
// not using extend2 as it involves a deep copy of the objects.
for (item in entityObj) {
newObj[item] = entityObj[item];
}
newObj.shortLabel = sName ? sName : entityObj.shortLabel;
newObj.label = lName ? lName : entityObj.label;
newObj.showhovereffect = defObj.showhovereffect;
newObj.fillhovercolor = defObj.fillhovercolor;
newObj.fillhoveralpha = defObj.fillhoveralpha;
newObj.fillhoverangle = defObj.fillhoverangle;
newObj.fillhoverratio = defObj.fillhoverratio;
newObj.borderhoverthickness = defObj.borderhoverthickness;
}
}
entityDataStore = dataset.components.data;
if (!entityDataStore) {
entityDataStore = dataset.components.data = {
};
}
for (id in redefinedEntities) {
id = id.toLowerCase();
if (!entityDataStore[id]) {
entityDataStore[id] = {
config : {}
};
}
entityDataStore[id].config = redefinedEntities[id];
entityCount += 1;
}
entityStore = entityDataStore;
for (id in entities) {
newObj = entities[id];
/**
* Handling the exception when the entity ids in the map js have an
* extra space (leading or trailing)
*/
id = lib.trimString(id);
if (!processedIds[id]) {
if (useSNameAsId) {
entityObj = entityStore[newObj.shortLabel.toLowerCase()];
if (!entityObj) {
entityObj = entityStore[newObj.shortLabel.toLowerCase()] = {};
}
entityObj.config = {};
entityObj.origId = newObj.shortLabel;
}
else {
entityObj = entityStore[id.toLowerCase()];
if (!entityObj) {
entityObj = entityStore[id.toLowerCase()] = {};
}
entityObj.config = {};
entityObj.config.origId = id;
}
for (item in newObj) {
entityObj.config[item] = newObj[item];
}
entityCount += 1;
}
}
// Entity count introduced to enable the batch rendering of entities.
conf.entityCount = entityCount;
},
draw: function () {
var dataset = this,
conf = dataset.conf,
chart = dataset.chart,
// The rendering should be done in samll batches only for VML.
// For SVG increase the batch size
// @todo Determine the batchsize in a generalized way for all browsers instead of hardcoding it.
BATCH_SIZE = hasSVG ? 200 : 10,
doBatchRendering;
this.conf.ready = false;
conf.BATCH_SIZE = BATCH_SIZE;
conf.labelBatchSize = hasSVG ? 200 : 20;
doBatchRendering = this._batchRender();
doBatchRendering(0);
chart.config.entityFlag = true;
},
_batchRender: function () {
var dataset = this,
conf = dataset.conf,
chart = dataset.chart,
jobList = chart.getJobList(),
firstEntity = chart.entities.firstEntity,
components = dataset.components,
entities = components.data,
batchSize = conf.BATCH_SIZE,
keys = conf.entityKeys = firstEntity ? dataset._getKeys(firstEntity, entities) : Object.keys(entities),
keysLength = conf.entityLength = keys.length,
i,
entityObj,
count,
ent,
outlinesDrawn,
drawEntities = function (entityStartIndex) {
i = entityStartIndex;
count = 0;
// To counter the performance issues while rendering in IE, the entities shall
// be rendered in bat +ches of BATCH_SIZE.
while (keys[i] !== UNDEFINED) {
entityObj = entities[keys[i]];
outlinesDrawn = dataset.drawEntity(entityObj, batchSize);
count += outlinesDrawn;
if (entityObj.config.drawn) {
ent = i === keysLength - 1 ? entityObj : entities[keys[i-1]];
if (ent) {
ent.config.drawn = false;
ent.config.outlineStartIndex = UNDEFINED;
}
i++;
}
if (count >= batchSize) {
jobList.entityDrawID.push(schedular.addJob(drawEntities, dataset, [i],
lib.priorityList.entitydraw, true));
break;
}
}
if (i === keysLength) {
dataset._addEventListenersToEntities(0);
dataset.initComplete();
}
};
return drawEntities;
},
_addEventListenersToEntities: function (startIndex) {
var dataset = this,
entities = dataset.components.data,
chart = dataset.chart,
jobList = chart.getJobList(),
conf = dataset.conf,
batchSize = conf.BATCH_SIZE,
keys = conf.entityKeys,
i,
len = conf.entityLength,
entity,
count = 0,
options,
entityConf;
for (i = startIndex; i < len; i++) {
entity = entities[keys[i]];
entityConf = entity.config;
options = entityConf.options;
if (!(options && options.isDataEnabled === false)) {
dataset.addMouseGestures.call(entity);
}
count++;
if (count === batchSize) {
jobList.entityDrawID.push(schedular.addJob(dataset._addEventListenersToEntities, dataset, [i],
lib.priorityList.entitydraw, true));
break;
}
}
},
_getKeys: function (firstEntity, entities) {
var keys = [firstEntity],
item = firstEntity;
while (entities[item]) {
item = entities[item].nextId;
keys.push(item);
}
return keys;
},
_getDefaultTooltip: function (entityJSON) {
var entity = this,
dataset = entity.dataset,
value = entity.config.cleanValue,
formattedValue = entity.config.formattedValue,
entityOpts = dataset.chart.config.entityOpts,
tooltip,
labelObj;
if (typeof entity.config.options === 'object') {
labelObj = entityJSON.labels && entityJSON.labels[0];
if (!labelObj) {
return UNDEFINED;
}
tooltip = ((entityOpts.useSNameInTooltip ?
labelObj.shortText : labelObj.text) +
(value === null ? BLANK : (entityOpts.tooltipSepChar +
formattedValue)));
}
else {
tooltip = ((entityOpts.useSNameInTooltip ?
entityJSON.shortLabel : entityJSON.label) +
(value === null ? BLANK : (entityOpts.tooltipSepChar +
formattedValue)));
}
return tooltip;
},
drawEntity: function (entity, outlinesDrawCount) {
var dataset = this,
chart = dataset.chart,
components = chart.components,
paper = components.paper,
entityConf = entity.config,
pathStr = (hasSVG || !isIE) ? 'litepath' : 'path',
entityOpts = chart.config.entityOpts,
outlines = entityConf.outlines,
addTo = chart.graphics.datasetGroup,
shadowGroup = chart.graphics.shadowGroup,
toolText = entityConf.toolText,
showShadow = entityOpts.shadow,
i,
outlinePath = [],
path,
visibleEntityAttr,
fillOpacity,
applyAttr,
outlineGraphics,
outline,
count,
customStrokeWidthModifier,
shadowOptions;
dataset._configureEntityDrawingParams(entity);
entityConf = entity.config;
visibleEntityAttr = entityConf.visibleEntityAttr;
shadowOptions = entityConf.shadowOptions;
fillOpacity = entityConf.fillOpacity;
i = entityConf.outlineStartIndex === UNDEFINED ? outlines.length : entityConf.outlineStartIndex;
if (!entity.graphics) {
entity.graphics = {};
}
count = 0;
outlinePath = entityConf.outlinePath || (entityConf.outlinePath = []);
customStrokeWidthModifier = entityConf.customStrokeWidthModifier;
if (typeof entityConf.options === 'object') {
while (i--) {
outlinePath = outlines[i].outline;
if (entityConf.options.isDataEnabled === true) {
applyAttr = visibleEntityAttr;
}
else {
applyAttr = extend2(extend2({}, visibleEntityAttr),
pruneStrokeAttrs(outlines[i].style, customStrokeWidthModifier));
}
outlineGraphics = entity.graphics.outlines;
if (!outlineGraphics) {
outlineGraphics = entity.graphics.outlines = [];
}
!outlineGraphics[i] && (outlineGraphics[i] = {});
outline = outlineGraphics[i].outline;
if (!outline) {
outline = outlineGraphics[i].outline = paper[pathStr](outlinePath, addTo);
}
else {
outline.attr({
'fill-opacity': fillOpacity
});
}
outline.attr(applyAttr)
.tooltip(toolText)
.shadow(showShadow ? shadowOptions : false, shadowGroup);
count++;
entityConf.outlineStartIndex = i;
if (count === outlinesDrawCount) {
return count;
}
}
entityConf.drawn = true;
return count;
}
else {
if (!entity.graphics.outlines) {
while (i--) {
path = outlines[i];
outlinePath = path.concat(outlinePath);
count++;
entityConf.outlineStartIndex = i;
if (count === outlinesDrawCount) {
entityConf.outlinePath = outlinePath;
return count;
}
}
entity.graphics.outlines = paper[pathStr](outlinePath, addTo)
.attr(visibleEntityAttr);
}
else {
entity.graphics.outlines.attr(visibleEntityAttr)
.attr({
'fill-opacity': fillOpacity
});
}
entityConf.drawn = true;
entityConf.outlineStartIndex = 0;
entityConf.outlinePath = [];
entity.graphics.outlines.tooltip(toolText).shadow(showShadow ? shadowOptions : false, shadowGroup);
return count;
}
},
_configureEntityDrawingParams: function (entity) {
var dataset = this,
chart = dataset.chart,
chartConf = chart.config,
components = chart.components,
gradientLegend = components.gradientLegend,
entityConf = entity.config,
entityOpts = chart.config.entityOpts,
scalingParams = chart.config.scalingParams,
scaleStrokes = (!isIE || hasSVG),
scaleFactor = scalingParams.scaleFactor,
scaledPixel = scalingParams.strokeWidth,
scaledPixelWithBaseFactor = (scaleStrokes ?
chart.baseScaleFactor : 1) * scaledPixel,
scaleBorder = entityOpts.scaleBorder === 1,
borderThickness = entityConf.borderThickness,
alphaArr = entityConf.alphaArr,
connectorThickness = entityConf.origConnectorThickness,
hoverBorderThickness = entityConf.hoverBorderThickness,
visibleEntityAttr = entityConf.visibleEntityAttr,
fillOpacity = visibleEntityAttr['fill-opacity'],
customStrokeWidthModifier;
entityConf.shadowOptions = {
scalefactor: [scaleFactor, scaleFactor * chart.baseScaleFactor],
opacity: mathMax.apply(math, alphaArr) / 100
};
if (gradientLegend && chartConf.gLegendEnabled === true) {
entity.hidden = false;
}
entityConf.fillOpacity = entity.hidden ? 0 : fillOpacity;
(gradientLegend && gradientLegend.enabled) && (gradientLegend.resetLegend(),
gradientLegend.clearListeners());
gradientLegend && gradientLegend.notifyWhenUpdate(dataset.updateEntityColors,
dataset);
// By default scaleBorder is false.
if (scaleStrokes) {
// SVG
borderThickness = entityConf.entityBorderThickness = scaleBorder ?
(borderThickness * scaledPixelWithBaseFactor) :
(borderThickness / scaleFactor);
connectorThickness = connectorThickness / scaleFactor;
customStrokeWidthModifier = scaleBorder ? scaleFactor : scalingParams.sFactor;
if (hoverBorderThickness) {
hoverBorderThickness = entityConf.hoverBorderThickness = scaleBorder ?
(hoverBorderThickness * scaledPixelWithBaseFactor) :
(hoverBorderThickness / scaleFactor);
}
// if (isWebKit) {
// webkit issue fix
// borderThickness = (borderThickness && mathCeil(borderThickness)) || 0;
// connectorThickness = (connectorThickness && mathCeil(connectorThickness)) || 0;
// }
}
else {
// VML
borderThickness = scaleBorder ? borderThickness * scaledPixel : borderThickness;
customStrokeWidthModifier = scaleBorder ? scalingParams.scaleFactor : chart.baseScaleFactor;
}
entityConf.entityBorderThickness = borderThickness;
entityConf.connectorThickness = connectorThickness;
entityConf.customStrokeWidthModifier = customStrokeWidthModifier;
visibleEntityAttr['stroke-width'] = borderThickness;
visibleEntityAttr.transform = hasSVG || !isIE ? '' : scalingParams.transformStr;
},
drawLabels: function (dataset) {
var ent = this,
itemMap = ent,
chart = dataset.chart,
conf = dataset.conf,
batchSize = conf.labelBatchSize,
components = chart.components,
keysLength = conf.entityLength,
labelItems = [],
i,
annotations,
count = 0,
annCount = 0,
labelItemsAdded = 0,
annConfig = chart.config.annotationConfig,
mapLabelAnnotations = components.mapLabelAnnotations = components.mapLabelAnnotations ||
(components.mapLabelAnnotations = []);
annotations = mapLabelAnnotations[count];
if (!annotations) {
annotations = mapLabelAnnotations[count] = new lib.Annotations();
}
annotations.reset(null, annConfig, chart);
annotations._renderer && (annotations._renderer = null);
for (i in itemMap) {
dataset.drawLabel.call(itemMap[i], labelItems);
count++;
if (count === batchSize) {
annotations.addGroup({
id: 'entityLabels',
items: labelItems
});
annCount++;
annotations = mapLabelAnnotations[annCount];
if (!annotations) {
annotations = mapLabelAnnotations[annCount] = new lib.Annotations();
}
annotations.reset(null, annConfig, chart);
annotations._renderer && (annotations._renderer = null);
count = 0;
labelItems = [];
}
else if (labelItemsAdded === keysLength - 1) {
annotations.addGroup({
id: 'entityLabels',
items: labelItems
});
}
labelItemsAdded++;
}
dataset.drawLabelConnFn(0);
},
drawLabelConnFn: function (startIndex) {
var dataset = this,
conf = dataset.conf,
chart = dataset.chart,
jobList = chart.getJobList(),
entities = dataset.components.data,
batchSize = conf.BATCH_SIZE,
keys = conf.entityKeys,
l,
i,
length = keys.length,
labelArr,
config,
entity,
count = 0;
for (i = startIndex; i < length; i++) {
entity = entities[keys[i]];
config = entity.config;
if (typeof config.options === 'object') {
labelArr = config.labels;
l = (labelArr && labelArr.length) || 0;
while (l--) {
if (labelArr[l].labelConnectors) {
dataset.drawLabelConnectors.call(entity, labelArr[l].labelConnectors);
count++;
}
}
}
else {
if (config.labelConnectors) {
dataset.drawLabelConnectors.call(entity, config.labelConnectors);
count++;
}
}
if (count === batchSize) {
jobList.entityDrawID.push(schedular.addJob(dataset.drawLabelConnFn, dataset, [i],
lib.priorityList.entitydraw, true));
break;
}
}
},
// Returns a label object with x y positions and cosmetic attributes which is then pushed to annotations array
_getLabelObject: function (index, userValue) {
var entity = this,
dataset = entity.dataset,
chart = dataset.chart,
entityConf = entity.config,
labelConfig,
scalingParams = chart.config.scalingParams,
labelPos,
labelAlignment,
firstEle = entity.graphics && entity.graphics.outlines,
fontStyleObj,
fontSize = entityConf.fontSize,
labelPadding = entityConf.labelPadding,
labelsArr = entityConf.labels || [],
entityLabels = entityConf.entityLabels || [],
baseWidth,
baseHeight,
labelX,
labelY,
box,
align,
valign,
fsize,
xOffset,
yOffset,
oriLabelObj,
labelObj;
if (index !== undefined) {
oriLabelObj = labelsArr[index];
labelObj = entityLabels[index];
labelConfig = labelObj.config;
fontStyleObj = labelConfig.style = oriLabelObj.style;
labelPos = oriLabelObj.labelPosition;
labelAlignment = oriLabelObj.labelAlignment;
}
else {
labelConfig = entity.labelConfig;
labelPos = entityConf.labelPosition;
labelAlignment = entityConf.labelAlignment;
}
if (labelPos) {
labelX = labelPos[0];
labelY = labelPos[1];
}
else {
box = firstEle.getBBox();
labelX = box.x + (box.width / 2);
labelY = box.y + (box.height / 2);
}
if (labelAlignment) {
// labelPadding neednt be scaleFactored.
align = labelAlignment[0];
valign = labelAlignment[1];
if (align === POSITION_RIGHT) {
labelX -= labelPadding;
}
else if (align === POSITION_LEFT) {
labelX += labelPadding;
}
if (valign === POSITION_TOP) {
labelY -= labelPadding;
}
else if (valign === POSITION_BOTTOM) {
labelY += labelPadding;
}
}
else {
align = POSITION_CENTER,
valign = POSITION_MIDDLE;
}
fsize = parseFloat(fontSize) / (scalingParams.sFactor);
if (!userValue && fontStyleObj) {
/** @todo change fill property to color as for fonts fill is non-standard */
fontStyleObj.color && (labelConfig.fontColor = fontStyleObj.color);
fontStyleObj['font-size'] && (fsize =
(parseFloat(fontStyleObj['font-size']) / (scalingParams.sFactor)));
fontStyleObj['font-family'] && (labelConfig.fontFamily = fontStyleObj['font-family']);
(fontStyleObj['font-weight'] !== UNDEFINED) && (labelConfig.fontBold =
(fontStyleObj['font-weight'] === 'bold'));
}
labelConfig.x = labelX.toString();
labelConfig.y = labelY.toString();
labelConfig.wrap = 1;
labelConfig.type = 'text';
labelConfig.fontSize = fsize;
return {
x: labelX.toString(),
y: labelY.toString(),
wrapwidth: getTextWrapWidth[align](baseWidth, labelX + xOffset) - labelPadding,
wrapheight: getTextWrapHeight[valign](baseHeight, labelY + yOffset) - labelPadding,
wrap: 1,
type: 'text',
align: labelConfig.align,
valign: labelConfig.vAlign,
text: labelConfig.displayValue,
tooltext: labelConfig.toolText,
link: labelConfig.link,
bgcolor: labelConfig.bgColor,
bordercolor: labelConfig.borderColor,
fillcolor: labelConfig.fontColor,
fontsize: labelConfig.fontSize,
font: labelConfig.fontFamily,
bold: labelConfig.fontBold,
onclick: function (e) {
/*
* A map contains *entities* marked by concrete boundaries. For example, the India map has 28
* states, each state can be marked as an entity. Every entity has an id by which it is referred
* to in the JS file . The user can assign an Id of choice to the entity or use the original ID
* of the entity. The `entityClick` event is fired when an *entity* is clicked.
*
* The user can used this event to perform an action on clicking the entity. This event is
* usually preceded by the the {@link FusionCharts#event:entityRollOver} event.
*
* @event FusionCharts#entityClick
* @group map:entity
* @see FusionCharts#event:entityRollOver
* @see FusionCharts#event:entityRollOut
*
* @param {number} value The value of the entity.
* @param {string} label The label of the entity.
* @param {string} shortLabel Short label used by the user.
* @param {string} originalId The ID of the entity stored in the JS file.
* @param {string} id This could be the original ID or the ID assigned by the user.
*
* @example
* FusionCharts.ready(function () {
* var map = new FusionCharts({
* type: 'maps/world',
* renderAt: 'map-container-div',
*
* events: {
* entityClick: function (event, args) {
* console.log(args.label + 'clicked');
* }
* }
* });
* });
*/
global.raiseEvent('entityclick', entityConf.eventArgs, chart.chartInstance, e);
},
onmouseover: function (e) {
var hoverEnt = entity.graphics.outlines,
i,
len;
// In some maps outline graphics is an array of elements for example - usmsa map
if (hoverEnt instanceof Array) {
for (i = 0, len = hoverEnt.length; i < len; i++) {
dataset.entityRollOver.call(hoverEnt[i].outline, e);
}
}
else {
dataset.entityRollOver.call(hoverEnt, e);
}
},
onmouseout: function (e) {
var hoverEnt = entity.graphics.outlines,
i,
len;
// In some maps outline graphics is an array of elements for example - usmsa map
if (hoverEnt instanceof Array) {
for (i = 0, len = hoverEnt.length; i < len; i++) {
dataset.entityRollOut.call(hoverEnt[i].outline, e);
}
}
else {
dataset.entityRollOut.call(hoverEnt, e);
}
},
ontouchstart: function (e) {
var hoverEnt = entity.graphics.outlines,
i,
len;
// In some maps outline graphics is an array of elements for example - usmsa map
if (hoverEnt instanceof Array) {
for (i = 0, len = hoverEnt.length; i < len; i++) {
dataset.entityRollOver.call(hoverEnt[i].outline, e);
}
}
else {
dataset.entityRollOver.call(hoverEnt, e);
}
dataset.entityRollOver.call(hoverEnt, e);
}
};
},
drawLabel: function (annotationsArray) {
var entity = this,
dataset = entity.dataset,
config = entity.config,
showLabel = config.showLabel,
useValue,
i,
labelArr;
if (!showLabel) {
return;
}
if (typeof config.options === 'object') {
labelArr = config.labels;
i = (labelArr && labelArr.length) || 0;
useValue = config.options.isDataEnabled;
while (i--) {
annotationsArray.push(dataset._getLabelObject.call(entity, i, useValue, !i));
}
}
else {
annotationsArray.push(dataset._getLabelObject.call(entity, undefined, true, true));
}
},
drawLabelConnectors: function (connectorArr) {
var entity = this,
entityConf = entity.config,
dataset = entity.dataset,
chart = dataset.chart,
paper = chart.components.paper,
scalingParams = chart.config.scalingParams,
datasetGroup = chart.graphics.datasetGroup,
i = (connectorArr && connectorArr.length) || 0,
connElem,
path,
showLabel = entityConf.showLabel;
while (i--) {
path = connectorArr[i];
connElem = entity.graphics.connectorElem;
if (showLabel) {
if (!connElem) {
entity.graphics.connectorElem = connElem = paper.path(path, datasetGroup);
}
connElem.show().attr({
transform: (hasSVG || !isIE) ? '' : scalingParams.transformStr,
stroke: convertColor(entityConf.connectorColor, entityConf.connectorAlpha),
'shape-rendering': CRISP,
'stroke-width': entityConf.connectorThickness
});
}
else {
connElem && connElem.hide();
}
}
},
entityClick: function (e) {
var entity = this.node.__entity,
dataset = entity.dataset,
chart = dataset.chart,
linkClickFN = chart.linkedItems.linkClickFN,
config = entity.config,
link = config.link;
global.raiseEvent('entityclick', config.eventArgs,
chart.chartInstance, e);
// parse user links
(link !== UNDEFINED) && linkClickFN.call({
link: link
}, chart);
},
entityRollOver: function (e) {
var entityElem = this,
entity = entityElem.node.__entity,
dataset = entity.dataset,
config = entity.config,
chart = dataset.chart,
hoverAttr = config.hoverAttr;
lib.plotEventHandler.call(entityElem, chart, e, 'entityRollOver');
if (entityElem.data('hovered')) {
clearTimeout(entity.config.timer);
}
else {
if (config.useHoverColor && config.isVisible && !entity.hidden) {
chart.config.hoverEntity = entityElem;
entityElem.attr(hoverAttr);
entityElem.data('hovered', true);
}
// lib.plotEventHandler.call(entityElem, chart, e, 'entityRollOver');
// global.raiseEvent('entityRollOver', eventArgs, chart.chartInstance);
}
},
entityRollOut: function (e) {
var entityElem = this,
entity = this.node.__entity,
dataset = entity.dataset,
hidden,
chart = dataset.chart,
config = entity.config,
revertAttr = config.revertAttr;
/**
* A map might contain entities marked by concrete boundaries. For example, the India map has 28 states,
* each state can be marked as an entity . Every entity has an id by which it is referred to in the
* map definition file. The user can assign an in autonomous id's to the entity or use the original Id.
*
* The `entityRollOver` event is fired when the pointer is rolled over an entity.
* This event is followed either by the {@link FusionCharts#event:entityClick} event or the
* {@link FusionCharts#event:entityRollOut} event.
*
* @event FusionCharts#entityRollOver
* @group map:entity
* @see FusionCharts#event:entityClick
* @see FusionCharts#event:entityRollOut
*
* @param {number} value The value of the entity.
* @param {string} label The label of the entity.
* @param {string} shortLabel Short label used by the user.
* @param {string} originalId The ID of the entity stored in the map definition file.
* @param {string} id This could be the original ID or the ID assigned by the user.
*/
lib.plotEventHandler.call(entityElem, chart, e, 'entityRollOut');
// global.raiseEvent('entityRollOut', eventArgs, chart.chartInstance);
entity.config.timer = setTimeout(function () {
hidden = entity.hidden;
if (hidden !== true) {
entityElem.attr(revertAttr);
entityElem.data('hovered', false);
}
}, 100);
},
addMouseGestures: function () {
var entity = this,
config = entity.config,
originalId = config.originalId,
dataset = entity.dataset,
graphics = entity.graphics,
useHoverColor = 1,
hoverBorderThickness = config.hoverBorderThickness,
hoverBorderColor = config.hoverBorderColor,
hoverBorderAlpha = config.hoverBorderAlpha,
borderThickness = config.entityBorderThickness,
borderColor = config.borderColor,
borderAlpha = config.borderAlpha,
link = config.link,
visibleEntityAttr = config.visibleEntityAttr,
groupId = 'groupId' + originalId,
item,
i,
graphic,
len,
outlines,
bindListener = function (graphic) {
if (link !== UNDEFINED) {
graphic
.css({
cursor : 'pointer',
'_cursor': 'hand'
});
}
graphic.data('eventArgs', config.eventArgs);
graphic.data('groupId', groupId);
graphic.node.__entity = entity;
// If listeners are not binded
if (!entity._listenersBinded) {
graphic.click(dataset.entityClick)
.hover(dataset.entityRollOver, dataset.entityRollOut);
}
};
config.eventArgs = {
value: config.cleanValue,
label: config.label,
shortLabel: config.shortLabel,
originalId: config.origId,
id: config.id || config.origId
};
config.legacyEventArgs = {
value: config.value,
lName: config.label,
sName: config.shortLabel,
id: config.originalId || config.id
};
if (useHoverColor) {
config.hoverAttr = {
fill: toRaphaelColor(config.hoverColor)
};
config.revertAttr = {
fill: toRaphaelColor(config.fillColor),
stroke: toRaphaelColor(config.borderColor, config.borderAlpha)
};
config.revertAttr['fill-opacity'] = visibleEntityAttr['fill-opacity'];
if (hoverBorderThickness !== borderThickness) {
config.hoverAttr['stroke-width'] = pluckNumber(hoverBorderThickness, borderThickness);
config.revertAttr['stroke-width'] = borderThickness;
}
/* @todo: Enable once the drawing of entities allows these
* hover effects to be applied properly.
*/
if (hoverBorderColor !== borderColor || hoverBorderAlpha !== borderAlpha) {
config.hoverAttr.stoke = convertColor(hoverBorderColor, hoverBorderAlpha);
config.revertAttr.stroke = convertColor(borderColor, borderAlpha);
}
}
for (item in graphics) {
if (graphics.hasOwnProperty(item)) {
if (graphics[item] instanceof Array) {
outlines = graphics[item];
for (i = 0, len = outlines.length; i < len; i++) {
graphic = outlines[i].outline;
bindListener(graphic);
}
entity._listenersBinded = true;
}
else {
graphic = graphics[item];
bindListener(graphic);
entity._listenersBinded = true;
}
}
}
},
getDataLimits: function () {
var dataset = this,
conf = dataset.conf;
return {
max: conf.max,
min: conf.min
};
},
initComplete: function () {
var dataset = this,
chart = dataset.chart,
entities = dataset.components.data;
this.drawLabels.call(entities, dataset);
chart.config.entitiesReady = true;
chart.checkComplete();
}
}]);
FusionCharts.register('component', ['dataset', 'Markers', {
type: 'markers',
configure: function () {
var dataset = this,
chart = dataset.chart,
markerOptions = chart.config.markerOpts;
dataset.calculateDataLimits();
if (markerOptions.dataEnabled) {
this._parseMarkers();
}
else {
this.defineMarkersNShapes();
}
this.configureConnectors();
},
init: function (markerJSONData) {
var dataSet = this;
dataSet.JSONData = markerJSONData;
dataSet.components = {
};
dataSet.conf = {
};
dataSet.graphics = {
};
dataSet.configure();
},
calculateMarkerRadiusLimits: function () {
var dataset = this,
JSONData = dataset.JSONData,
conf = dataset.conf,
chart = dataset.chart,
width = chart.config.width,
height = chart.config.height,
markerMaxRadius = JSONData.markermaxradius,
markerMinRadius = JSONData.markerminradius,
minMax = this.getMarkerRadiusLimits(width, height, markerMaxRadius, markerMinRadius);
conf.minRadius = minMax.min;
conf.maxRadius = minMax.max;
},
calculateDataLimits: function () {
var dataset = this,
chart = dataset.chart,
conf = dataset.conf,
jsonData = chart.jsonData,
markers = jsonData.markers || {},
markerData = markers[MARKER_ITEM_KEY] || [],
numberFormatter = chart.components.numberFormatter,
min = +Infinity,
max = -Infinity,
markerObj,
cleanValue,
len,
value,
i;
for (i = 0, len = markerData.length; i < len; i++) {
markerObj = markerData[i];
value = markerObj.value;
cleanValue = numberFormatter.getCleanValue(value);
if (cleanValue !== null) {
min = mathMin(cleanValue, min);
max = mathMax(cleanValue, max);
}
}
conf.min = min;
conf.max = max;
},
_parseMarkers: function () {
var dataset = this,
chart = dataset.chart,
jsonData = chart.jsonData,
markers = jsonData.markers,
markerData = markers[MARKER_ITEM_KEY],
shapeArr = markers.shapes,
markerOptions = chart.config.markerOpts,
numberFormatter = chart.components.numberFormatter,
shapeObjs = dataset.components.shapeObjs = dataset.components.shapeObjs ||
(dataset.components.shapeObjs = {}),
markerObjs = dataset.components.markerObjs = dataset.components.markerObjs ||
(dataset.components.markerObjs = {}),
value,
i,
markerObj,
item,
shapeId,
markerConfig,
options,
id;
if (!markerData || !markerData.length) {
return;
}
if (shapeArr && shapeArr.length) {
i = shapeArr.length;
for (; i; i -= 1) {
item = shapeArr[i - 1];
if ((id = item.id.toLowerCase())) {
shapeObjs[id] = item;
}
}
}
i = markerData.length;
while (i--) {
item = markerData[i];
if ((id = (item.id && item.id.toLowerCase()))) {
value = item.value;
if (value !== UNDEFINED && value !== '') {
value = parseFloat(value);
}
markerObj = this._initializeMarkerItem(id, item, null, chart);
shapeId = markerObj.config.options.shapeid;
shapeId && typeof shapeId === 'string' && (shapeId = shapeId.toLowerCase());
markerConfig = markerObj.config;
options = markerConfig.options;
markerConfig.cleanValue = numberFormatter.getCleanValue(value);
if (markerConfig.cleanValue !== null) {
markerConfig.formattedValue = numberFormatter.dataLabels(value);
}
else {
markerConfig.formattedValue = UNDEFINED;
}
markerConfig.fillColor = pluck(options.fillcolor, options.color, markerOptions.fillColor),
markerConfig.fillAlpha = pluck(options.fillalpha, options.alpha, markerOptions.fillAlpha),
markerConfig.fillRatio = pluck(options.fillratio, markerOptions.fillRatio),
markerConfig.fillAngle = pluck(options.fillangle, markerOptions.fillAngle),
markerConfig.borderThickness = pluckNumber(options.borderthickness, markerOptions.borderThickness),
markerConfig.borderColor = pluck(options.bordercolor, markerOptions.borderColor),
markerConfig.borderAlpha = pluck(options.borderalpha, markerOptions.borderAlpha),
markerConfig.labelPadding = (options.labelpadding || markerOptions.labelPadding),
markerObj.dataset = dataset;
if (item.__hideMarker) {
markerObj._isHidden = true;
}
if (shapeId) {
markerObj.shapeObj = shapeObjs[shapeId];
}
markerObjs[id] = markerObj;
}
}
},
/**
* Create objects for the markers and shapes as per their definition.
*/
defineMarkersNShapes: function () {
var dataset = this,
chart = dataset.chart,
jsonData = chart.jsonData,
markers = jsonData.markers,
defineArr = markers.definition,
numberFormatter = chart.components.numberFormatter,
markerOptions = chart.config.markerOpts,
defineObject = convertToObj(defineArr) || {},
applyObject = convertToObj(markers.application) || {},
shapeArr = markers.shapes,
shapeObjs = dataset.components.shapeObjs = dataset.components.shapeObjs ||
(dataset.components.shapeObjs = {}),
markerObjs = dataset.components.markerObjs = dataset.components.markerObjs ||
(dataset.components.markerObjs = {}),
options,
markerConfig,
value,
i,
markerObj,
item,
shapeId,
id;
if (!defineArr || !defineArr.length) {
return;
}
if (shapeArr && shapeArr.length) {
i = shapeArr.length;
for (; i; i -= 1) {
item = shapeArr[i - 1];
if ((id = item.id.toLowerCase())) {
shapeObjs[id] = item;
}
}
}
for (id in defineObject) {
item = defineObject[id];
markerObj = markerObjs[id] = this._initializeMarkerItem(id, item, applyObject[id], chart);
markerObj.dataset = dataset;
shapeId = markerObj.config.options.shapeid;
markerConfig = markerObj.config;
value = item.value;
markerConfig.cleanValue = numberFormatter.getCleanValue(value);
options = markerConfig.options;
if (markerConfig.cleanValue !== null) {
markerConfig.formattedValue = numberFormatter.dataLabels(value);
}
else {
markerConfig.formattedValue = UNDEFINED;
}
markerConfig.fillColor = pluck(options.fillcolor, options.color, markerOptions.fillColor),
markerConfig.fillAlpha = pluck(options.fillalpha, options.alpha, markerOptions.fillAlpha),
markerConfig.fillRatio = pluck(options.fillratio, markerOptions.fillRatio),
markerConfig.fillAngle = pluck(options.fillangle, markerOptions.fillAngle),
markerConfig.borderThickness = pluckNumber(options.borderthickness, markerOptions.borderThickness),
markerConfig.borderColor = pluck(options.bordercolor, markerOptions.borderColor),
markerConfig.borderAlpha = pluck(options.borderalpha, markerOptions.borderAlpha),
markerConfig.labelPadding = (options.labelpadding || markerOptions.labelPadding);
markerConfig.options.tooltext = pluck(options.tooltext, markerOptions.tooltext);
markerConfig.link = options.link;
if (shapeId) {
markerObj.shapeObj = shapeObjs[shapeId.toLowerCase()];
}
}
},
getMarkerRadiusLimits: function (width, height, userMax, userMin) {
var dime = mathMin(width, height),
factor = 0.02,
times = 3.5,
minR = (factor * dime),
maxR = (factor * times * dime);
userMin = parseFloat(userMin);
userMax = parseFloat(userMax);
if (!isNaN(userMin) && !isNaN(userMax)) {
if (userMin < userMax) {
return {
min: userMin,
max: userMax
};
}
else {
return {
min: userMax,
max: userMin
};
}
}
else if (!isNaN(userMin)) {
return {
min: userMin,
max: 10 * userMin
};
}
else if (!isNaN(userMax)) {
return {
min: parseInt((userMax / 10), 10),
max: userMax
};
}
else {
return {
min: minR,
max: maxR
};
}
},
getDataLimits: function () {
var dataset = this,
conf = dataset.conf;
return {
min: conf.min,
max: conf.max
};
},
_initializeMarkerItem: function (id, markerDefinition, markerApplication) {
var markerObj = {},
config = markerObj.config,
opts;
if (!config) {
config = markerObj.config = {};
}
config.id = id;
config.definition = markerDefinition;
config.application = markerApplication;
// new member variables for value markers.
config.hasValue = null;
config.value = null;
config.options = null;
config.label = null;
config.markerShape = null;
config.markerLabel = null;
config.drawOptions = { shape: null, label: null };
config.drawComplete = false;
opts = markerObj.config.options = extend2({}, config.definition);
if (config.dataEnabled) {
if (!isNaN(opts.value) && opts.value !== '') {
markerObj.value = parseFloat(opts.value);
markerObj.hasValue = true;
}
}
else if (config.applyAll) {
config.options = extend2(opts, config.application);
}
else if (markerApplication) {
config.options = extend2(opts, config.application);
}
return markerObj;
},
configureConnectors: function () {
var dataset = this,
chart = dataset.chart,
jsonData = chart.jsonData,
datasetComponents = dataset.components,
markers = jsonData.markers || {},
connectors = markers.connector || markers.connectors || [],
markerObjs = datasetComponents.markerObjs,
length = connectors.length,
connectorComponents = dataset.components.connectors,
getMouseOverFn = function (eventArgs) {
return function (e) {
var shape = e.data,
wrapper = shape.wrapper;
if (wrapper && shape.options.hoverEffect) {
wrapper.attr(shape.options._hoverAttrs);
}
/**
* In maps, markers are used to denote important or essential locations.
* We might encounter situations where we will need to connect markers to make the
* information more lucid. `Connectors` are used to connect markers.
* The `connectorRollOver` event is fired when the pointer is rolled over the connector.
*
* @event FusionCharts#connectorRollOver
* @group map
*
* @param {string} fromMarkerId The Id of the marker from which the connector starts.
* @param {string} toMarkerId The Id of the marker to which the connector is drawn.
* @param {label} label The label on the connector.
*
*/
global.raiseEvent('connectorrollover', eventArgs, chart.chartInstance, e);
};
},
getMouseOutFn = function (eventArgs) {
return function (e) {
var shape = e.data,
wrapper = shape.wrapper;
if (wrapper && shape.options.hoverEffect) {
wrapper.attr(shape.options._defaultAttrs);
}
/**
* In maps, markers are used to denote important or essential locations. We might encounter
* situations where we will need to connect markers to make the information more lucid.
* `Connectors` are used to connect markers. The `connectorRollOut` event is fired when the
* pointer is rolled out of the connector. The {@link FusionCharts#event:connectorRollOver}
* event precedes this event.
*
* @event FusionCharts#connectorRollOut
* @group map
* @see FusionCharts#event:connectorRollOver
*
* @param {string} fromMarkerId The Id of the marker from which the connector starts.
* @param {string} toMarkerId The Id of the marker to which the connector is drawn.
* @param {label} label The label on the connector.
*/
global.raiseEvent('connectorrollout', eventArgs, chart.chartInstance, e);
};
},
getClickFn = function (eventArgs) {
return function (e) {
/**
* In maps, markers are used to denote important or essential locations.
* We might encounter situations where we will need to connect markers to make the
* information more lucid. `Connectors` are used to connect markers.
* The `connectClick` event is fired when a connector is clicked. It is preceded by
* the {@link FusionCharts#event:connectorRollOver} event.
*
* @event FusionCharts#connectorClick
* @param {string} fromMarkerId The Id of the marker from which the connector starts.
* @param {string} toMarkerId The Id of the marker to which the connector is drawn.
* @param {label} label The label on the connector.
*
* @example
* //declaring the fusioncharts object.
* var myMap = new FusionCharts( "Maps/FCMap_World.swf", "myMapId", "400", "300", "0" );
* //setting the data source.
* myMap.setXMLUrl("Data.xml");
* //rendering the chart in the associated Div.
* myMap.render("mapContainer")
*
* //function to perform the necessary action on capturing the connectorClicked event.
* //alert the user with the from and to marker id's.
* function listenerEvent(eventObject, argumentsObject){
* alert( "From marker ID: "+ argumentsObject.fromMarkerId + ",
* To marker ID: " + argumentsObject.toMarkerId);
* }
*
* //listening to the connector click event
* FusionCharts("myMapId").addEventListener ("connectorClicked" , listenerEvent );
*
*/
global.raiseEvent('connectorClick', eventArgs, chart.chartInstance, e);
};
},
chartConnOptions = chart.config.connectorOpts,
connectorItem = {},
config,
options,
fromMarker,
toMarker,
label,
toolText,
thickness,
color,
alpha,
hovercolor,
hoveralpha,
hoverthickness,
connLabelConfig,
obj,
i;
if (!connectorComponents) {
connectorComponents = dataset.components.connectors = [];
}
for (i = 0; i < length; i++) {
obj = connectors[i];
if (!obj.from && !obj.to) {
continue;
}
fromMarker = markerObjs[obj.from.toLowerCase()];
toMarker = markerObjs[obj.to.toLowerCase()];
if (!fromMarker || !toMarker) {
continue;
}
label = connectors[i].label;
// if (connectorConfig.hideOpen &&
// (fromMarker._isHidden || toMarker._isHidden)) {
// continue;
// }
connectorItem = connectorComponents[i];
!connectorItem && (connectorItem = connectorComponents[i] = {});
!connectorItem.config && (config = connectorItem.config = {});
!connectorItem.graphics && (connectorItem.graphics = {});
config = connectorItem.config = extend2({}, obj);
config.fromMarker = fromMarker;
config.toMarker = toMarker;
config.link = obj.link;
config.showTooltip = pluckNumber(obj.showtooltip, chartConnOptions.showTooltip),
toolText = config.tooltext = config.showTooltip ? pluck(obj.tooltext,
chartConnOptions.tooltext) : BLANK;
thickness = config.thickness = pluck(obj.thickness, chartConnOptions.thickness),
color = config.color = pluck(obj.color, chartConnOptions.color),
alpha = config.alpha = pluck(obj.alpha, chartConnOptions.alpha),
config.hoverEffect = pluckNumber(obj.showhovereffect, chartConnOptions.showHoverEffect),
hovercolor = pluck(obj.hovercolor, chartConnOptions.hoverColor, color),
hoveralpha = pluck(obj.hoveralpha, chartConnOptions.hoverAlpha, alpha),
hoverthickness = pluck(obj.hoverthickness, chartConnOptions.hoverThickness, thickness),
config.dashed = pluck(obj.dashed, chartConnOptions.dashed),
config.dashLen = pluckNumber(obj.dashlen, chartConnOptions.dashlen),
config.dashGap = pluckNumber(obj.dashgap, chartConnOptions.dashgap);
if (toolText) {
config.tooltext = toolText = parseUnsafeString(parseTooltext(toolText, [3, 40, 41, 42, 43], {
label: label,
fromId: fromMarker.config.definition.id,
toId: toMarker.config.definition.id,
fromLabel: fromMarker.config.definition.label,
toLabel: toMarker.config.definition.label
}, options));
}
config.eventArgs = {
fromMarkerId: fromMarker.config.id,
toMarkerId: toMarker.config.id,
label: label
};
config._hoverAttrs = {
stroke: colorize({
color: hovercolor,
alpha: hoveralpha
}).toString(),
'stroke-width': hoverthickness
};
config._defaultAttrs = {
stroke: colorize({
color: color,
alpha: alpha
}).toString(),
'stroke-width': thickness
};
config.type = 'line';
config.onclick = getClickFn(config.eventArgs);
config.onmouseover = getMouseOverFn(config.eventArgs);
config.onmouseout = getMouseOutFn(config.eventArgs);
if (label) {
connLabelConfig = connectorItem.labelConfig;
!connLabelConfig && (connLabelConfig = connectorItem.labelConfig = {});
connLabelConfig.type = 'text';
connLabelConfig.text = label;
connLabelConfig.align = POSITION_CENTER,
connLabelConfig.valign = POSITION_MIDDLE,
connLabelConfig.font = chartConnOptions.font,
connLabelConfig.fillcolor = chartConnOptions.fontColor;
connLabelConfig.bgcolor = chartConnOptions.labelBgColor;
connLabelConfig.bordercolor = chartConnOptions.labelBorderColor;
connLabelConfig.tooltext = chartConnOptions.tooltext;
}
}
},
draw: function () {
var dataset = this,
chart = dataset.chart,
conf = dataset.conf,
annotations = chart.components.mapAnnotations,
markers = dataset.components.markerObjs,
chartConf = chart.config,
chartMarkerOpts = chartConf.markerOpts,
scalingParams = chartConf.scalingParams,
markerItems = [],
markerLabels = [],
appliedMarkers = {},
appliedObj,
markerItem,
shapeId,
config,
id,
markerLabelGroup,
markerGroup,
jobList = chart.getJobList();
dataset.imageLoadCount = 0;
dataset.imageCount = 0;
markerLabelGroup = annotations.addGroup({
items: markerLabels
});
markerGroup = annotations.addGroup({
fillalpha: '100',
items: markerItems
});
dataset.components.markerGroup = markerGroup;
dataset.components.markerLabelGroup = markerLabelGroup;
// Have to configure the autoscale property in draw function as the scale factor is
// available only after spaceManagement
conf.autoScale = chartMarkerOpts.autoScale ? scalingParams.sFactor : 1;
for (id in markers) {
appliedObj = null;
markerItem = markers[id];
config = markerItem.config;
shapeId = config.options.shapeid;
if (!config.conIsHidden) {
appliedObj = this._drawMarkerItem.call(markerItem);
}
if (!appliedObj) {
continue;
}
config._annotationIndex = markerItems.length;
appliedMarkers[id] = markerItem;
markerItem.markerShape = appliedObj.markerShape &&
markerGroup.addItem(appliedObj.markerShape, false, chart);
markerItems.push(markerItem.markerShape);
markerItem.markerLabel = appliedObj.markerLabel &&
markerLabelGroup.addItem(appliedObj.markerLabel, false, chart);
markerLabels.push(markerItem.markerLabel);
}
this._drawConnectors();
jobList.kdTreeID.push(schedular.addJob(this._buildKdTree, dataset,
[], lib.priorityList.kdTree, true));
},
_buildKdTree: function () {
var dataset = this,
kdArrayMap = dataset.components.kdArrayMap,
markerGroup = dataset.components.markerGroup,
kdPointsArr = [],
id,
i,
items = markerGroup && markerGroup.items,
len = (items && items.length) || 0;
for (i = 0; i < len; i++) {
id = items[i]._id;
kdArrayMap[id] && kdPointsArr.push(kdArrayMap[id]);
}
if (!dataset.components.kDTree) {
//Create a new instance of the KdTree class and store its reference in the current instance.
dataset.components.kDTree = new KdTree(true);
}
dataset.components.kDTree._setSearchLimit(Infinity, Infinity);
dataset.components.kDTree.buildKdTree(kdPointsArr);
},
_drawMarkerItem: function () {
var marker = this,
dataset = marker.dataset,
chart = dataset.chart,
conf = chart.config,
datasetConfig = dataset.conf,
scalingParams = conf.scalingParams,
markerConfig = marker.config,
options = markerConfig.options,
definition = markerConfig.definition,
chartMarkerOpts = conf.markerOpts,
markerStyle = chartMarkerOpts.dataLabels.style,
shapeId = options.shapeid,
itemScale = (options.scale || 1),
label = options.label || BLANK,
scaleFactor = chart.config.scalingParams.scaleFactor * chart.baseScaleFactor,
labelPos = (options.labelpos || POSITION_TOP).toLowerCase(),
value = markerConfig.formattedValue,
tooltext = options.tooltext,
radius = (pluckNumber(definition.radius, markerConfig.radius, chartMarkerOpts.radius) *
itemScale * datasetConfig.autoScale) || 0.0001,
fillColor = markerConfig.fillColor,
fillAlpha = markerConfig.fillAlpha,
fillRatio = markerConfig.fillRatio,
fillAngle = markerConfig.fillAngle,
borderThickness = markerConfig.borderThickness,
borderColor = markerConfig.borderColor,
borderAlpha = markerConfig.borderAlpha,
labelObj,
align,
valign,
baseWidth,
baseHeight,
xOffset,
yOffset,
wrapWidth,
wrapHeight,
labelPadding,
shapeObj,
fillAttrs,
hoverFillAttrs,
shapeType,
calcX,
calcY,
kdPoint,
innerradius,
sides,
type,
kdArrayMap = dataset.components.kdArrayMap || (dataset.components.kdArrayMap = {}),
markerId = marker.config.id;
markerConfig.autoScale = chartMarkerOpts.autoScale ? scaleFactor : 1;
if (!shapeId) {
return;
}
if (tooltext) {
tooltext = parseUnsafeString(parseTooltext(tooltext, [1, 2, 3], {
formattedValue: value,
label: label
}, options));
}
else {
tooltext = (value ? (label + chartMarkerOpts.tooltipSepChar + value) : label);
}
if (value !== UNDEFINED && value !== null) {
/* value_for_markers */
label = label + chartMarkerOpts.labelSepChar + value;
}
else {
if (!isNaN(itemScale)) {
if (itemScale < 0) {
itemScale = 0;
}
else if (itemScale > 5) {
itemScale = 5;
}
}
else {
itemScale = 1;
}
}
extend2(options, {
x: options.x.toString(),
y: options.y.toString(),
fillcolor: fillColor,
fillalpha: fillAlpha,
fillratio: fillRatio,
fillangle: fillAngle,
borderthickness: borderThickness,
bordercolor: borderColor,
borderalpha: borderAlpha,
hovereffect: pluck(chartMarkerOpts.showHoverEffect),
radius: radius.toString(),
// tooltext: chartMarkerOpts.showTooltip ? tooltext : 0,
link: options.link,
showshadow: pluckNumber(options.showshadow, markerConfig.shadow),
_markerLabel: label, // for event
_markerId: options.id, // for event
id: (options.id + BLANK).toLowerCase()
});
delete options.tooltext;
markerConfig.tooltext = chartMarkerOpts.showTooltip ? tooltext : false;
shapeType = options.type;
calcX = (Number(options.x) * scalingParams.sFactor) + scalingParams.translateX;
calcY = (Number(options.y) * scalingParams.sFactor) + scalingParams.translateY;
radius = options.radius;
if (shapeId === 'triangle') {
extend2(options, {
type: 'polygon',
sides: 3,
startangle: chartMarkerOpts.startAngle
});
type = 'polygon';
sides = 3;
}
else if (shapeId === 'diamond') {
extend2(options, {
type: 'polygon',
sides: 4,
startangle: chartMarkerOpts.startAngle
});
type = 'polygon';
sides = 4;
}
else if (shapeId === 'arc') {
innerradius = radius * INNERRADIUSFACTOR;
extend2(options, {
type: 'arc',
startangle: 0,
endangle: 360,
innerradius: innerradius
});
type = 'arc';
}
else if (shapeId === 'circle') {
options.type = 'circle';
type = 'circle';
}
else {
shapeObj = dataset.getShapeArgs.call(marker);
if (!chartMarkerOpts.dataEnabled || !chartMarkerOpts.valueToRadius || options.radius === UNDEFINED) {
!shapeObj.radius && (shapeObj.radius = chartMarkerOpts.radius);
shapeObj.radius *= (itemScale * markerConfig.autoScale);
}
else {
delete shapeObj.radius;
}
extend2(options, shapeObj);
options.id = options._markerId && options._markerId.toLowerCase();
innerradius = shapeObj.innerradius;
radius = shapeObj.radius;
type = shapeObj.type;
sides = shapeObj.sides;
}
// Setting the hover attributes after all the cosmetics have been finalized.
extend2(options, {
hoverfillcolor: pluck(options.fillhovercolor, chartMarkerOpts.hoverFillColor, options.fillcolor),
hoverfillalpha: pluck(options.fillhoveralpha, chartMarkerOpts.hoverFillAlpha, options.fillalpha),
hoverfillratio: pluck(options.fillhoverratio, chartMarkerOpts.hoverFillRatio, options.fillratio),
hoverfillangle: pluck(options.fillhoverangle, chartMarkerOpts.hoverFillAngle, options.fillangle),
hoverborderthickness: pluckNumber(options.borderhoverthickness,
chartMarkerOpts.hoverBorderThickness,
options.borderthickness),
hoverbordercolor: pluck(options.borderhovercolor,
chartMarkerOpts.hoverBorderColor, options.bordercolor),
hoverborderalpha: pluck(options.borderhoveralpha,
chartMarkerOpts.hoverBorderAlpha, options.borderalpha)
});
fillAttrs = {
alpha: options.fillalpha,
color: options.fillcolor,
angle: 360 - options.fillangle,
ratio: options.fillratio
};
hoverFillAttrs = {
alpha: options.hoverfillalpha,
color: options.hoverfillcolor,
angle: 360 - options.hoverfillangle,
ratio: options.hoverfillratio
};
/** Hover Effect for markers **/
options._defaultattrs = {
fill: toRaphaelColor(fillAttrs),
'stroke-width': options.showborder !== '0' ? options.borderthickness : 0,
stroke: convertColor(options.bordercolor, options.borderalpha)
};
options._hoverattrs = {
fill: toRaphaelColor(hoverFillAttrs),
'stroke-width': options.showborder !== '0' ? options.hoverborderthickness : 0,
stroke: convertColor(options.hoverbordercolor, options.hoverborderalpha)
};
if (options.type === 'image') {
// In case of image there should not be a border around it by default.
options.borderthickness = options.borderthickness || 0;
options.onload = function (imageattr) {
var shape = this,
options = shape.options,
width = imageattr.width,
height = imageattr.height,
kdPoint = {},
// Recalculating x & y because by default annotations fx
// aligns the image to the top left corner.
// In this case the image needs to be centrally aligned.
calcX = (Number(options.x) - (width / (2 * scalingParams.sFactor))) * scalingParams.sFactor,
calcY = (Number(options.y) - (height / (2 * scalingParams.sFactor))) * scalingParams.sFactor,
item;
kdPoint = kdArrayMap[markerId] || (kdArrayMap[markerId] = {});
kdPoint.x = calcX + scalingParams.translateX;
kdPoint.y = calcY + scalingParams.translateY;
kdPoint.element = marker;
kdPoint.shapeInfo = {
type: 'rect',
width: width,
height: height
};
if (width && height) {
for (item in { wrapper: 1, tracker: 1 }) {
shape[item] && shape[item].attr({
x: calcX,
y: calcY,
width: width,
height: height
});
}
}
dataset.imageLoadCount++;
if (dataset.imageLoadCount === dataset.imageCount) {
dataset._buildKdTree();
}
};
options.onerror = function () {
dataset.imageLoadCount++;
if (dataset.imageLoadCount === dataset.imageCount) {
dataset._buildKdTree();
}
};
dataset.imageCount++;
}
else {
kdPoint = kdArrayMap[markerId] || (kdArrayMap[markerId] = {});
kdPoint.x = calcX;
kdPoint.y = calcY;
kdPoint.element = marker;
kdPoint.shapeInfo = {
type: type,
sides: sides,
radius: Number(radius) + (options.borderthickness / 2),
innerradius: innerradius
};
}
markerConfig.drawOptions.shape = options;
if (!chartMarkerOpts.showLabels) {
return {
markerShape: options
};
}
// creating marker label options here.
labelPadding = (options.labelpadding || chartMarkerOpts.labelPadding),
labelObj = dataset._getLabelOptions(labelPos, labelPadding, options),
align = labelObj.align,
valign = labelObj.valign,
baseWidth = markerConfig._labelBaseWidth,
baseHeight = markerConfig._labelBaseHeight,
xOffset = markerConfig._labelXOffset,
yOffset = markerConfig._labelYOffset,
wrapWidth = chartMarkerOpts.labelWrapWidth ? chartMarkerOpts.labelWrapWidth :
(dataset.getWrapWidth[align](baseWidth, Number(labelObj.x) + xOffset)),
wrapHeight = chartMarkerOpts.labelWrapHeight ? chartMarkerOpts.labelWrapHeight :
(dataset.getWrapHeight[valign](baseHeight, Number(labelObj.y) + yOffset));
if (wrapWidth > labelPadding) {
wrapWidth -= labelPadding;
}
if (wrapHeight > labelPadding) {
wrapHeight -= labelPadding;
}
// When the marker label is placed over the marker then the label
// will have the same hovering effect as the marker
if (align == 'center' && valign == 'middle') {
markerConfig.drawOptions.label = extend2({ type: 'text' }, {
text: label,
tooltext: options.tooltext,
x: labelObj.x,
y: labelObj.y,
align: align,
valign: labelObj.valign,
wrap: 1,
wrapwidth: wrapWidth,
wrapheight: wrapHeight,
fontsize: markerStyle.fontSize / scalingParams.sFactor,
font: markerStyle.fontFamily,
fillcolor: markerStyle.fontColor
});
} else {
markerConfig.drawOptions.label = extend2({ type: 'text' }, {
text: label,
tooltext: options.tooltext,
x: labelObj.x,
y: labelObj.y,
align: align,
valign: labelObj.valign,
wrap: 1,
wrapwidth: wrapWidth,
wrapheight: wrapHeight,
fontsize: markerStyle.fontSize / scalingParams.sFactor,
font: markerStyle.fontFamily,
fillcolor: markerStyle.fontColor
});
}
return {
markerShape: options,
markerLabel: markerConfig.drawOptions.label
};
},
getHoverFn: function () {
var dataset = this,
chart = dataset.chart;
return function () {
var element = this,
marker = element.data('marker'),
shape = marker.markerShape,
options = shape.options,
bounds = shape.bounds,
eventArgs = options._markerEventArgs,
wrapper = shape.wrapper,
fillOptions = shape.fillOptions,
attrs,
fillObj,
scalingParams = chart.config.scalingParams,
plotLeft = scalingParams.translateX,
plotTop = scalingParams.translateY,
markerConfig = marker.config;
if (wrapper && options.hovereffect) {
if (shape.type === 'circle') {
fillObj = {
color: options.hoverfillcolor,
alpha: options.hoverfillalpha,
angle: 360 - options.hoverfillangle,
ratio: options.hoverfillratio,
gradientUnits: 'objectBoundingBox',
radialGradient: fillOptions.radialGradient,
cx: fillOptions.cx,
cy: fillOptions.cy
};
options._hoverattrs.fill = toRaphaelColor(fillObj);
// options._hoverattrs = extend2(obj, options._hoverattrs);
}
attrs = extend2({}, options._hoverattrs);
if (wrapper.type === 'image') {
delete attrs.fill;
delete attrs.stroke;
delete attrs['stroke-width'];
}
wrapper.attr(attrs);
}
if (!eventArgs) {
eventArgs = options._markerEventArgs = {
x: bounds.x1 / bounds.xs,
y: bounds.y1 / bounds.ys,
scaledX: bounds.x1,
scaledY: bounds.y1,
chartX: plotLeft + bounds.x1,
chartY: plotTop + bounds.y1,
id: options._markerId,
label: options._markerLabel
};
}
/**
* ``Markers`` are used to denote important or essential points in a map.
* e.g In an India map , markers might be used to denote capitals of the different states.
* The markerRollOver event is fired when the pointer is rolled over a marker.
* @event FusionCharts#markerRollOver
* @group map
*
* @param {number} x The original X co-ordinate of the marker.
* @param {number} y The original Y co-ordinate of the marker.
* @param {number} scaledX The scaled value of X co-ordinate of the marker.
* @param {number} scaledY The scaled value of Y co-ordinate of the marker.
* @param {number} chartX The x position of the marker with respect to the top-left
* corner of the map canvas (that is 0,0 position).
* @param {number} chartY The y position of the marker with respect to the top-left
* corner of the map canvas (that is 0,0 position).
* @param {string} label The label of the marker.
*
* @example
* //declaring the FusionCharts object.
* var myMap = new FusionCharts( "Maps/FCMap_World.swf", "myMapId", "400", "300", "0" );
* //passing the data to the object in *XML* format.
* myMap.setXMLUrl("Data.xml");
* //rendering the chart in the map container.
* myMap.render("mapContainer");
*
* //the function which gets executed when the MarkerRollOver event is captured.
* function myChartListener(eventObject, argumentsObject){
* alert([
* "ID: ", argumentsObject.id, "; Label: ", argumentsObject.label,
* "; x: ", argumentsObject.x, ", y: ", argumentsObject.x,
* "; scaledX: ", argumentsObject.scaledX, ", scaledY: ", argumentsObject.scaledY,
* "; chartX: ", argumentsObject.chartX, ", chartY: ", argumentsObject.chartY
* ].join(""));
* }
*
* //listening to the markerRollOver event.
* FusionCharts("myMapId").addEventListener ("markerRollOver" , myChartListener );
**/
global.raiseEventGroup(markerConfig.id,'markerRollOver', eventArgs,
chart.chartInstance, markerConfig, undefined, undefined, undefined);
};
},
getHoverOutFn: function () {
var dataset = this,
chart = dataset.chart;
return function () {
var element = this,
marker = element.data('marker'),
shape = marker.markerShape,
wrapper = shape.wrapper,
fillOptions = shape.fillOptions,
markerConfig = marker.config,
options = shape.options,
attrs;
if (wrapper && shape.options.hovereffect) {
if (shape.type === 'circle') {
options._defaultattrs.fill = toRaphaelColor(fillOptions);
}
attrs = extend2({}, shape.options._defaultattrs);
if (wrapper.type === 'image') {
delete attrs.fill;
delete attrs.stroke;
delete attrs['stroke-width'];
}
wrapper.attr(attrs);
}
/**
* `Markers` are used to denote important or essential points in a map.
* e.g In an India map , markers might be used to denote capitals of the different states.
* The `markerRollOut` event is fired when the pointer is rolled out of a marker.
* This event is usually preceded by the {@link FusionCharts#markerRollOver} or the
* {@link FusionCharts#markerClicked} event.
*
* @event FusionCharts#markerRollOut
* @group map
*
* @param {number} x The original X co-ordinate of the marker.
* @param {number} y The original Y co-ordinate of the marker.
* @param {number} scaledX The scaled value of X co-ordinate of the marker.
* @param {number} scaledY The scaled value of Y co-ordinate of the marker.
* @param {number} chartX The x position of the marker with respect to the top-left
* corner of the map canvas (that is 0,0 position).
* @param {number} chartY The y position of the marker with respect to the top-left
* corner of the map canvas (that is 0,0 position).
* @param {string} label The label of the marker.
*
* @example
* //declaring the Fusion Charts object.
* var myMap = new FusionCharts( "Maps/FCMap_World.swf", "myMapId", "400", "300", "0" );
* //passing the data to the object in *XML* format.
* myMap.setXMLUrl("Data.xml");
* //rendering the chart in the map container.
* myMap.render("mapContainer");
*
* //the function which gets executed when the MarkerRollOut event is captured.
* function myChartListener(eventObject, argumentsObject){
* alert([
* "ID: ", argumentsObject.id, "; Label: ", argumentsObject.label,
* "; x: ", argumentsObject.x, ", y: ", argumentsObject.x,
* "; scaledX: ", argumentsObject.scaledX, ", scaledY: ", argumentsObject.scaledY,
* "; chartX: ", argumentsObject.chartX, ", chartY: ", argumentsObject.chartY
* ].join(""));
* }
*
* //listening to the markerRollOut event.
* FusionCharts("myMapId").addEventListener ("markerRollOut" , myChartListener );
*
*/
global.raiseEventGroup(markerConfig.id,'markerRollOut',
shape.options._markerEventArgs, chart.chartInstance, undefined, undefined, undefined);
};
},
getClickFn: function () {
var dataset = this;
return function (e) {
var element = this,
marker = element.data('marker'),
chart = dataset.chart,
scalingParams = chart.config.scalingParams,
plotLeft = scalingParams.translateX,
plotTop = scalingParams.translateY,
shape = marker.markerShape,
options = shape.options,
bounds = shape.bounds,
eventArgs = options._markerEventArgs;
if (!eventArgs) {
eventArgs = options._markerEventArgs = {
x: bounds.x1 / bounds.xs,
y: bounds.y1 / bounds.ys,
scaledX: bounds.x1,
scaledY: bounds.y1,
chartX: plotLeft + bounds.x1,
chartY: plotTop + bounds.y1,
id: options._markerId,
label: options._markerLabel
};
}
/*
* `Markers` are used to denote important or essential points in a map.
* e.g In an India map , markers might be used to denote capitals of the different states.
* The markerClick event is fired when a marker is clicked.
* This event is usually preceded by the {@link FusionCharts#event:markerRollOver} event.
*
* By listening to this event , the user can retrieve the position of the marker and the label
* associated with it.
*
* @event FusionCharts#markerClick
* @group map
*
* @param {number} x The original X co-ordinate of the marker.
* @param {number} y The original Y co-ordinate of the marker.
* @param {number} scaledX The scaled value of X co-ordinate of the marker.
* @param {number} scaledY The scaled value of Y co-ordinate of the marker.
* @param {number} chartX The x position of the marker with respect to the top-left
* corner of the map canvas (that is 0,0 position).
* @param {number} chartY The y position of the marker with respect to the top-left
* corner of the map canvas (that is 0,0 position).
* @param {string} label The label of the marker.
*
* @example
* //declaring the Fusion Charts object.
* var myMap = new FusionCharts( "Maps/FCMap_World.swf", "myMapId", "400", "300", "0" );
* //passing the data to the object in *XML* format.
* myMap.setXMLUrl("Data.xml");
* //rendering the chart in the map container.
* myMap.render("mapContainer");
*
* //the function which gets executed when the MarkerClick event is captured.
* function myChartListener(eventObject, argumentsObject){
* alert([
* "ID: ", argumentsObject.id, "; Label: ", argumentsObject.label,
* "; x: ", argumentsObject.x, ", y: ", argumentsObject.x,
* "; scaledX: ", argumentsObject.scaledX, ", scaledY: ", argumentsObject.scaledY,
* "; chartX: ", argumentsObject.chartX, ", chartY: ", argumentsObject.chartY
* ].join(""));
* }
*
* //listening to the markerClicked event.
* FusionCharts("myMapId").addEventListener ("markerClicked" , myChartListener );
*/
global.raiseEvent('markerClick', eventArgs, chart.chartInstance, e);
};
},
highlightPoint: function (kdPoint) {
var marker = kdPoint.element,
dataset = this,
chart = dataset.chart,
chartGraphics = chart.graphics,
paper = chart.components.paper,
trackerElems = chartGraphics.trackerElems || (chartGraphics.trackerElems = {}),
shapeInfo = kdPoint.shapeInfo,
type,
trackerElement,
x = kdPoint.x,
y = kdPoint.y,
sides,
startAngle,
width,
radius,
innerradius,
height,
endAngle,
angles,
lastHoveredPoint = chart.config.lastHoveredPoint,
newElem = false;
if (kdPoint === false) {
type = lastHoveredPoint && lastHoveredPoint.shapeInfo.type;
trackerElement = trackerElems[type];
trackerElement && trackerElement.hide();
chart.config.lastHoveredPoint = null;
return;
}
type = shapeInfo.type;
type === 'circle' && (type = 'polygon');
trackerElement = trackerElems[type];
if (type === 'polygon') {
sides = shapeInfo.sides || 1;
startAngle = shapeInfo.startAngle;
radius = shapeInfo.radius;
if (!trackerElement) {
trackerElement = chartGraphics.trackerElems[type] = paper.polypath(sides, x, y, radius, startAngle);
newElem = true;
}
else {
trackerElement.attr({
polypath: [sides, x, y, radius, startAngle]
});
}
}
else if (type === 'rect') {
width = shapeInfo.width;
height = shapeInfo.height;
if (!trackerElement) {
trackerElement = chartGraphics.trackerElems[type] = paper.rect(x, y, width, height);
newElem = true;
}
else {
trackerElement.attr({
x: x,
y: y,
width: width,
height: height
});
}
}
else if (type === 'arc') {
innerradius = marker.markerShape.bounds.innerR;
angles = marker.markerShape.bounds.angles;
startAngle = angles.start * deg2rad;
endAngle = angles.end * deg2rad;
radius = shapeInfo.radius;
if (!trackerElement) {
trackerElement = chartGraphics.trackerElems[type] = paper.ringpath(x, y, radius, innerradius,
startAngle, endAngle);
newElem = true;
}
else {
trackerElement.attr({
ringpath: [x, y, radius, innerradius, startAngle, endAngle]
});
}
}
chart.config.lastHoveredPoint = kdPoint;
if (newElem) {
trackerElement.attr({
fill: TRACKER_FILL,
stroke: TRACKER_FILL
})
.click(dataset.getClickFn())
.hover(dataset.getHoverFn(), dataset.getHoverOutFn());
trackerElement.trackTooltip(true);
}
// trackerElement.toFront();
trackerElement.show().tooltip(marker.config.tooltext)
.data('marker', marker);
// newElem && fireMouseEvent('mouseover', trackerElement && trackerElement.node,
// chartConfig.lastMouseEvent);
},
_drawConnectors: function () {
var dataset = this,
chart = dataset.chart,
datasetComponents = dataset.components,
connectors = datasetComponents.connectors,
length = connectors.length,
scalingParams = chart.config.scalingParams,
chartConnOptions = chart.config.connectorOpts,
showConnectorLabels = chartConnOptions.showLabels,
annotations = chart.components.mapAnnotations,
i,
connectorOptions = [],
connectorLabelItems = [],
x,
y,
toX,
toY,
fromMarkerConfig,
toMarkerConfig,
groups = [];
groups.push({
id: 'connectorLabels',
fillalpha: '100',
items: connectorLabelItems
});
groups.push({
id: 'connectors',
fillalpha: '100',
items: connectorOptions
});
for (i = 0; i < length; i++) {
if (!connectors[i]) {
continue;
}
fromMarkerConfig = connectors[i].config.fromMarker.config;
toMarkerConfig = connectors[i].config.toMarker.config;
x = fromMarkerConfig.options.x;
y = fromMarkerConfig.options.y;
toX = toMarkerConfig.options.x;
toY = toMarkerConfig.options.y;
connectors[i].config.x = x;
connectors[i].config.y = y;
connectors[i].config.tox = toX;
connectors[i].config.toy = toY;
connectorOptions.push(connectors[i].config);
if (connectors[i].labelConfig && showConnectorLabels) {
connectors[i].labelConfig.x = ((Number(x) + Number(toX)) / 2).toString();
connectors[i].labelConfig.y = ((Number(y) + Number(toY)) / 2).toString();
connectors[i].labelConfig.fontsize = (chartConnOptions.fontSize /
(scalingParams.scaleFactor * chart.baseScaleFactor));
connectorLabelItems.push(connectors[i].labelConfig);
}
}
annotations.addGroup(groups[0]);
annotations.addGroup(groups[1]);
},
getShapeArgs: function () {
var mark = this,
config = mark.config,
shapeObj = extend2({}, mark.shapeObj),
// FMXT-388: work on a copy of the shapeObj so as to not alter the original shapeObj.
radius;
config.autoScale = 1;
if (shapeObj) {
if (shapeObj.type === 'polygon') {
if (shapeObj.sides < 3) {
shapeObj.type = 'circle';
}
else {
shapeObj.startangle = config.startAngle;
}
}
else if (shapeObj.type === 'arc') {
radius = (shapeObj.radius || config.markerRadius) * config.autoScale;
shapeObj.radius = radius;
shapeObj.innerradius = ((shapeObj.innerradius &&
(shapeObj.innerradius * config.autoScale)) || (radius * INNERRADIUSFACTOR));
}
return shapeObj;
}
else {
return null;
}
},
_getLabelOptions: function (labelPos, labelPadding, options, width, height) {
var dataset = this,
radius,
x,
y,
alignment = labelPos && labelPos.toLowerCase();
// validate alignments
if (!dataset.getLabelAlignment[alignment]) {
alignment = 'center';
}
x = Number(options.x),
y = Number(options.y);
if (width === UNDEFINED || height === UNDEFINED) {
// not an image
radius = options.radius || 0;
}
else {
// image
radius = /^(top|bottom)$/ig.test(alignment) && (height * 0.5) ||
/^(left|right)$/ig.test(alignment) && (width * 0.5) || 0;
}
radius = (Number(radius) + Number(labelPadding));
return dataset.getLabelAlignment[alignment](x, y, radius);
},
getLabelAlignment: {
top: function (x, y, radius) {
return {
x: x.toString(),
y: (y - radius).toString(),
align: POSITION_CENTER,
valign: POSITION_TOP
};
},
left: function (x, y, radius) {
return {
x: (x - radius).toString(),
y: y.toString(),
align: POSITION_RIGHT,
valign: POSITION_MIDDLE
};
},
right: function (x, y, radius) {
return {
x: (x + radius).toString(),
y: y.toString(),
align: POSITION_LEFT,
valign: POSITION_MIDDLE
};
},
bottom: function (x, y, radius) {
return {
x: x.toString(),
y: (y + radius).toString(),
align: POSITION_CENTER,
valign: POSITION_BOTTOM
};
},
center: function (x, y) {
return {
x: x.toString(),
y: y.toString(),
align: POSITION_CENTER,
valign: POSITION_MIDDLE
};
}
},
getWrapWidth: {
right: function (width, x) {
return x;
},
left: function (width, x) {
return (width - x);
},
center: function (width, x) {
return (mathMin(x, width - x) * 2);
}
},
getWrapHeight: {
top: function (height, y) {
return y;
},
middle: function (height, y) {
return (mathMin(y, height - y) * 2);
},
bottom: function (height, y) {
return (height - y);
}
},
addMarkerItem: function (options) {
var markers = this,
item = options,
markerObj,
items = markers.components.markerObjs,
shapeObjs = markers.components.shapeObjs,
markerGroup = markers.components.markerGroup,
markerLabelGroup = markers.components.markerLabelGroup,
drawOptions,
shapeId,
id;
if ((id = item.id.toLowerCase())) {
if (items[id]) {
return;
}
// Data enabled markers not yet supported by this method.
delete item.value;
markers.imageLoadCount = 0;
markerObj = markers._initializeMarkerItem(id, item, null);
markerObj.dataset = markers;
shapeId = markerObj.config.options.shapeid;
if (shapeId) {
markerObj.shapeObj = shapeObjs[shapeId && shapeId.toLowerCase()];
}
items[id] = markerObj;
drawOptions = markers._drawMarkerItem.call(markerObj);
if (markerGroup && markerLabelGroup) {
markerObj.markerShape = drawOptions.markerShape &&
markerGroup.addItem(drawOptions.markerShape, true);
markerObj.markerLabel = drawOptions.markerLabel &&
markerLabelGroup.addItem(drawOptions.markerLabel, true);
}
markers._buildKdTree();
}
},
updateMarkerItem: function (id, options) {
var markers = this,
chart = markers.chart,
annotations = chart.components.mapAnnotations,
markerObjs = markers.components.markerObjs,
origOptions,
marker = markerObjs[id],
annotOptions;
if (marker) {
origOptions = marker.config.options;
// Add the marker options passed to the original options to persist in case of multiple updates.
extend2(origOptions, options);
markers.imageLoadCount = 0;
// Get the annotation options from marker options.
annotOptions = markers._drawMarkerItem.call(marker).markerShape;
markers._buildKdTree();
// Update annotations
annotations.update(id, annotOptions);
}
},
_removeMarkerItem: function (id) {
var markers = this,
components = markers.components,
markerObjs = components.markerObjs,
markerObj = markerObjs[id],
kdArrayMap = components.kdArrayMap,
markerShape,
markerLabel;
if (markerObj) {
markerShape = markerObj.markerShape;
markerLabel = markerObj.markerLabel;
markerShape && markerShape.destroy();
markerLabel && markerLabel.destroy();
delete kdArrayMap[id];
markers._buildKdTree();
}
delete markerObjs[id];
},
getElement: function (point) {
var datasetObj = this;
if (datasetObj.components.kDTree) {
// searches the neighbouring points using the kdtree instance.
return datasetObj.components.kDTree.getNeighbour(point);
}
}
}, 'Entities']);
}, [3, 2, 0, 'release']]);
}));