You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

699 lines
17 KiB

9 years ago
  1. /**
  2. * @license Highcharts JS v4.1.8 (2015-08-20)
  3. *
  4. * (c) 2011-2014 Torstein Honsi
  5. *
  6. * License: www.highcharts.com/license
  7. */
  8. /*global HighchartsAdapter*/
  9. (function (Highcharts) {
  10. var UNDEFINED,
  11. Axis = Highcharts.Axis,
  12. Chart = Highcharts.Chart,
  13. Color = Highcharts.Color,
  14. Legend = Highcharts.Legend,
  15. LegendSymbolMixin = Highcharts.LegendSymbolMixin,
  16. Series = Highcharts.Series,
  17. Point = Highcharts.Point,
  18. defaultOptions = Highcharts.getOptions(),
  19. each = Highcharts.each,
  20. extend = Highcharts.extend,
  21. extendClass = Highcharts.extendClass,
  22. merge = Highcharts.merge,
  23. pick = Highcharts.pick,
  24. seriesTypes = Highcharts.seriesTypes,
  25. wrap = Highcharts.wrap,
  26. noop = function () {};
  27. /**
  28. * The ColorAxis object for inclusion in gradient legends
  29. */
  30. var ColorAxis = Highcharts.ColorAxis = function () {
  31. this.isColorAxis = true;
  32. this.init.apply(this, arguments);
  33. };
  34. extend(ColorAxis.prototype, Axis.prototype);
  35. extend(ColorAxis.prototype, {
  36. defaultColorAxisOptions: {
  37. lineWidth: 0,
  38. minPadding: 0,
  39. maxPadding: 0,
  40. gridLineWidth: 1,
  41. tickPixelInterval: 72,
  42. startOnTick: true,
  43. endOnTick: true,
  44. offset: 0,
  45. marker: {
  46. animation: {
  47. duration: 50
  48. },
  49. color: 'gray',
  50. width: 0.01
  51. },
  52. labels: {
  53. overflow: 'justify'
  54. },
  55. minColor: '#EFEFFF',
  56. maxColor: '#003875',
  57. tickLength: 5
  58. },
  59. init: function (chart, userOptions) {
  60. var horiz = chart.options.legend.layout !== 'vertical',
  61. options;
  62. // Build the options
  63. options = merge(this.defaultColorAxisOptions, {
  64. side: horiz ? 2 : 1,
  65. reversed: !horiz
  66. }, userOptions, {
  67. opposite: !horiz,
  68. showEmpty: false,
  69. title: null,
  70. isColor: true
  71. });
  72. Axis.prototype.init.call(this, chart, options);
  73. // Base init() pushes it to the xAxis array, now pop it again
  74. //chart[this.isXAxis ? 'xAxis' : 'yAxis'].pop();
  75. // Prepare data classes
  76. if (userOptions.dataClasses) {
  77. this.initDataClasses(userOptions);
  78. }
  79. this.initStops(userOptions);
  80. // Override original axis properties
  81. this.horiz = horiz;
  82. this.zoomEnabled = false;
  83. },
  84. /*
  85. * Return an intermediate color between two colors, according to pos where 0
  86. * is the from color and 1 is the to color.
  87. * NOTE: Changes here should be copied
  88. * to the same function in drilldown.src.js and solid-gauge-src.js.
  89. */
  90. tweenColors: function (from, to, pos) {
  91. // Check for has alpha, because rgba colors perform worse due to lack of
  92. // support in WebKit.
  93. var hasAlpha,
  94. ret;
  95. // Unsupported color, return to-color (#3920)
  96. if (!to.rgba.length || !from.rgba.length) {
  97. ret = to.raw || 'none';
  98. // Interpolate
  99. } else {
  100. from = from.rgba;
  101. to = to.rgba;
  102. hasAlpha = (to[3] !== 1 || from[3] !== 1);
  103. ret = (hasAlpha ? 'rgba(' : 'rgb(') +
  104. Math.round(to[0] + (from[0] - to[0]) * (1 - pos)) + ',' +
  105. Math.round(to[1] + (from[1] - to[1]) * (1 - pos)) + ',' +
  106. Math.round(to[2] + (from[2] - to[2]) * (1 - pos)) +
  107. (hasAlpha ? (',' + (to[3] + (from[3] - to[3]) * (1 - pos))) : '') + ')';
  108. }
  109. return ret;
  110. },
  111. initDataClasses: function (userOptions) {
  112. var axis = this,
  113. chart = this.chart,
  114. dataClasses,
  115. colorCounter = 0,
  116. options = this.options,
  117. len = userOptions.dataClasses.length;
  118. this.dataClasses = dataClasses = [];
  119. this.legendItems = [];
  120. each(userOptions.dataClasses, function (dataClass, i) {
  121. var colors;
  122. dataClass = merge(dataClass);
  123. dataClasses.push(dataClass);
  124. if (!dataClass.color) {
  125. if (options.dataClassColor === 'category') {
  126. colors = chart.options.colors;
  127. dataClass.color = colors[colorCounter++];
  128. // loop back to zero
  129. if (colorCounter === colors.length) {
  130. colorCounter = 0;
  131. }
  132. } else {
  133. dataClass.color = axis.tweenColors(
  134. Color(options.minColor),
  135. Color(options.maxColor),
  136. len < 2 ? 0.5 : i / (len - 1) // #3219
  137. );
  138. }
  139. }
  140. });
  141. },
  142. initStops: function (userOptions) {
  143. this.stops = userOptions.stops || [
  144. [0, this.options.minColor],
  145. [1, this.options.maxColor]
  146. ];
  147. each(this.stops, function (stop) {
  148. stop.color = Color(stop[1]);
  149. });
  150. },
  151. /**
  152. * Extend the setOptions method to process extreme colors and color
  153. * stops.
  154. */
  155. setOptions: function (userOptions) {
  156. Axis.prototype.setOptions.call(this, userOptions);
  157. this.options.crosshair = this.options.marker;
  158. this.coll = 'colorAxis';
  159. },
  160. setAxisSize: function () {
  161. var symbol = this.legendSymbol,
  162. chart = this.chart,
  163. x,
  164. y,
  165. width,
  166. height;
  167. if (symbol) {
  168. this.left = x = symbol.attr('x');
  169. this.top = y = symbol.attr('y');
  170. this.width = width = symbol.attr('width');
  171. this.height = height = symbol.attr('height');
  172. this.right = chart.chartWidth - x - width;
  173. this.bottom = chart.chartHeight - y - height;
  174. this.len = this.horiz ? width : height;
  175. this.pos = this.horiz ? x : y;
  176. }
  177. },
  178. /**
  179. * Translate from a value to a color
  180. */
  181. toColor: function (value, point) {
  182. var pos,
  183. stops = this.stops,
  184. from,
  185. to,
  186. color,
  187. dataClasses = this.dataClasses,
  188. dataClass,
  189. i;
  190. if (dataClasses) {
  191. i = dataClasses.length;
  192. while (i--) {
  193. dataClass = dataClasses[i];
  194. from = dataClass.from;
  195. to = dataClass.to;
  196. if ((from === UNDEFINED || value >= from) && (to === UNDEFINED || value <= to)) {
  197. color = dataClass.color;
  198. if (point) {
  199. point.dataClass = i;
  200. }
  201. break;
  202. }
  203. }
  204. } else {
  205. if (this.isLog) {
  206. value = this.val2lin(value);
  207. }
  208. pos = 1 - ((this.max - value) / ((this.max - this.min) || 1));
  209. i = stops.length;
  210. while (i--) {
  211. if (pos > stops[i][0]) {
  212. break;
  213. }
  214. }
  215. from = stops[i] || stops[i + 1];
  216. to = stops[i + 1] || from;
  217. // The position within the gradient
  218. pos = 1 - (to[0] - pos) / ((to[0] - from[0]) || 1);
  219. color = this.tweenColors(
  220. from.color,
  221. to.color,
  222. pos
  223. );
  224. }
  225. return color;
  226. },
  227. getOffset: function () {
  228. var group = this.legendGroup,
  229. sideOffset = this.chart.axisOffset[this.side];
  230. if (group) {
  231. Axis.prototype.getOffset.call(this);
  232. if (!this.axisGroup.parentGroup) {
  233. // Move the axis elements inside the legend group
  234. this.axisGroup.add(group);
  235. this.gridGroup.add(group);
  236. this.labelGroup.add(group);
  237. this.added = true;
  238. this.labelLeft = 0;
  239. this.labelRight = this.width;
  240. }
  241. // Reset it to avoid color axis reserving space
  242. this.chart.axisOffset[this.side] = sideOffset;
  243. }
  244. },
  245. /**
  246. * Create the color gradient
  247. */
  248. setLegendColor: function () {
  249. var grad,
  250. horiz = this.horiz,
  251. options = this.options,
  252. reversed = this.reversed;
  253. grad = horiz ? [+reversed, 0, +!reversed, 0] : [0, +!reversed, 0, +reversed]; // #3190
  254. this.legendColor = {
  255. linearGradient: { x1: grad[0], y1: grad[1], x2: grad[2], y2: grad[3] },
  256. stops: options.stops || [
  257. [0, options.minColor],
  258. [1, options.maxColor]
  259. ]
  260. };
  261. },
  262. /**
  263. * The color axis appears inside the legend and has its own legend symbol
  264. */
  265. drawLegendSymbol: function (legend, item) {
  266. var padding = legend.padding,
  267. legendOptions = legend.options,
  268. horiz = this.horiz,
  269. box,
  270. width = pick(legendOptions.symbolWidth, horiz ? 200 : 12),
  271. height = pick(legendOptions.symbolHeight, horiz ? 12 : 200),
  272. labelPadding = pick(legendOptions.labelPadding, horiz ? 16 : 30),
  273. itemDistance = pick(legendOptions.itemDistance, 10);
  274. this.setLegendColor();
  275. // Create the gradient
  276. item.legendSymbol = this.chart.renderer.rect(
  277. 0,
  278. legend.baseline - 11,
  279. width,
  280. height
  281. ).attr({
  282. zIndex: 1
  283. }).add(item.legendGroup);
  284. box = item.legendSymbol.getBBox();
  285. // Set how much space this legend item takes up
  286. this.legendItemWidth = width + padding + (horiz ? itemDistance : labelPadding);
  287. this.legendItemHeight = height + padding + (horiz ? labelPadding : 0);
  288. },
  289. /**
  290. * Fool the legend
  291. */
  292. setState: noop,
  293. visible: true,
  294. setVisible: noop,
  295. getSeriesExtremes: function () {
  296. var series;
  297. if (this.series.length) {
  298. series = this.series[0];
  299. this.dataMin = series.valueMin;
  300. this.dataMax = series.valueMax;
  301. }
  302. },
  303. drawCrosshair: function (e, point) {
  304. var plotX = point && point.plotX,
  305. plotY = point && point.plotY,
  306. crossPos,
  307. axisPos = this.pos,
  308. axisLen = this.len;
  309. if (point) {
  310. crossPos = this.toPixels(point[point.series.colorKey]);
  311. if (crossPos < axisPos) {
  312. crossPos = axisPos - 2;
  313. } else if (crossPos > axisPos + axisLen) {
  314. crossPos = axisPos + axisLen + 2;
  315. }
  316. point.plotX = crossPos;
  317. point.plotY = this.len - crossPos;
  318. Axis.prototype.drawCrosshair.call(this, e, point);
  319. point.plotX = plotX;
  320. point.plotY = plotY;
  321. if (this.cross) {
  322. this.cross
  323. .attr({
  324. fill: this.crosshair.color
  325. })
  326. .add(this.legendGroup);
  327. }
  328. }
  329. },
  330. getPlotLinePath: function (a, b, c, d, pos) {
  331. if (typeof pos === 'number') { // crosshairs only // #3969 pos can be 0 !!
  332. return this.horiz ?
  333. ['M', pos - 4, this.top - 6, 'L', pos + 4, this.top - 6, pos, this.top, 'Z'] :
  334. ['M', this.left, pos, 'L', this.left - 6, pos + 6, this.left - 6, pos - 6, 'Z'];
  335. } else {
  336. return Axis.prototype.getPlotLinePath.call(this, a, b, c, d);
  337. }
  338. },
  339. update: function (newOptions, redraw) {
  340. var chart = this.chart,
  341. legend = chart.legend;
  342. each(this.series, function (series) {
  343. series.isDirtyData = true; // Needed for Axis.update when choropleth colors change
  344. });
  345. // When updating data classes, destroy old items and make sure new ones are created (#3207)
  346. if (newOptions.dataClasses && legend.allItems) {
  347. each(legend.allItems, function (item) {
  348. if (item.isDataClass) {
  349. item.legendGroup.destroy();
  350. }
  351. });
  352. chart.isDirtyLegend = true;
  353. }
  354. // Keep the options structure updated for export. Unlike xAxis and yAxis, the colorAxis is
  355. // not an array. (#3207)
  356. chart.options[this.coll] = merge(this.userOptions, newOptions);
  357. Axis.prototype.update.call(this, newOptions, redraw);
  358. if (this.legendItem) {
  359. this.setLegendColor();
  360. legend.colorizeItem(this, true);
  361. }
  362. },
  363. /**
  364. * Get the legend item symbols for data classes
  365. */
  366. getDataClassLegendSymbols: function () {
  367. var axis = this,
  368. chart = this.chart,
  369. legendItems = this.legendItems,
  370. legendOptions = chart.options.legend,
  371. valueDecimals = legendOptions.valueDecimals,
  372. valueSuffix = legendOptions.valueSuffix || '',
  373. name;
  374. if (!legendItems.length) {
  375. each(this.dataClasses, function (dataClass, i) {
  376. var vis = true,
  377. from = dataClass.from,
  378. to = dataClass.to;
  379. // Assemble the default name. This can be overridden by legend.options.labelFormatter
  380. name = '';
  381. if (from === UNDEFINED) {
  382. name = '< ';
  383. } else if (to === UNDEFINED) {
  384. name = '> ';
  385. }
  386. if (from !== UNDEFINED) {
  387. name += Highcharts.numberFormat(from, valueDecimals) + valueSuffix;
  388. }
  389. if (from !== UNDEFINED && to !== UNDEFINED) {
  390. name += ' - ';
  391. }
  392. if (to !== UNDEFINED) {
  393. name += Highcharts.numberFormat(to, valueDecimals) + valueSuffix;
  394. }
  395. // Add a mock object to the legend items
  396. legendItems.push(extend({
  397. chart: chart,
  398. name: name,
  399. options: {},
  400. drawLegendSymbol: LegendSymbolMixin.drawRectangle,
  401. visible: true,
  402. setState: noop,
  403. isDataClass: true,
  404. setVisible: function () {
  405. vis = this.visible = !vis;
  406. each(axis.series, function (series) {
  407. each(series.points, function (point) {
  408. if (point.dataClass === i) {
  409. point.setVisible(vis);
  410. }
  411. });
  412. });
  413. chart.legend.colorizeItem(this, vis);
  414. }
  415. }, dataClass));
  416. });
  417. }
  418. return legendItems;
  419. },
  420. name: '' // Prevents 'undefined' in legend in IE8
  421. });
  422. /**
  423. * Handle animation of the color attributes directly
  424. */
  425. each(['fill', 'stroke'], function (prop) {
  426. HighchartsAdapter.addAnimSetter(prop, function (fx) {
  427. fx.elem.attr(prop, ColorAxis.prototype.tweenColors(Color(fx.start), Color(fx.end), fx.pos));
  428. });
  429. });
  430. /**
  431. * Extend the chart getAxes method to also get the color axis
  432. */
  433. wrap(Chart.prototype, 'getAxes', function (proceed) {
  434. var options = this.options,
  435. colorAxisOptions = options.colorAxis;
  436. proceed.call(this);
  437. this.colorAxis = [];
  438. if (colorAxisOptions) {
  439. proceed = new ColorAxis(this, colorAxisOptions); // Fake assignment for jsLint
  440. }
  441. });
  442. /**
  443. * Wrap the legend getAllItems method to add the color axis. This also removes the
  444. * axis' own series to prevent them from showing up individually.
  445. */
  446. wrap(Legend.prototype, 'getAllItems', function (proceed) {
  447. var allItems = [],
  448. colorAxis = this.chart.colorAxis[0];
  449. if (colorAxis) {
  450. // Data classes
  451. if (colorAxis.options.dataClasses) {
  452. allItems = allItems.concat(colorAxis.getDataClassLegendSymbols());
  453. // Gradient legend
  454. } else {
  455. // Add this axis on top
  456. allItems.push(colorAxis);
  457. }
  458. // Don't add the color axis' series
  459. each(colorAxis.series, function (series) {
  460. series.options.showInLegend = false;
  461. });
  462. }
  463. return allItems.concat(proceed.call(this));
  464. });/**
  465. * Mixin for maps and heatmaps
  466. */
  467. var colorPointMixin = {
  468. /**
  469. * Set the visibility of a single point
  470. */
  471. setVisible: function (vis) {
  472. var point = this,
  473. method = vis ? 'show' : 'hide';
  474. // Show and hide associated elements
  475. each(['graphic', 'dataLabel'], function (key) {
  476. if (point[key]) {
  477. point[key][method]();
  478. }
  479. });
  480. }
  481. };
  482. var colorSeriesMixin = {
  483. pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
  484. stroke: 'borderColor',
  485. 'stroke-width': 'borderWidth',
  486. fill: 'color',
  487. dashstyle: 'dashStyle'
  488. },
  489. pointArrayMap: ['value'],
  490. axisTypes: ['xAxis', 'yAxis', 'colorAxis'],
  491. optionalAxis: 'colorAxis',
  492. trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
  493. getSymbol: noop,
  494. parallelArrays: ['x', 'y', 'value'],
  495. colorKey: 'value',
  496. /**
  497. * In choropleth maps, the color is a result of the value, so this needs translation too
  498. */
  499. translateColors: function () {
  500. var series = this,
  501. nullColor = this.options.nullColor,
  502. colorAxis = this.colorAxis,
  503. colorKey = this.colorKey;
  504. each(this.data, function (point) {
  505. var value = point[colorKey],
  506. color;
  507. color = point.options.color ||
  508. (value === null ? nullColor : (colorAxis && value !== undefined) ? colorAxis.toColor(value, point) : point.color || series.color);
  509. if (color) {
  510. point.color = color;
  511. }
  512. });
  513. }
  514. };
  515. /**
  516. * Extend the default options with map options
  517. */
  518. defaultOptions.plotOptions.heatmap = merge(defaultOptions.plotOptions.scatter, {
  519. animation: false,
  520. borderWidth: 0,
  521. nullColor: '#F8F8F8',
  522. dataLabels: {
  523. formatter: function () { // #2945
  524. return this.point.value;
  525. },
  526. inside: true,
  527. verticalAlign: 'middle',
  528. crop: false,
  529. overflow: false,
  530. padding: 0 // #3837
  531. },
  532. marker: null,
  533. pointRange: null, // dynamically set to colsize by default
  534. tooltip: {
  535. pointFormat: '{point.x}, {point.y}: {point.value}<br/>'
  536. },
  537. states: {
  538. normal: {
  539. animation: true
  540. },
  541. hover: {
  542. halo: false, // #3406, halo is not required on heatmaps
  543. brightness: 0.2
  544. }
  545. }
  546. });
  547. // The Heatmap series type
  548. seriesTypes.heatmap = extendClass(seriesTypes.scatter, merge(colorSeriesMixin, {
  549. type: 'heatmap',
  550. pointArrayMap: ['y', 'value'],
  551. hasPointSpecificOptions: true,
  552. pointClass: extendClass(Point, colorPointMixin),
  553. supportsDrilldown: true,
  554. getExtremesFromAll: true,
  555. directTouch: true,
  556. /**
  557. * Override the init method to add point ranges on both axes.
  558. */
  559. init: function () {
  560. var options;
  561. seriesTypes.scatter.prototype.init.apply(this, arguments);
  562. options = this.options;
  563. this.pointRange = options.pointRange = pick(options.pointRange, options.colsize || 1); // #3758, prevent resetting in setData
  564. this.yAxis.axisPointRange = options.rowsize || 1; // general point range
  565. },
  566. translate: function () {
  567. var series = this,
  568. options = series.options,
  569. xAxis = series.xAxis,
  570. yAxis = series.yAxis;
  571. series.generatePoints();
  572. each(series.points, function (point) {
  573. var xPad = (options.colsize || 1) / 2,
  574. yPad = (options.rowsize || 1) / 2,
  575. x1 = Math.round(xAxis.len - xAxis.translate(point.x - xPad, 0, 1, 0, 1)),
  576. x2 = Math.round(xAxis.len - xAxis.translate(point.x + xPad, 0, 1, 0, 1)),
  577. y1 = Math.round(yAxis.translate(point.y - yPad, 0, 1, 0, 1)),
  578. y2 = Math.round(yAxis.translate(point.y + yPad, 0, 1, 0, 1));
  579. // Set plotX and plotY for use in K-D-Tree and more
  580. point.plotX = point.clientX = (x1 + x2) / 2;
  581. point.plotY = (y1 + y2) / 2;
  582. point.shapeType = 'rect';
  583. point.shapeArgs = {
  584. x: Math.min(x1, x2),
  585. y: Math.min(y1, y2),
  586. width: Math.abs(x2 - x1),
  587. height: Math.abs(y2 - y1)
  588. };
  589. });
  590. series.translateColors();
  591. // Make sure colors are updated on colorAxis update (#2893)
  592. if (this.chart.hasRendered) {
  593. each(series.points, function (point) {
  594. point.shapeArgs.fill = point.options.color || point.color; // #3311
  595. });
  596. }
  597. },
  598. drawPoints: seriesTypes.column.prototype.drawPoints,
  599. animate: noop,
  600. getBox: noop,
  601. drawLegendSymbol: LegendSymbolMixin.drawRectangle,
  602. getExtremes: function () {
  603. // Get the extremes from the value data
  604. Series.prototype.getExtremes.call(this, this.valueData);
  605. this.valueMin = this.dataMin;
  606. this.valueMax = this.dataMax;
  607. // Get the extremes from the y data
  608. Series.prototype.getExtremes.call(this);
  609. }
  610. }));
  611. }(Highcharts));