heatmap.src.js 16 KB

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