Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 
 

706 рядки
17 KiB

  1. /**
  2. * @license Highmaps JS v1.1.9 (2015-10-07)
  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. /**
  228. * Override the getOffset method to add the whole axis groups inside the legend.
  229. */
  230. getOffset: function () {
  231. var group = this.legendGroup,
  232. sideOffset = this.chart.axisOffset[this.side];
  233. if (group) {
  234. // Hook for the getOffset method to add groups to this parent group
  235. this.axisParent = group;
  236. // Call the base
  237. Axis.prototype.getOffset.call(this);
  238. // First time only
  239. if (!this.added) {
  240. this.added = true;
  241. this.labelLeft = 0;
  242. this.labelRight = this.width;
  243. }
  244. // Reset it to avoid color axis reserving space
  245. this.chart.axisOffset[this.side] = sideOffset;
  246. }
  247. },
  248. /**
  249. * Create the color gradient
  250. */
  251. setLegendColor: function () {
  252. var grad,
  253. horiz = this.horiz,
  254. options = this.options,
  255. reversed = this.reversed;
  256. grad = horiz ? [+reversed, 0, +!reversed, 0] : [0, +!reversed, 0, +reversed]; // #3190
  257. this.legendColor = {
  258. linearGradient: { x1: grad[0], y1: grad[1], x2: grad[2], y2: grad[3] },
  259. stops: options.stops || [
  260. [0, options.minColor],
  261. [1, options.maxColor]
  262. ]
  263. };
  264. },
  265. /**
  266. * The color axis appears inside the legend and has its own legend symbol
  267. */
  268. drawLegendSymbol: function (legend, item) {
  269. var padding = legend.padding,
  270. legendOptions = legend.options,
  271. horiz = this.horiz,
  272. box,
  273. width = pick(legendOptions.symbolWidth, horiz ? 200 : 12),
  274. height = pick(legendOptions.symbolHeight, horiz ? 12 : 200),
  275. labelPadding = pick(legendOptions.labelPadding, horiz ? 16 : 30),
  276. itemDistance = pick(legendOptions.itemDistance, 10);
  277. this.setLegendColor();
  278. // Create the gradient
  279. item.legendSymbol = this.chart.renderer.rect(
  280. 0,
  281. legend.baseline - 11,
  282. width,
  283. height
  284. ).attr({
  285. zIndex: 1
  286. }).add(item.legendGroup);
  287. box = item.legendSymbol.getBBox();
  288. // Set how much space this legend item takes up
  289. this.legendItemWidth = width + padding + (horiz ? itemDistance : labelPadding);
  290. this.legendItemHeight = height + padding + (horiz ? labelPadding : 0);
  291. },
  292. /**
  293. * Fool the legend
  294. */
  295. setState: noop,
  296. visible: true,
  297. setVisible: noop,
  298. getSeriesExtremes: function () {
  299. var series;
  300. if (this.series.length) {
  301. series = this.series[0];
  302. this.dataMin = series.valueMin;
  303. this.dataMax = series.valueMax;
  304. }
  305. },
  306. drawCrosshair: function (e, point) {
  307. var plotX = point && point.plotX,
  308. plotY = point && point.plotY,
  309. crossPos,
  310. axisPos = this.pos,
  311. axisLen = this.len;
  312. if (point) {
  313. crossPos = this.toPixels(point[point.series.colorKey]);
  314. if (crossPos < axisPos) {
  315. crossPos = axisPos - 2;
  316. } else if (crossPos > axisPos + axisLen) {
  317. crossPos = axisPos + axisLen + 2;
  318. }
  319. point.plotX = crossPos;
  320. point.plotY = this.len - crossPos;
  321. Axis.prototype.drawCrosshair.call(this, e, point);
  322. point.plotX = plotX;
  323. point.plotY = plotY;
  324. if (this.cross) {
  325. this.cross
  326. .attr({
  327. fill: this.crosshair.color
  328. })
  329. .add(this.legendGroup);
  330. }
  331. }
  332. },
  333. getPlotLinePath: function (a, b, c, d, pos) {
  334. if (typeof pos === 'number') { // crosshairs only // #3969 pos can be 0 !!
  335. return this.horiz ?
  336. ['M', pos - 4, this.top - 6, 'L', pos + 4, this.top - 6, pos, this.top, 'Z'] :
  337. ['M', this.left, pos, 'L', this.left - 6, pos + 6, this.left - 6, pos - 6, 'Z'];
  338. } else {
  339. return Axis.prototype.getPlotLinePath.call(this, a, b, c, d);
  340. }
  341. },
  342. update: function (newOptions, redraw) {
  343. var chart = this.chart,
  344. legend = chart.legend;
  345. each(this.series, function (series) {
  346. series.isDirtyData = true; // Needed for Axis.update when choropleth colors change
  347. });
  348. // When updating data classes, destroy old items and make sure new ones are created (#3207)
  349. if (newOptions.dataClasses && legend.allItems) {
  350. each(legend.allItems, function (item) {
  351. if (item.isDataClass) {
  352. item.legendGroup.destroy();
  353. }
  354. });
  355. chart.isDirtyLegend = true;
  356. }
  357. // Keep the options structure updated for export. Unlike xAxis and yAxis, the colorAxis is
  358. // not an array. (#3207)
  359. chart.options[this.coll] = merge(this.userOptions, newOptions);
  360. Axis.prototype.update.call(this, newOptions, redraw);
  361. if (this.legendItem) {
  362. this.setLegendColor();
  363. legend.colorizeItem(this, true);
  364. }
  365. },
  366. /**
  367. * Get the legend item symbols for data classes
  368. */
  369. getDataClassLegendSymbols: function () {
  370. var axis = this,
  371. chart = this.chart,
  372. legendItems = this.legendItems,
  373. legendOptions = chart.options.legend,
  374. valueDecimals = legendOptions.valueDecimals,
  375. valueSuffix = legendOptions.valueSuffix || '',
  376. name;
  377. if (!legendItems.length) {
  378. each(this.dataClasses, function (dataClass, i) {
  379. var vis = true,
  380. from = dataClass.from,
  381. to = dataClass.to;
  382. // Assemble the default name. This can be overridden by legend.options.labelFormatter
  383. name = '';
  384. if (from === UNDEFINED) {
  385. name = '< ';
  386. } else if (to === UNDEFINED) {
  387. name = '> ';
  388. }
  389. if (from !== UNDEFINED) {
  390. name += Highcharts.numberFormat(from, valueDecimals) + valueSuffix;
  391. }
  392. if (from !== UNDEFINED && to !== UNDEFINED) {
  393. name += ' - ';
  394. }
  395. if (to !== UNDEFINED) {
  396. name += Highcharts.numberFormat(to, valueDecimals) + valueSuffix;
  397. }
  398. // Add a mock object to the legend items
  399. legendItems.push(extend({
  400. chart: chart,
  401. name: name,
  402. options: {},
  403. drawLegendSymbol: LegendSymbolMixin.drawRectangle,
  404. visible: true,
  405. setState: noop,
  406. isDataClass: true,
  407. setVisible: function () {
  408. vis = this.visible = !vis;
  409. each(axis.series, function (series) {
  410. each(series.points, function (point) {
  411. if (point.dataClass === i) {
  412. point.setVisible(vis);
  413. }
  414. });
  415. });
  416. chart.legend.colorizeItem(this, vis);
  417. }
  418. }, dataClass));
  419. });
  420. }
  421. return legendItems;
  422. },
  423. name: '' // Prevents 'undefined' in legend in IE8
  424. });
  425. /**
  426. * Handle animation of the color attributes directly
  427. */
  428. each(['fill', 'stroke'], function (prop) {
  429. HighchartsAdapter.addAnimSetter(prop, function (fx) {
  430. fx.elem.attr(prop, ColorAxis.prototype.tweenColors(Color(fx.start), Color(fx.end), fx.pos));
  431. });
  432. });
  433. /**
  434. * Extend the chart getAxes method to also get the color axis
  435. */
  436. wrap(Chart.prototype, 'getAxes', function (proceed) {
  437. var options = this.options,
  438. colorAxisOptions = options.colorAxis;
  439. proceed.call(this);
  440. this.colorAxis = [];
  441. if (colorAxisOptions) {
  442. proceed = new ColorAxis(this, colorAxisOptions); // Fake assignment for jsLint
  443. }
  444. });
  445. /**
  446. * Wrap the legend getAllItems method to add the color axis. This also removes the
  447. * axis' own series to prevent them from showing up individually.
  448. */
  449. wrap(Legend.prototype, 'getAllItems', function (proceed) {
  450. var allItems = [],
  451. colorAxis = this.chart.colorAxis[0];
  452. if (colorAxis) {
  453. // Data classes
  454. if (colorAxis.options.dataClasses) {
  455. allItems = allItems.concat(colorAxis.getDataClassLegendSymbols());
  456. // Gradient legend
  457. } else {
  458. // Add this axis on top
  459. allItems.push(colorAxis);
  460. }
  461. // Don't add the color axis' series
  462. each(colorAxis.series, function (series) {
  463. series.options.showInLegend = false;
  464. });
  465. }
  466. return allItems.concat(proceed.call(this));
  467. });/**
  468. * Mixin for maps and heatmaps
  469. */
  470. var colorPointMixin = {
  471. /**
  472. * Set the visibility of a single point
  473. */
  474. setVisible: function (vis) {
  475. var point = this,
  476. method = vis ? 'show' : 'hide';
  477. // Show and hide associated elements
  478. each(['graphic', 'dataLabel'], function (key) {
  479. if (point[key]) {
  480. point[key][method]();
  481. }
  482. });
  483. }
  484. };
  485. var colorSeriesMixin = {
  486. pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
  487. stroke: 'borderColor',
  488. 'stroke-width': 'borderWidth',
  489. fill: 'color',
  490. dashstyle: 'dashStyle'
  491. },
  492. pointArrayMap: ['value'],
  493. axisTypes: ['xAxis', 'yAxis', 'colorAxis'],
  494. optionalAxis: 'colorAxis',
  495. trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
  496. getSymbol: noop,
  497. parallelArrays: ['x', 'y', 'value'],
  498. colorKey: 'value',
  499. /**
  500. * In choropleth maps, the color is a result of the value, so this needs translation too
  501. */
  502. translateColors: function () {
  503. var series = this,
  504. nullColor = this.options.nullColor,
  505. colorAxis = this.colorAxis,
  506. colorKey = this.colorKey;
  507. each(this.data, function (point) {
  508. var value = point[colorKey],
  509. color;
  510. color = point.options.color ||
  511. (value === null ? nullColor : (colorAxis && value !== undefined) ? colorAxis.toColor(value, point) : point.color || series.color);
  512. if (color) {
  513. point.color = color;
  514. }
  515. });
  516. }
  517. };
  518. /**
  519. * Extend the default options with map options
  520. */
  521. defaultOptions.plotOptions.heatmap = merge(defaultOptions.plotOptions.scatter, {
  522. animation: false,
  523. borderWidth: 0,
  524. nullColor: '#F8F8F8',
  525. dataLabels: {
  526. formatter: function () { // #2945
  527. return this.point.value;
  528. },
  529. inside: true,
  530. verticalAlign: 'middle',
  531. crop: false,
  532. overflow: false,
  533. padding: 0 // #3837
  534. },
  535. marker: null,
  536. pointRange: null, // dynamically set to colsize by default
  537. tooltip: {
  538. pointFormat: '{point.x}, {point.y}: {point.value}<br/>'
  539. },
  540. states: {
  541. normal: {
  542. animation: true
  543. },
  544. hover: {
  545. halo: false, // #3406, halo is not required on heatmaps
  546. brightness: 0.2
  547. }
  548. }
  549. });
  550. // The Heatmap series type
  551. seriesTypes.heatmap = extendClass(seriesTypes.scatter, merge(colorSeriesMixin, {
  552. type: 'heatmap',
  553. pointArrayMap: ['y', 'value'],
  554. hasPointSpecificOptions: true,
  555. pointClass: extendClass(Point, colorPointMixin),
  556. supportsDrilldown: true,
  557. getExtremesFromAll: true,
  558. directTouch: true,
  559. /**
  560. * Override the init method to add point ranges on both axes.
  561. */
  562. init: function () {
  563. var options;
  564. seriesTypes.scatter.prototype.init.apply(this, arguments);
  565. options = this.options;
  566. this.pointRange = options.pointRange = pick(options.pointRange, options.colsize || 1); // #3758, prevent resetting in setData
  567. this.yAxis.axisPointRange = options.rowsize || 1; // general point range
  568. },
  569. translate: function () {
  570. var series = this,
  571. options = series.options,
  572. xAxis = series.xAxis,
  573. yAxis = series.yAxis,
  574. between = function (x, a, b) {
  575. return Math.min(Math.max(a, x), b);
  576. };
  577. series.generatePoints();
  578. each(series.points, function (point) {
  579. var xPad = (options.colsize || 1) / 2,
  580. yPad = (options.rowsize || 1) / 2,
  581. x1 = between(Math.round(xAxis.len - xAxis.translate(point.x - xPad, 0, 1, 0, 1)), 0, xAxis.len),
  582. x2 = between(Math.round(xAxis.len - xAxis.translate(point.x + xPad, 0, 1, 0, 1)), 0, xAxis.len),
  583. y1 = between(Math.round(yAxis.translate(point.y - yPad, 0, 1, 0, 1)), 0, yAxis.len),
  584. y2 = between(Math.round(yAxis.translate(point.y + yPad, 0, 1, 0, 1)), 0, yAxis.len);
  585. // Set plotX and plotY for use in K-D-Tree and more
  586. point.plotX = point.clientX = (x1 + x2) / 2;
  587. point.plotY = (y1 + y2) / 2;
  588. point.shapeType = 'rect';
  589. point.shapeArgs = {
  590. x: Math.min(x1, x2),
  591. y: Math.min(y1, y2),
  592. width: Math.abs(x2 - x1),
  593. height: Math.abs(y2 - y1)
  594. };
  595. });
  596. series.translateColors();
  597. // Make sure colors are updated on colorAxis update (#2893)
  598. if (this.chart.hasRendered) {
  599. each(series.points, function (point) {
  600. point.shapeArgs.fill = point.options.color || point.color; // #3311
  601. });
  602. }
  603. },
  604. drawPoints: seriesTypes.column.prototype.drawPoints,
  605. animate: noop,
  606. getBox: noop,
  607. drawLegendSymbol: LegendSymbolMixin.drawRectangle,
  608. getExtremes: function () {
  609. // Get the extremes from the value data
  610. Series.prototype.getExtremes.call(this, this.valueData);
  611. this.valueMin = this.dataMin;
  612. this.valueMax = this.dataMax;
  613. // Get the extremes from the y data
  614. Series.prototype.getExtremes.call(this);
  615. }
  616. }));
  617. }(Highcharts));