a87a9d784e48b8928da0d1757f783b239ea1f34c.svn-base 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882
  1. /**
  2. * @license Highcharts JS v4.2.5 (2016-05-06)
  3. *
  4. * (c) 2014 Highsoft AS
  5. * Authors: Jon Arild Nygard / Oystein Moseng
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. (function (factory) {
  10. if (typeof module === 'object' && module.exports) {
  11. module.exports = factory;
  12. } else {
  13. factory(Highcharts);
  14. }
  15. }(function (H) {
  16. var seriesTypes = H.seriesTypes,
  17. map = H.map,
  18. merge = H.merge,
  19. extend = H.extend,
  20. extendClass = H.extendClass,
  21. defaultOptions = H.getOptions(),
  22. plotOptions = defaultOptions.plotOptions,
  23. noop = function () {
  24. },
  25. each = H.each,
  26. grep = H.grep,
  27. pick = H.pick,
  28. Series = H.Series,
  29. stableSort = H.stableSort,
  30. Color = H.Color,
  31. eachObject = function (list, func, context) {
  32. var key;
  33. context = context || this;
  34. for (key in list) {
  35. if (list.hasOwnProperty(key)) {
  36. func.call(context, list[key], key, list);
  37. }
  38. }
  39. },
  40. reduce = function (arr, func, previous, context) {
  41. context = context || this;
  42. arr = arr || []; // @note should each be able to handle empty values automatically?
  43. each(arr, function (current, i) {
  44. previous = func.call(context, previous, current, i, arr);
  45. });
  46. return previous;
  47. },
  48. // @todo find correct name for this function.
  49. // @todo Similar to reduce, this function is likely redundant
  50. recursive = function (item, func, context) {
  51. var next;
  52. context = context || this;
  53. next = func.call(context, item);
  54. if (next !== false) {
  55. recursive(next, func, context);
  56. }
  57. };
  58. // Define default options
  59. plotOptions.treemap = merge(plotOptions.scatter, {
  60. showInLegend: false,
  61. marker: false,
  62. borderColor: '#E0E0E0',
  63. borderWidth: 1,
  64. dataLabels: {
  65. enabled: true,
  66. defer: false,
  67. verticalAlign: 'middle',
  68. formatter: function () { // #2945
  69. return this.point.name || this.point.id;
  70. },
  71. inside: true
  72. },
  73. tooltip: {
  74. headerFormat: '',
  75. pointFormat: '<b>{point.name}</b>: {point.node.val}</b><br/>'
  76. },
  77. layoutAlgorithm: 'sliceAndDice',
  78. layoutStartingDirection: 'vertical',
  79. alternateStartingDirection: false,
  80. levelIsConstant: true,
  81. opacity: 0.15,
  82. states: {
  83. hover: {
  84. borderColor: '#A0A0A0',
  85. brightness: seriesTypes.heatmap ? 0 : 0.1,
  86. opacity: 0.75,
  87. shadow: false
  88. }
  89. },
  90. drillUpButton: {
  91. position: {
  92. align: 'right',
  93. x: -10,
  94. y: 10
  95. }
  96. }
  97. });
  98. // Stolen from heatmap
  99. var colorSeriesMixin = {
  100. // mapping between SVG attributes and the corresponding options
  101. pointAttrToOptions: {},
  102. pointArrayMap: ['value'],
  103. axisTypes: seriesTypes.heatmap ? ['xAxis', 'yAxis', 'colorAxis'] : ['xAxis', 'yAxis'],
  104. optionalAxis: 'colorAxis',
  105. getSymbol: noop,
  106. parallelArrays: ['x', 'y', 'value', 'colorValue'],
  107. colorKey: 'colorValue', // Point color option key
  108. translateColors: seriesTypes.heatmap && seriesTypes.heatmap.prototype.translateColors
  109. };
  110. // The Treemap series type
  111. seriesTypes.treemap = extendClass(seriesTypes.scatter, merge(colorSeriesMixin, {
  112. type: 'treemap',
  113. trackerGroups: ['group', 'dataLabelsGroup'],
  114. pointClass: extendClass(H.Point, {
  115. setVisible: seriesTypes.pie.prototype.pointClass.prototype.setVisible
  116. }),
  117. /**
  118. * Creates an object map from parent id to childrens index.
  119. * @param {Array} data List of points set in options.
  120. * @param {string} data[].parent Parent id of point.
  121. * @param {Array} ids List of all point ids.
  122. * @return {Object} Map from parent id to children index in data.
  123. */
  124. getListOfParents: function (data, ids) {
  125. var listOfParents = reduce(data, function (prev, curr, i) {
  126. var parent = pick(curr.parent, '');
  127. if (prev[parent] === undefined) {
  128. prev[parent] = [];
  129. }
  130. prev[parent].push(i);
  131. return prev;
  132. }, {});
  133. // If parent does not exist, hoist parent to root of tree.
  134. eachObject(listOfParents, function (children, parent, list) {
  135. if ((parent !== '') && (H.inArray(parent, ids) === -1)) {
  136. each(children, function (child) {
  137. list[''].push(child);
  138. });
  139. delete list[parent];
  140. }
  141. });
  142. return listOfParents;
  143. },
  144. /**
  145. * Creates a tree structured object from the series points
  146. */
  147. getTree: function () {
  148. var tree,
  149. series = this,
  150. allIds = map(this.data, function (d) {
  151. return d.id;
  152. }),
  153. parentList = series.getListOfParents(this.data, allIds);
  154. series.nodeMap = [];
  155. tree = series.buildNode('', -1, 0, parentList, null);
  156. // Parents of the root node is by default visible
  157. recursive(this.nodeMap[this.rootNode], function (node) {
  158. var next = false,
  159. p = node.parent;
  160. node.visible = true;
  161. if (p || p === '') {
  162. next = series.nodeMap[p];
  163. }
  164. return next;
  165. });
  166. // Children of the root node is by default visible
  167. recursive(this.nodeMap[this.rootNode].children, function (children) {
  168. var next = false;
  169. each(children, function (child) {
  170. child.visible = true;
  171. if (child.children.length) {
  172. next = (next || []).concat(child.children);
  173. }
  174. });
  175. return next;
  176. });
  177. this.setTreeValues(tree);
  178. return tree;
  179. },
  180. init: function (chart, options) {
  181. var series = this;
  182. Series.prototype.init.call(series, chart, options);
  183. if (series.options.allowDrillToNode) {
  184. series.drillTo();
  185. }
  186. },
  187. buildNode: function (id, i, level, list, parent) {
  188. var series = this,
  189. children = [],
  190. point = series.points[i],
  191. node,
  192. child;
  193. // Actions
  194. each((list[id] || []), function (i) {
  195. child = series.buildNode(series.points[i].id, i, (level + 1), list, id);
  196. children.push(child);
  197. });
  198. node = {
  199. id: id,
  200. i: i,
  201. children: children,
  202. level: level,
  203. parent: parent,
  204. visible: false // @todo move this to better location
  205. };
  206. series.nodeMap[node.id] = node;
  207. if (point) {
  208. point.node = node;
  209. }
  210. return node;
  211. },
  212. setTreeValues: function (tree) {
  213. var series = this,
  214. options = series.options,
  215. childrenTotal = 0,
  216. children = [],
  217. val,
  218. point = series.points[tree.i];
  219. // First give the children some values
  220. each(tree.children, function (child) {
  221. child = series.setTreeValues(child);
  222. children.push(child);
  223. if (!child.ignore) {
  224. childrenTotal += child.val;
  225. } else {
  226. // @todo Add predicate to avoid looping already ignored children
  227. recursive(child.children, function (children) {
  228. var next = false;
  229. each(children, function (node) {
  230. extend(node, {
  231. ignore: true,
  232. isLeaf: false,
  233. visible: false
  234. });
  235. if (node.children.length) {
  236. next = (next || []).concat(node.children);
  237. }
  238. });
  239. return next;
  240. });
  241. }
  242. });
  243. // Sort the children
  244. stableSort(children, function (a, b) {
  245. return a.sortIndex - b.sortIndex;
  246. });
  247. // Set the values
  248. val = pick(point && point.value, childrenTotal);
  249. extend(tree, {
  250. children: children,
  251. childrenTotal: childrenTotal,
  252. // Ignore this node if point is not visible
  253. ignore: !(pick(point && point.visible, true) && (val > 0)),
  254. isLeaf: tree.visible && !childrenTotal,
  255. levelDynamic: (options.levelIsConstant ? tree.level : (tree.level - series.nodeMap[series.rootNode].level)),
  256. name: pick(point && point.name, ''),
  257. sortIndex: pick(point && point.sortIndex, -val),
  258. val: val
  259. });
  260. return tree;
  261. },
  262. /**
  263. * Recursive function which calculates the area for all children of a node.
  264. * @param {Object} node The node which is parent to the children.
  265. * @param {Object} area The rectangular area of the parent.
  266. */
  267. calculateChildrenAreas: function (parent, area) {
  268. var series = this,
  269. options = series.options,
  270. level = this.levelMap[parent.levelDynamic + 1],
  271. algorithm = pick((series[level && level.layoutAlgorithm] && level.layoutAlgorithm), options.layoutAlgorithm),
  272. alternate = options.alternateStartingDirection,
  273. childrenValues = [],
  274. children;
  275. // Collect all children which should be included
  276. children = grep(parent.children, function (n) {
  277. return !n.ignore;
  278. });
  279. if (level && level.layoutStartingDirection) {
  280. area.direction = level.layoutStartingDirection === 'vertical' ? 0 : 1;
  281. }
  282. childrenValues = series[algorithm](area, children);
  283. each(children, function (child, index) {
  284. var values = childrenValues[index];
  285. child.values = merge(values, {
  286. val: child.childrenTotal,
  287. direction: (alternate ? 1 - area.direction : area.direction)
  288. });
  289. child.pointValues = merge(values, {
  290. x: (values.x / series.axisRatio),
  291. width: (values.width / series.axisRatio)
  292. });
  293. // If node has children, then call method recursively
  294. if (child.children.length) {
  295. series.calculateChildrenAreas(child, child.values);
  296. }
  297. });
  298. },
  299. setPointValues: function () {
  300. var series = this,
  301. xAxis = series.xAxis,
  302. yAxis = series.yAxis;
  303. each(series.points, function (point) {
  304. var node = point.node,
  305. values = node.pointValues,
  306. x1,
  307. x2,
  308. y1,
  309. y2;
  310. // Points which is ignored, have no values.
  311. if (values && node.visible) {
  312. x1 = Math.round(xAxis.translate(values.x, 0, 0, 0, 1));
  313. x2 = Math.round(xAxis.translate(values.x + values.width, 0, 0, 0, 1));
  314. y1 = Math.round(yAxis.translate(values.y, 0, 0, 0, 1));
  315. y2 = Math.round(yAxis.translate(values.y + values.height, 0, 0, 0, 1));
  316. // Set point values
  317. point.shapeType = 'rect';
  318. point.shapeArgs = {
  319. x: Math.min(x1, x2),
  320. y: Math.min(y1, y2),
  321. width: Math.abs(x2 - x1),
  322. height: Math.abs(y2 - y1)
  323. };
  324. point.plotX = point.shapeArgs.x + (point.shapeArgs.width / 2);
  325. point.plotY = point.shapeArgs.y + (point.shapeArgs.height / 2);
  326. } else {
  327. // Reset visibility
  328. delete point.plotX;
  329. delete point.plotY;
  330. }
  331. });
  332. },
  333. setColorRecursive: function (node, color) {
  334. var series = this,
  335. point,
  336. level;
  337. if (node) {
  338. point = series.points[node.i];
  339. level = series.levelMap[node.levelDynamic];
  340. // Select either point color, level color or inherited color.
  341. color = pick(point && point.options.color, level && level.color, color);
  342. if (point) {
  343. point.color = color;
  344. }
  345. // Do it all again with the children
  346. if (node.children.length) {
  347. each(node.children, function (child) {
  348. series.setColorRecursive(child, color);
  349. });
  350. }
  351. }
  352. },
  353. algorithmGroup: function (h, w, d, p) {
  354. this.height = h;
  355. this.width = w;
  356. this.plot = p;
  357. this.direction = d;
  358. this.startDirection = d;
  359. this.total = 0;
  360. this.nW = 0;
  361. this.lW = 0;
  362. this.nH = 0;
  363. this.lH = 0;
  364. this.elArr = [];
  365. this.lP = {
  366. total: 0,
  367. lH: 0,
  368. nH: 0,
  369. lW: 0,
  370. nW: 0,
  371. nR: 0,
  372. lR: 0,
  373. aspectRatio: function (w, h) {
  374. return Math.max((w / h), (h / w));
  375. }
  376. };
  377. this.addElement = function (el) {
  378. this.lP.total = this.elArr[this.elArr.length - 1];
  379. this.total = this.total + el;
  380. if (this.direction === 0) {
  381. // Calculate last point old aspect ratio
  382. this.lW = this.nW;
  383. this.lP.lH = this.lP.total / this.lW;
  384. this.lP.lR = this.lP.aspectRatio(this.lW, this.lP.lH);
  385. // Calculate last point new aspect ratio
  386. this.nW = this.total / this.height;
  387. this.lP.nH = this.lP.total / this.nW;
  388. this.lP.nR = this.lP.aspectRatio(this.nW, this.lP.nH);
  389. } else {
  390. // Calculate last point old aspect ratio
  391. this.lH = this.nH;
  392. this.lP.lW = this.lP.total / this.lH;
  393. this.lP.lR = this.lP.aspectRatio(this.lP.lW, this.lH);
  394. // Calculate last point new aspect ratio
  395. this.nH = this.total / this.width;
  396. this.lP.nW = this.lP.total / this.nH;
  397. this.lP.nR = this.lP.aspectRatio(this.lP.nW, this.nH);
  398. }
  399. this.elArr.push(el);
  400. };
  401. this.reset = function () {
  402. this.nW = 0;
  403. this.lW = 0;
  404. this.elArr = [];
  405. this.total = 0;
  406. };
  407. },
  408. algorithmCalcPoints: function (directionChange, last, group, childrenArea) {
  409. var pX,
  410. pY,
  411. pW,
  412. pH,
  413. gW = group.lW,
  414. gH = group.lH,
  415. plot = group.plot,
  416. keep,
  417. i = 0,
  418. end = group.elArr.length - 1;
  419. if (last) {
  420. gW = group.nW;
  421. gH = group.nH;
  422. } else {
  423. keep = group.elArr[group.elArr.length - 1];
  424. }
  425. each(group.elArr, function (p) {
  426. if (last || (i < end)) {
  427. if (group.direction === 0) {
  428. pX = plot.x;
  429. pY = plot.y;
  430. pW = gW;
  431. pH = p / pW;
  432. } else {
  433. pX = plot.x;
  434. pY = plot.y;
  435. pH = gH;
  436. pW = p / pH;
  437. }
  438. childrenArea.push({
  439. x: pX,
  440. y: pY,
  441. width: pW,
  442. height: pH
  443. });
  444. if (group.direction === 0) {
  445. plot.y = plot.y + pH;
  446. } else {
  447. plot.x = plot.x + pW;
  448. }
  449. }
  450. i = i + 1;
  451. });
  452. // Reset variables
  453. group.reset();
  454. if (group.direction === 0) {
  455. group.width = group.width - gW;
  456. } else {
  457. group.height = group.height - gH;
  458. }
  459. plot.y = plot.parent.y + (plot.parent.height - group.height);
  460. plot.x = plot.parent.x + (plot.parent.width - group.width);
  461. if (directionChange) {
  462. group.direction = 1 - group.direction;
  463. }
  464. // If not last, then add uncalculated element
  465. if (!last) {
  466. group.addElement(keep);
  467. }
  468. },
  469. algorithmLowAspectRatio: function (directionChange, parent, children) {
  470. var childrenArea = [],
  471. series = this,
  472. pTot,
  473. plot = {
  474. x: parent.x,
  475. y: parent.y,
  476. parent: parent
  477. },
  478. direction = parent.direction,
  479. i = 0,
  480. end = children.length - 1,
  481. group = new this.algorithmGroup(parent.height, parent.width, direction, plot);
  482. // Loop through and calculate all areas
  483. each(children, function (child) {
  484. pTot = (parent.width * parent.height) * (child.val / parent.val);
  485. group.addElement(pTot);
  486. if (group.lP.nR > group.lP.lR) {
  487. series.algorithmCalcPoints(directionChange, false, group, childrenArea, plot);
  488. }
  489. // If last child, then calculate all remaining areas
  490. if (i === end) {
  491. series.algorithmCalcPoints(directionChange, true, group, childrenArea, plot);
  492. }
  493. i = i + 1;
  494. });
  495. return childrenArea;
  496. },
  497. algorithmFill: function (directionChange, parent, children) {
  498. var childrenArea = [],
  499. pTot,
  500. direction = parent.direction,
  501. x = parent.x,
  502. y = parent.y,
  503. width = parent.width,
  504. height = parent.height,
  505. pX,
  506. pY,
  507. pW,
  508. pH;
  509. each(children, function (child) {
  510. pTot = (parent.width * parent.height) * (child.val / parent.val);
  511. pX = x;
  512. pY = y;
  513. if (direction === 0) {
  514. pH = height;
  515. pW = pTot / pH;
  516. width = width - pW;
  517. x = x + pW;
  518. } else {
  519. pW = width;
  520. pH = pTot / pW;
  521. height = height - pH;
  522. y = y + pH;
  523. }
  524. childrenArea.push({
  525. x: pX,
  526. y: pY,
  527. width: pW,
  528. height: pH
  529. });
  530. if (directionChange) {
  531. direction = 1 - direction;
  532. }
  533. });
  534. return childrenArea;
  535. },
  536. strip: function (parent, children) {
  537. return this.algorithmLowAspectRatio(false, parent, children);
  538. },
  539. squarified: function (parent, children) {
  540. return this.algorithmLowAspectRatio(true, parent, children);
  541. },
  542. sliceAndDice: function (parent, children) {
  543. return this.algorithmFill(true, parent, children);
  544. },
  545. stripes: function (parent, children) {
  546. return this.algorithmFill(false, parent, children);
  547. },
  548. translate: function () {
  549. var pointValues,
  550. seriesArea,
  551. tree,
  552. val;
  553. // Call prototype function
  554. Series.prototype.translate.call(this);
  555. // Assign variables
  556. this.rootNode = pick(this.options.rootId, '');
  557. // Create a object map from level to options
  558. this.levelMap = reduce(this.options.levels, function (arr, item) {
  559. arr[item.level] = item;
  560. return arr;
  561. }, {});
  562. tree = this.tree = this.getTree(); // @todo Only if series.isDirtyData is true
  563. // Calculate plotting values.
  564. this.axisRatio = (this.xAxis.len / this.yAxis.len);
  565. this.nodeMap[''].pointValues = pointValues = { x: 0, y: 0, width: 100, height: 100 };
  566. this.nodeMap[''].values = seriesArea = merge(pointValues, {
  567. width: (pointValues.width * this.axisRatio),
  568. direction: (this.options.layoutStartingDirection === 'vertical' ? 0 : 1),
  569. val: tree.val
  570. });
  571. this.calculateChildrenAreas(tree, seriesArea);
  572. // Logic for point colors
  573. if (this.colorAxis) {
  574. this.translateColors();
  575. } else if (!this.options.colorByPoint) {
  576. this.setColorRecursive(this.tree, undefined);
  577. }
  578. // Update axis extremes according to the root node.
  579. if (this.options.allowDrillToNode) {
  580. val = this.nodeMap[this.rootNode].pointValues;
  581. this.xAxis.setExtremes(val.x, val.x + val.width, false);
  582. this.yAxis.setExtremes(val.y, val.y + val.height, false);
  583. this.xAxis.setScale();
  584. this.yAxis.setScale();
  585. }
  586. // Assign values to points.
  587. this.setPointValues();
  588. },
  589. /**
  590. * Extend drawDataLabels with logic to handle custom options related to the treemap series:
  591. * - Points which is not a leaf node, has dataLabels disabled by default.
  592. * - Options set on series.levels is merged in.
  593. * - Width of the dataLabel is set to match the width of the point shape.
  594. */
  595. drawDataLabels: function () {
  596. var series = this,
  597. points = grep(series.points, function (n) {
  598. return n.node.visible;
  599. }),
  600. options,
  601. level;
  602. each(points, function (point) {
  603. level = series.levelMap[point.node.levelDynamic];
  604. // Set options to new object to avoid problems with scope
  605. options = { style: {} };
  606. // If not a leaf, then label should be disabled as default
  607. if (!point.node.isLeaf) {
  608. options.enabled = false;
  609. }
  610. // If options for level exists, include them as well
  611. if (level && level.dataLabels) {
  612. options = merge(options, level.dataLabels);
  613. series._hasPointLabels = true;
  614. }
  615. // Set dataLabel width to the width of the point shape.
  616. if (point.shapeArgs) {
  617. options.style.width = point.shapeArgs.width;
  618. if (point.dataLabel) {
  619. point.dataLabel.css({ width: point.shapeArgs.width + 'px' });
  620. }
  621. }
  622. // Merge custom options with point options
  623. point.dlOptions = merge(options, point.options.dataLabels);
  624. });
  625. Series.prototype.drawDataLabels.call(this);
  626. },
  627. alignDataLabel: seriesTypes.column.prototype.alignDataLabel,
  628. /**
  629. * Get presentational attributes
  630. */
  631. pointAttribs: function (point, state) {
  632. var level = this.levelMap[point.node.levelDynamic] || {},
  633. options = this.options,
  634. attr,
  635. stateOptions = (state && options.states[state]) || {},
  636. opacity;
  637. // Set attributes by precedence. Point trumps level trumps series. Stroke width uses pick
  638. // because it can be 0.
  639. attr = {
  640. 'stroke': point.borderColor || level.borderColor || stateOptions.borderColor || options.borderColor,
  641. 'stroke-width': pick(point.borderWidth, level.borderWidth, stateOptions.borderWidth, options.borderWidth),
  642. 'dashstyle': point.borderDashStyle || level.borderDashStyle || stateOptions.borderDashStyle || options.borderDashStyle,
  643. 'fill': point.color || this.color,
  644. 'zIndex': state === 'hover' ? 1 : 0
  645. };
  646. if (point.node.level <= this.nodeMap[this.rootNode].level) {
  647. // Hide levels above the current view
  648. attr.fill = 'none';
  649. attr['stroke-width'] = 0;
  650. } else if (!point.node.isLeaf) {
  651. // If not a leaf, either set opacity or remove fill
  652. if (pick(options.interactByLeaf, !options.allowDrillToNode)) {
  653. attr.fill = 'none';
  654. } else {
  655. opacity = pick(stateOptions.opacity, options.opacity);
  656. attr.fill = Color(attr.fill).setOpacity(opacity).get();
  657. }
  658. } else if (state) {
  659. // Brighten and hoist the hover nodes
  660. attr.fill = Color(attr.fill).brighten(stateOptions.brightness).get();
  661. }
  662. return attr;
  663. },
  664. /**
  665. * Extending ColumnSeries drawPoints
  666. */
  667. drawPoints: function () {
  668. var series = this,
  669. points = grep(series.points, function (n) {
  670. return n.node.visible;
  671. });
  672. each(points, function (point) {
  673. var groupKey = 'levelGroup-' + point.node.levelDynamic;
  674. if (!series[groupKey]) {
  675. series[groupKey] = series.chart.renderer.g(groupKey)
  676. .attr({
  677. zIndex: 1000 - point.node.levelDynamic // @todo Set the zIndex based upon the number of levels, instead of using 1000
  678. })
  679. .add(series.group);
  680. }
  681. point.group = series[groupKey];
  682. // Preliminary code in prepraration for HC5 that uses pointAttribs for all series
  683. point.pointAttr = {
  684. '': series.pointAttribs(point),
  685. 'hover': series.pointAttribs(point, 'hover'),
  686. 'select': {}
  687. };
  688. });
  689. // Call standard drawPoints
  690. seriesTypes.column.prototype.drawPoints.call(this);
  691. // If drillToNode is allowed, set a point cursor on clickables & add drillId to point
  692. if (series.options.allowDrillToNode) {
  693. each(points, function (point) {
  694. var cursor,
  695. drillId;
  696. if (point.graphic) {
  697. drillId = point.drillId = series.options.interactByLeaf ? series.drillToByLeaf(point) : series.drillToByGroup(point);
  698. cursor = drillId ? 'pointer' : 'default';
  699. point.graphic.css({ cursor: cursor });
  700. }
  701. });
  702. }
  703. },
  704. /**
  705. * Add drilling on the suitable points
  706. */
  707. drillTo: function () {
  708. var series = this;
  709. H.addEvent(series, 'click', function (event) {
  710. var point = event.point,
  711. drillId = point.drillId,
  712. drillName;
  713. // If a drill id is returned, add click event and cursor.
  714. if (drillId) {
  715. drillName = series.nodeMap[series.rootNode].name || series.rootNode;
  716. point.setState(''); // Remove hover
  717. series.drillToNode(drillId);
  718. series.showDrillUpButton(drillName);
  719. }
  720. });
  721. },
  722. /**
  723. * Finds the drill id for a parent node.
  724. * Returns false if point should not have a click event
  725. * @param {Object} point
  726. * @return {string || boolean} Drill to id or false when point should not have a click event
  727. */
  728. drillToByGroup: function (point) {
  729. var series = this,
  730. drillId = false;
  731. if ((point.node.level - series.nodeMap[series.rootNode].level) === 1 && !point.node.isLeaf) {
  732. drillId = point.id;
  733. }
  734. return drillId;
  735. },
  736. /**
  737. * Finds the drill id for a leaf node.
  738. * Returns false if point should not have a click event
  739. * @param {Object} point
  740. * @return {string || boolean} Drill to id or false when point should not have a click event
  741. */
  742. drillToByLeaf: function (point) {
  743. var series = this,
  744. drillId = false,
  745. nodeParent;
  746. if ((point.node.parent !== series.rootNode) && (point.node.isLeaf)) {
  747. nodeParent = point.node;
  748. while (!drillId) {
  749. nodeParent = series.nodeMap[nodeParent.parent];
  750. if (nodeParent.parent === series.rootNode) {
  751. drillId = nodeParent.id;
  752. }
  753. }
  754. }
  755. return drillId;
  756. },
  757. drillUp: function () {
  758. var drillPoint = null,
  759. node,
  760. parent;
  761. if (this.rootNode) {
  762. node = this.nodeMap[this.rootNode];
  763. if (node.parent !== null) {
  764. drillPoint = this.nodeMap[node.parent];
  765. } else {
  766. drillPoint = this.nodeMap[''];
  767. }
  768. }
  769. if (drillPoint !== null) {
  770. this.drillToNode(drillPoint.id);
  771. if (drillPoint.id === '') {
  772. this.drillUpButton = this.drillUpButton.destroy();
  773. } else {
  774. parent = this.nodeMap[drillPoint.parent];
  775. this.showDrillUpButton((parent.name || parent.id));
  776. }
  777. }
  778. },
  779. drillToNode: function (id) {
  780. this.options.rootId = id;
  781. this.isDirty = true; // Force redraw
  782. this.chart.redraw();
  783. },
  784. showDrillUpButton: function (name) {
  785. var series = this,
  786. backText = (name || '< Back'),
  787. buttonOptions = series.options.drillUpButton,
  788. attr,
  789. states;
  790. if (buttonOptions.text) {
  791. backText = buttonOptions.text;
  792. }
  793. if (!this.drillUpButton) {
  794. attr = buttonOptions.theme;
  795. states = attr && attr.states;
  796. this.drillUpButton = this.chart.renderer.button(
  797. backText,
  798. null,
  799. null,
  800. function () {
  801. series.drillUp();
  802. },
  803. attr,
  804. states && states.hover,
  805. states && states.select
  806. )
  807. .attr({
  808. align: buttonOptions.position.align,
  809. zIndex: 9
  810. })
  811. .add()
  812. .align(buttonOptions.position, false, buttonOptions.relativeTo || 'plotBox');
  813. } else {
  814. this.drillUpButton.attr({
  815. text: backText
  816. })
  817. .align();
  818. }
  819. },
  820. buildKDTree: noop,
  821. drawLegendSymbol: H.LegendSymbolMixin.drawRectangle,
  822. getExtremes: function () {
  823. // Get the extremes from the value data
  824. Series.prototype.getExtremes.call(this, this.colorValueData);
  825. this.valueMin = this.dataMin;
  826. this.valueMax = this.dataMax;
  827. // Get the extremes from the y data
  828. Series.prototype.getExtremes.call(this);
  829. },
  830. getExtremesFromAll: true,
  831. bindAxes: function () {
  832. var treeAxis = {
  833. endOnTick: false,
  834. gridLineWidth: 0,
  835. lineWidth: 0,
  836. min: 0,
  837. dataMin: 0,
  838. minPadding: 0,
  839. max: 100,
  840. dataMax: 100,
  841. maxPadding: 0,
  842. startOnTick: false,
  843. title: null,
  844. tickPositions: []
  845. };
  846. Series.prototype.bindAxes.call(this);
  847. H.extend(this.yAxis.options, treeAxis);
  848. H.extend(this.xAxis.options, treeAxis);
  849. }
  850. }));
  851. }));