highcharts-rails 3.0.10 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2124bcb4226c044154dbb93b494774f0cc4a8967
4
- data.tar.gz: 528b076176b459de6b5cb46d6037b9fde1032b33
3
+ metadata.gz: 34ea2717f56c979675fe905a6f18999c7a8d5e0c
4
+ data.tar.gz: ffc12189cbda0a9ec80f4fe7c6e7fe1615086e8d
5
5
  SHA512:
6
- metadata.gz: 24784acba17ad0c1c61bf346eedd31df6edabfb403dffb86193e15a894267e09c42b23da10ca91d75ac57a75228d6c770fe34f18d80ffebb915f06a01edba248
7
- data.tar.gz: a2686a34ac75e26c6f0a48738f1ca3ce875d6097b78e39b1b8bfdb88b6605f3c4bd1fe34e84c02d51142516026418b42ad7f7fdab4846c2ebecd2e36e5dafc95
6
+ metadata.gz: d80310a434ae483f3913e9d00a28b461a8a36818393978251a141397af804bf2db5f2dfca76c269a9f9ba5e6b0346ad885c6d8d6150a0b697b33e74dd98d5c41
7
+ data.tar.gz: a65952e25bfc4f2d877774f12a7511ca6590283d8c91b64a2fa578887b90f5893bb3a46a770b18f77b7470dad48c679223b18a1d138c30058ff57a4fd06743cd
checksums.yaml.gz.asc CHANGED
@@ -2,17 +2,17 @@
2
2
  Version: GnuPG/MacGPG2 v2.0.22 (Darwin)
3
3
  Comment: GPGTools - http://gpgtools.org
4
4
 
5
- iQIcBAABAgAGBQJTHr6JAAoJEH1ncb0Txu7XhY8QALRXHWCfkguOspgTJFw2twas
6
- opDiVwzEe9skmHL8Q6SxG2aL1s4BiPRfU1O3PUpRMjmkksR6vT50BFPI70lQ8piW
7
- WOZR8Z2oxlSG0OQTB1EUym9mYD7Qpwhh15W8TkyafbDlVdhrfHdffXlYeZKLMCJn
8
- NY/n3KcyZe1NhyU12vCYgNoVmz/kNbwhmYczFnsuELa6m4oCoucnzK3510bf6kAR
9
- y7Wjc+waTHAwTYHkF8oCfmrpGyvRdA6wIAsJHF15bLi0AwJTCiPN3c+1Fhbt83T0
10
- lrzPAsKoVq1Df6QbVIRL81CMKS8oZuJoe5WnXNhtE8hijqcexdSlwf/hxIchSrGB
11
- yO/+R6GWCCIUX8trV5KWgmRoySHG4MZk72lYztFbcZsXRlfKwR/GpfugahrkD/sC
12
- ChPcFRNQJgqrbQzVMLyVXSSH8PMMuxET3OCUVzuX925fb2SbPrin/hjGEgnOXug6
13
- zz03YdrNL+cW0SZqI7RfWYOADiAs32r981TOj9TZASka9pzPL0bsutZeZnNUv3xf
14
- HHyF1ijxzWjj7qUIuqQXtp04JKMdTo/MR2prANagHYgQNbjT/MP5hJZKcC0McekV
15
- 1fwJFSf4ffJj6tHz3/Kbn9B2/KUY8ozPTBRbSEgKa8LEk9mzzDaxUotgNF0e32d/
16
- iE1qP8t0KvgA5S27cc1u
17
- =C9CY
5
+ iQIcBAABAgAGBQJTVobYAAoJEH1ncb0Txu7X4NIP/jW/2XkfaZMGcCTRNTjw0cE0
6
+ iNpqs6LkmKgJ1whBFq3CochGBevML+4JTcdxvotB+EyIqYLhZgJNFXg3HEEopADZ
7
+ Wi9nf2E4V4HCwN4gRd/WUisRc5h8DMxNQFl6N2FImumpsyQ1Xy9DrOoOgfGEkCKZ
8
+ nPB2bhRieFgBPFe3IgvZXGtHaacavk3YjtIxZPofjAt7cn4tjCk0K3HSNGiZqsfS
9
+ Cq+H7tVJJPaNmUkeBs8S5K/s65+biaItbqGvQbmglToMtMMY72ngkVoTyegjNEqb
10
+ gObY8iutWj8rs2tpqaM/XF9618vclD4gVFLzvI6G5Gl3ZM4EOmNnLoXDIs1BTqhs
11
+ 9LiamP1kazyYWkA5MJfhihtO/Apijm6Iydrg+hyC7NDQ+POsT80q5rUGvqglsbIW
12
+ kUb7QwActx744jrxIoTmVcG+CefvuV0mO5vRsgKO6wti1r5O4PVBrAqYktQYp9sK
13
+ No5j+616QC6uyQP/TnIJdZnaLRn80Iv9eqVvZEviN8dEuFKIC+215zbu/L2a1Kk0
14
+ k02K3J/1WGsb6nC4inXw0pB2gH98FAPo1QT9lIg5rS05bTLsDNvCgOfSpeJ0IXwQ
15
+ fkru8TyOA4z5NWMIr5F1fHnpJy0q+ZYtcc/h+iYDyuiLOy5PlVDF8ZPWb0OsFK1d
16
+ NeG7+J7OCRidntAeE89M
17
+ =YkrH
18
18
  -----END PGP SIGNATURE-----
data.tar.gz.asc CHANGED
@@ -2,17 +2,17 @@
2
2
  Version: GnuPG/MacGPG2 v2.0.22 (Darwin)
3
3
  Comment: GPGTools - http://gpgtools.org
4
4
 
5
- iQIcBAABAgAGBQJTHr6JAAoJEH1ncb0Txu7Xw9kQAIIykvTwDhlvsouHOcyoz72t
6
- UMkwrqsLvpAWO+IMTwcatzImUCvpZciTGifA1S/66a3ySYcj2Bwu9C/vU7RL+oAw
7
- YnrKHBNbimsyxlkvqc3JwW6Wa42QlymMTI9akntycXktTqp/u7AlsBpyA5IPkLb4
8
- wC7Womt5gtZIvlUQWb300AwE6rmW6/evcL00IklvCWwVnBOlVIXI+qA0l9xwHklI
9
- mHPg8b5DAc6cksqcM36kR9+S+X0mYAIbYqY7ZIhzYbPuhJethIq75v01cs52+sIM
10
- 4cCWrkRZ+RcrfGWL5ry0NW82Lzgr6T5A63UDaL6Iz05iaAxRb9jEVSqN+ShHa0TL
11
- Jb5d3FKATz0mHvZqpmuOfxb9Iwx6XJQnyQW/FkeFo1l5LS0hE9n8KhrqoKeZCYz1
12
- BDHdn9ZbdQSBPEcq1+eqVAhrbc1gUp9YLCwwJsRW3Yn7EiMSZG0lTW599CDNtHWI
13
- KT3qcAfM8F3NgKaGXTH8cRfvctsvDUA1fVhsZVgosqGsIo82GETfdWbT+ke1MqND
14
- n7MZqw2XsTSZIgObiiuqizX3n5bS7Nr2Qlxx4307+g8eDf68leMiC40fdcx/oydh
15
- p0zIaSlIlq4u2ecp3R3DefzNia17slLCc43h9YiJYuWmrapW7KFwnJ+10bbmXF+x
16
- 18lb8uqO1+i1yGhP9gTP
17
- =oJQ5
5
+ iQIcBAABAgAGBQJTVobYAAoJEH1ncb0Txu7X49EP/AqBvgdlbe9Av1BtXRov6rly
6
+ 1STFk5806XANJUaoCML8z76/Jzc2Kxqhhy6ylRhI3G+NcI6fGTLdKjQPXo95KVvS
7
+ 6CHoR5Jjff8zTToiVn7Vmr8Nfj28sz7DBAIOtcfbwbCniisPfsTbzljbEX0ekh58
8
+ F7msBBJKNED05l9mmTzfiCUwKamx8lc/eKrqmx3/AOXB9ojqQ+uTeaMqTpGjif9u
9
+ A0xJ+CK3ORbZ5vJihhaVwHwE7H/ERPwptAFqSFcIdJy+3BbHzse+Xi1VMkbwk+AP
10
+ +ooJgF+WZtWUtJ4SkQ0/vLtxG+W1nr+Q4P3+TJYRaVXVQfkwXPTe/mnqmvDeamP0
11
+ MYBF9kJKWThcTxJaZCLJW+C3CTP0SFk+ohd6735y0TfMoh6Gt5xvmS4CqEEy5Z+q
12
+ /0EmmUwnq4A4skvgEnUbyPPZBAUjzSZSuuMQmgKgl56P/Cxi9+eMTU5bhFtC1OSI
13
+ zGejoMelgeB7GvKhdMNBl9bunt6bOJRDmRD0wTx5X0qvTmn1n7Z0JK3Uo5rWVoFP
14
+ tBmi5ylhntdgESNpaWPdzswMxzH0cGyoXtMlmXojhqq4mAADUNLcaIp4Wzi4Q5Mv
15
+ c30732TVOCq9H8Lhh5/XWrJ82oqu6wVtCcqoTGV6AEaO8X9wqVGFwtVofpTsEmwp
16
+ zu6UA9W5DyJxK3Z+Nv6l
17
+ =DWva
18
18
  -----END PGP SIGNATURE-----
data/CHANGELOG.markdown CHANGED
@@ -1,3 +1,8 @@
1
+ # 4.0.0 / 2014-04-22
2
+
3
+ * Updated Highcharts to 4.0.0
4
+ * See [the release announcement](http://www.highcharts.com/component/content/article/2-news/134-announcing-highcharts-4/)
5
+
1
6
  # 3.0.10 / 2014-03-11
2
7
 
3
8
  * Updated Highcharts to 3.0.10
data/Rakefile CHANGED
@@ -11,18 +11,17 @@ task :update, :version do |t, args|
11
11
  mappings = {
12
12
  "highcharts.src.js" => "highcharts.js",
13
13
  "highcharts-more.src.js" => "highcharts/highcharts-more.js",
14
- "mootools-adapter.src.js" => "highcharts/adapters/mootools-adapter.js",
15
- "prototype-adapter.src.js" => "highcharts/adapters/prototype-adapter.js",
14
+ "highcharts-3d.src.js" => "highcharts/highcharts-3d.js",
16
15
  "standalone-framework.src.js" => "highcharts/adapters/standalone-framework.js",
17
16
  "annotations.src.js" => "highcharts/modules/annotations.js",
18
17
  "canvas-tools.src.js" => "highcharts/modules/canvas-tools.js",
19
18
  "data.src.js" => "highcharts/modules/data.js",
20
- "no-data-to-display.src.js" => "highcharts/modules/no-data-to-display.js",
19
+ "drilldown.src.js" => "highcharts/modules/drilldown.js",
21
20
  "exporting.src.js" => "highcharts/modules/exporting.js",
22
21
  "funnel.src.js" => "highcharts/modules/funnel.js",
23
22
  "heatmap.src.js" => "highcharts/modules/heatmap.js",
24
- "map.src.js" => "highcharts/modules/map.js",
25
- "drilldown.src.js" => "highcharts/modules/drilldown.js",
23
+ "no-data-to-display.src.js" => "highcharts/modules/no-data-to-display.js",
24
+ "solid-gauge.src.js" => "highcharts/modules/solid-gauge.js",
26
25
  }
27
26
  dest = "app/assets/javascripts/"
28
27
  Dir.glob("tmp/#{version}/js/**/*.src.js").each do |file|
@@ -2,7 +2,7 @@
2
2
  // @compilation_level SIMPLE_OPTIMIZATIONS
3
3
 
4
4
  /**
5
- * @license Highcharts JS v3.0.10 (2014-03-10)
5
+ * @license Highcharts JS v4.0.0 (2014-04-22)
6
6
  *
7
7
  * (c) 2009-2014 Torstein Honsi
8
8
  *
@@ -54,8 +54,9 @@ var UNDEFINED,
54
54
  timeUnits,
55
55
  noop = function () {},
56
56
  charts = [],
57
+ chartCount = 0,
57
58
  PRODUCT = 'Highcharts',
58
- VERSION = '3.0.10',
59
+ VERSION = '4.0.0',
59
60
 
60
61
  // some constants for frequently used strings
61
62
  DIV = 'div',
@@ -276,15 +277,13 @@ function defined(obj) {
276
277
  */
277
278
  function attr(elem, prop, value) {
278
279
  var key,
279
- setAttribute = 'setAttribute',
280
280
  ret;
281
281
 
282
282
  // if the prop is a string
283
283
  if (isString(prop)) {
284
284
  // set the value
285
285
  if (defined(value)) {
286
-
287
- elem[setAttribute](prop, value);
286
+ elem.setAttribute(prop, value);
288
287
 
289
288
  // get the value
290
289
  } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
@@ -294,7 +293,7 @@ function attr(elem, prop, value) {
294
293
  // else if prop is defined, it is a hash of key/value pairs
295
294
  } else if (defined(prop) && isObject(prop)) {
296
295
  for (key in prop) {
297
- elem[setAttribute](key, prop[key]);
296
+ elem.setAttribute(key, prop[key]);
298
297
  }
299
298
  }
300
299
  return ret;
@@ -505,12 +504,14 @@ function formatSingle(format, val) {
505
504
  if (floatRegex.test(format)) { // float
506
505
  decimals = format.match(decRegex);
507
506
  decimals = decimals ? decimals[1] : -1;
508
- val = numberFormat(
509
- val,
510
- decimals,
511
- lang.decimalPoint,
512
- format.indexOf(',') > -1 ? lang.thousandsSep : ''
513
- );
507
+ if (val !== null) {
508
+ val = numberFormat(
509
+ val,
510
+ decimals,
511
+ lang.decimalPoint,
512
+ format.indexOf(',') > -1 ? lang.thousandsSep : ''
513
+ );
514
+ }
514
515
  } else {
515
516
  val = dateFormat(format, val);
516
517
  }
@@ -1024,27 +1025,30 @@ pathAnim = {
1024
1025
  ret,
1025
1026
  chart;
1026
1027
 
1027
- if (isString(args[0])) {
1028
- constr = args[0];
1029
- args = Array.prototype.slice.call(args, 1);
1030
- }
1031
- options = args[0];
1032
-
1033
- // Create the chart
1034
- if (options !== UNDEFINED) {
1035
- /*jslint unused:false*/
1036
- options.chart = options.chart || {};
1037
- options.chart.renderTo = this[0];
1038
- chart = new Highcharts[constr](options, args[1]);
1039
- ret = this;
1040
- /*jslint unused:true*/
1041
- }
1028
+ if (this[0]) {
1042
1029
 
1043
- // When called without parameters or with the return argument, get a predefined chart
1044
- if (options === UNDEFINED) {
1045
- ret = charts[attr(this[0], 'data-highcharts-chart')];
1046
- }
1030
+ if (isString(args[0])) {
1031
+ constr = args[0];
1032
+ args = Array.prototype.slice.call(args, 1);
1033
+ }
1034
+ options = args[0];
1035
+
1036
+ // Create the chart
1037
+ if (options !== UNDEFINED) {
1038
+ /*jslint unused:false*/
1039
+ options.chart = options.chart || {};
1040
+ options.chart.renderTo = this[0];
1041
+ chart = new Highcharts[constr](options, args[1]);
1042
+ ret = this;
1043
+ /*jslint unused:true*/
1044
+ }
1047
1045
 
1046
+ // When called without parameters or with the return argument, get a predefined chart
1047
+ if (options === UNDEFINED) {
1048
+ ret = charts[attr(this[0], 'data-highcharts-chart')];
1049
+ }
1050
+ }
1051
+
1048
1052
  return ret;
1049
1053
  };
1050
1054
 
@@ -1140,7 +1144,7 @@ pathAnim = {
1140
1144
  detachedType = 'detached' + type,
1141
1145
  defaultPrevented;
1142
1146
 
1143
- // Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts
1147
+ // Remove warnings in Chrome when accessing returnValue (#2790), layerX and layerY. Although Highcharts
1144
1148
  // never uses these properties, Chrome includes them in the default click event and
1145
1149
  // raises the warning when they are copied over in the extend statement below.
1146
1150
  //
@@ -1149,6 +1153,7 @@ pathAnim = {
1149
1153
  if (!isIE && eventArguments) {
1150
1154
  delete eventArguments.layerX;
1151
1155
  delete eventArguments.layerY;
1156
+ delete eventArguments.returnValue;
1152
1157
  }
1153
1158
 
1154
1159
  extend(event, eventArguments);
@@ -1285,16 +1290,15 @@ defaultLabelOptions = {
1285
1290
  return this.value;
1286
1291
  },*/
1287
1292
  style: {
1288
- color: '#666',
1293
+ color: '#606060',
1289
1294
  cursor: 'default',
1290
1295
  fontSize: '11px'
1291
1296
  }
1292
1297
  };
1293
1298
 
1294
1299
  defaultOptions = {
1295
- colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970',
1296
- '#f28f43', '#77a1e5', '#c42525', '#a6c96a'],
1297
- //colors: ['#8085e8', '#252530', '#90ee7e', '#8d4654', '#2b908f', '#76758e', '#f6a45c', '#7eb5ee', '#f45b5b', '#9ff0cf'],
1300
+ colors: ['#7cb5ec', '#434348', '#90ed7d', '#f7a35c',
1301
+ '#8085e9', '#f15c80', '#e4d354', '#8085e8', '#8d4653', '#91e8e1'], // docs
1298
1302
  symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
1299
1303
  lang: {
1300
1304
  loading: 'Loading...',
@@ -1311,8 +1315,8 @@ defaultOptions = {
1311
1315
  global: {
1312
1316
  useUTC: true,
1313
1317
  //timezoneOffset: 0,
1314
- canvasToolsURL: 'http://code.highcharts.com/3.0.10/modules/canvas-tools.js',
1315
- VMLRadialGradientURL: 'http://code.highcharts.com/3.0.10/gfx/vml-radial-gradient.png'
1318
+ canvasToolsURL: 'http://code.highcharts.com/4.0.0/modules/canvas-tools.js',
1319
+ VMLRadialGradientURL: 'http://code.highcharts.com/4.0.0/gfx/vml-radial-gradient.png'
1316
1320
  },
1317
1321
  chart: {
1318
1322
  //animation: true,
@@ -1327,7 +1331,7 @@ defaultOptions = {
1327
1331
  //marginLeft: null,
1328
1332
  borderColor: '#4572A7',
1329
1333
  //borderWidth: 0,
1330
- borderRadius: 5,
1334
+ borderRadius: 0,
1331
1335
  defaultSeriesType: 'line',
1332
1336
  ignoreHiddenSeries: true,
1333
1337
  //inverted: false,
@@ -1369,8 +1373,8 @@ defaultOptions = {
1369
1373
  // verticalAlign: 'top',
1370
1374
  // y: null,
1371
1375
  style: {
1372
- color: '#274b6d',//#3E576F',
1373
- fontSize: '16px'
1376
+ color: '#333333', // docs
1377
+ fontSize: '18px'
1374
1378
  }
1375
1379
 
1376
1380
  },
@@ -1382,7 +1386,7 @@ defaultOptions = {
1382
1386
  // verticalAlign: 'top',
1383
1387
  // y: null,
1384
1388
  style: {
1385
- color: '#4d759e'
1389
+ color: '#555555' // docs
1386
1390
  }
1387
1391
  },
1388
1392
 
@@ -1405,7 +1409,7 @@ defaultOptions = {
1405
1409
  //shadow: false,
1406
1410
  // stacking: null,
1407
1411
  marker: {
1408
- enabled: true,
1412
+ //enabled: true,
1409
1413
  //symbol: null,
1410
1414
  lineWidth: 0,
1411
1415
  radius: 4,
@@ -1428,6 +1432,7 @@ defaultOptions = {
1428
1432
  },
1429
1433
  dataLabels: merge(defaultLabelOptions, {
1430
1434
  align: 'center',
1435
+ //defer: true,
1431
1436
  enabled: false,
1432
1437
  formatter: function () {
1433
1438
  return this.y === null ? '' : numberFormat(this.y, -1);
@@ -1453,6 +1458,10 @@ defaultOptions = {
1453
1458
  marker: {
1454
1459
  // lineWidth: base + 1,
1455
1460
  // radius: base + 1
1461
+ },
1462
+ halo: {
1463
+ size: 10,
1464
+ opacity: 0.25
1456
1465
  }
1457
1466
  },
1458
1467
  select: {
@@ -1461,7 +1470,7 @@ defaultOptions = {
1461
1470
  },
1462
1471
  stickyTracking: true,
1463
1472
  //tooltip: {
1464
- //pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b>'
1473
+ //pointFormat: '<span style="color:{series.color}">\u25CF</span> {series.name}: <b>{point.y}</b>'
1465
1474
  //valueDecimals: null,
1466
1475
  //xDateFormat: '%A, %b %e, %Y',
1467
1476
  //valuePrefix: '',
@@ -1487,9 +1496,9 @@ defaultOptions = {
1487
1496
  labelFormatter: function () {
1488
1497
  return this.name;
1489
1498
  },
1490
- borderWidth: 1,
1499
+ //borderWidth: 0,
1491
1500
  borderColor: '#909090',
1492
- borderRadius: 5,
1501
+ borderRadius: 0, // docs
1493
1502
  navigation: {
1494
1503
  // animation: true,
1495
1504
  activeColor: '#274b6d',
@@ -1497,7 +1506,7 @@ defaultOptions = {
1497
1506
  inactiveColor: '#CCC'
1498
1507
  // style: {} // text styles
1499
1508
  },
1500
- // margin: 10,
1509
+ // margin: 20,
1501
1510
  // reversed: false,
1502
1511
  shadow: false,
1503
1512
  // backgroundColor: null,
@@ -1505,8 +1514,9 @@ defaultOptions = {
1505
1514
  padding: '5px'
1506
1515
  },*/
1507
1516
  itemStyle: {
1508
- color: '#274b6d',
1509
- fontSize: '12px'
1517
+ color: '#333333', // docs
1518
+ fontSize: '12px',
1519
+ fontWeight: 'bold' // docs
1510
1520
  },
1511
1521
  itemHoverStyle: {
1512
1522
  //cursor: 'pointer', removed as of #601
@@ -1521,6 +1531,7 @@ defaultOptions = {
1521
1531
  height: '13px'
1522
1532
  },
1523
1533
  // itemWidth: undefined,
1534
+ // symbolRadius: 0,
1524
1535
  // symbolWidth: 16,
1525
1536
  symbolPadding: 5,
1526
1537
  verticalAlign: 'bottom',
@@ -1555,7 +1566,7 @@ defaultOptions = {
1555
1566
  enabled: true,
1556
1567
  animation: hasSVG,
1557
1568
  //crosshairs: null,
1558
- backgroundColor: 'rgba(255, 255, 255, .85)',
1569
+ backgroundColor: 'rgba(249, 249, 249, .85)',
1559
1570
  borderWidth: 1,
1560
1571
  borderRadius: 3,
1561
1572
  dateTimeLabelFormats: {
@@ -1570,8 +1581,9 @@ defaultOptions = {
1570
1581
  },
1571
1582
  //formatter: defaultFormatter,
1572
1583
  headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
1573
- pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',
1584
+ pointFormat: '<span style="color:{series.color}">\u25CF</span> {series.name}: <b>{point.y}</b><br/>', // docs
1574
1585
  shadow: true,
1586
+ //shape: 'calout',
1575
1587
  //shared: false,
1576
1588
  snap: isTouchDevice ? 25 : 10,
1577
1589
  style: {
@@ -1815,13 +1827,6 @@ SVGElement.prototype = {
1815
1827
  createElement(nodeName) :
1816
1828
  doc.createElementNS(SVG_NS, nodeName);
1817
1829
  wrapper.renderer = renderer;
1818
- /**
1819
- * A collection of attribute setters. These methods, if defined, are called right before a certain
1820
- * attribute is set on an element wrapper. Returning false prevents the default attribute
1821
- * setter to run. Returning a value causes the default setter to set that value. Used in
1822
- * Renderer.label.
1823
- */
1824
- wrapper.attrSetters = {};
1825
1830
  },
1826
1831
  /**
1827
1832
  * Default base for animation
@@ -1849,241 +1854,171 @@ SVGElement.prototype = {
1849
1854
  }
1850
1855
  }
1851
1856
  },
1857
+
1858
+ /**
1859
+ * Build an SVG gradient out of a common JavaScript configuration object
1860
+ */
1861
+ colorGradient: function (color, prop, elem) {
1862
+ var renderer = this.renderer,
1863
+ colorObject,
1864
+ gradName,
1865
+ gradAttr,
1866
+ gradients,
1867
+ gradientObject,
1868
+ stops,
1869
+ stopColor,
1870
+ stopOpacity,
1871
+ radialReference,
1872
+ n,
1873
+ id,
1874
+ key = [];
1875
+
1876
+ // Apply linear or radial gradients
1877
+ if (color.linearGradient) {
1878
+ gradName = 'linearGradient';
1879
+ } else if (color.radialGradient) {
1880
+ gradName = 'radialGradient';
1881
+ }
1882
+
1883
+ if (gradName) {
1884
+ gradAttr = color[gradName];
1885
+ gradients = renderer.gradients;
1886
+ stops = color.stops;
1887
+ radialReference = elem.radialReference;
1888
+
1889
+ // Keep < 2.2 kompatibility
1890
+ if (isArray(gradAttr)) {
1891
+ color[gradName] = gradAttr = {
1892
+ x1: gradAttr[0],
1893
+ y1: gradAttr[1],
1894
+ x2: gradAttr[2],
1895
+ y2: gradAttr[3],
1896
+ gradientUnits: 'userSpaceOnUse'
1897
+ };
1898
+ }
1899
+
1900
+ // Correct the radial gradient for the radial reference system
1901
+ if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {
1902
+ gradAttr = merge(gradAttr, {
1903
+ cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],
1904
+ cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],
1905
+ r: gradAttr.r * radialReference[2],
1906
+ gradientUnits: 'userSpaceOnUse'
1907
+ });
1908
+ }
1909
+
1910
+ // Build the unique key to detect whether we need to create a new element (#1282)
1911
+ for (n in gradAttr) {
1912
+ if (n !== 'id') {
1913
+ key.push(n, gradAttr[n]);
1914
+ }
1915
+ }
1916
+ for (n in stops) {
1917
+ key.push(stops[n]);
1918
+ }
1919
+ key = key.join(',');
1920
+
1921
+ // Check if a gradient object with the same config object is created within this renderer
1922
+ if (gradients[key]) {
1923
+ id = gradients[key].attr('id');
1924
+
1925
+ } else {
1926
+
1927
+ // Set the id and create the element
1928
+ gradAttr.id = id = PREFIX + idCounter++;
1929
+ gradients[key] = gradientObject = renderer.createElement(gradName)
1930
+ .attr(gradAttr)
1931
+ .add(renderer.defs);
1932
+
1933
+
1934
+ // The gradient needs to keep a list of stops to be able to destroy them
1935
+ gradientObject.stops = [];
1936
+ each(stops, function (stop) {
1937
+ var stopObject;
1938
+ if (stop[1].indexOf('rgba') === 0) {
1939
+ colorObject = Color(stop[1]);
1940
+ stopColor = colorObject.get('rgb');
1941
+ stopOpacity = colorObject.get('a');
1942
+ } else {
1943
+ stopColor = stop[1];
1944
+ stopOpacity = 1;
1945
+ }
1946
+ stopObject = renderer.createElement('stop').attr({
1947
+ offset: stop[0],
1948
+ 'stop-color': stopColor,
1949
+ 'stop-opacity': stopOpacity
1950
+ }).add(gradientObject);
1951
+
1952
+ // Add the stop element to the gradient
1953
+ gradientObject.stops.push(stopObject);
1954
+ });
1955
+ }
1956
+
1957
+ // Set the reference to the gradient object
1958
+ elem.setAttribute(prop, 'url(' + renderer.url + '#' + id + ')');
1959
+ }
1960
+ },
1961
+
1852
1962
  /**
1853
1963
  * Set or get a given attribute
1854
1964
  * @param {Object|String} hash
1855
1965
  * @param {Mixed|Undefined} val
1856
1966
  */
1857
1967
  attr: function (hash, val) {
1858
- var wrapper = this,
1859
- key,
1968
+ var key,
1860
1969
  value,
1861
- result,
1862
- i,
1863
- child,
1864
- element = wrapper.element,
1865
- nodeName = element.nodeName.toLowerCase(), // Android2 requires lower for "text"
1866
- renderer = wrapper.renderer,
1867
- skipAttr,
1868
- titleNode,
1869
- attrSetters = wrapper.attrSetters,
1870
- shadows = wrapper.shadows,
1970
+ element = this.element,
1871
1971
  hasSetSymbolSize,
1872
- doTransform,
1873
- ret = wrapper;
1972
+ ret = this,
1973
+ skipAttr;
1874
1974
 
1875
1975
  // single key-value pair
1876
- if (isString(hash) && defined(val)) {
1976
+ if (typeof hash === 'string' && val !== UNDEFINED) {
1877
1977
  key = hash;
1878
1978
  hash = {};
1879
1979
  hash[key] = val;
1880
1980
  }
1881
1981
 
1882
1982
  // used as a getter: first argument is a string, second is undefined
1883
- if (isString(hash)) {
1884
- key = hash;
1885
- if (nodeName === 'circle') {
1886
- key = { x: 'cx', y: 'cy' }[key] || key;
1887
- } else if (key === 'strokeWidth') {
1888
- key = 'stroke-width';
1889
- }
1890
- ret = attr(element, key) || wrapper[key] || 0;
1891
- if (key !== 'd' && key !== 'visibility' && key !== 'fill') { // 'd' is string in animation step
1892
- ret = parseFloat(ret);
1893
- }
1894
-
1983
+ if (typeof hash === 'string') {
1984
+ ret = (this[hash + 'Getter'] || this._defaultGetter).call(this, hash, element);
1985
+
1895
1986
  // setter
1896
1987
  } else {
1897
1988
 
1898
1989
  for (key in hash) {
1899
- skipAttr = false; // reset
1900
1990
  value = hash[key];
1991
+ skipAttr = false;
1901
1992
 
1902
- // check for a specific attribute setter
1903
- result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);
1904
-
1905
- if (result !== false) {
1906
- if (result !== UNDEFINED) {
1907
- value = result; // the attribute setter has returned a new value to set
1908
- }
1909
-
1910
-
1911
- // paths
1912
- if (key === 'd') {
1913
- if (value && value.join) { // join path
1914
- value = value.join(' ');
1915
- }
1916
- if (/(NaN| {2}|^$)/.test(value)) {
1917
- value = 'M 0 0';
1918
- }
1919
- //wrapper.d = value; // shortcut for animations
1920
-
1921
- // update child tspans x values
1922
- } else if (key === 'x' && nodeName === 'text') {
1923
- for (i = 0; i < element.childNodes.length; i++) {
1924
- child = element.childNodes[i];
1925
- // if the x values are equal, the tspan represents a linebreak
1926
- if (attr(child, 'x') === attr(element, 'x')) {
1927
- //child.setAttribute('x', value);
1928
- attr(child, 'x', value);
1929
- }
1930
- }
1931
-
1932
- } else if (wrapper.rotation && (key === 'x' || key === 'y')) {
1933
- doTransform = true;
1934
-
1935
- // apply gradients
1936
- } else if (key === 'fill') {
1937
- value = renderer.color(value, element, key);
1938
-
1939
- // circle x and y
1940
- } else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
1941
- key = { x: 'cx', y: 'cy' }[key] || key;
1942
-
1943
- // rectangle border radius
1944
- } else if (nodeName === 'rect' && key === 'r') {
1945
- attr(element, {
1946
- rx: value,
1947
- ry: value
1948
- });
1949
- skipAttr = true;
1950
-
1951
- // translation and text rotation
1952
- } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' ||
1953
- key === 'verticalAlign' || key === 'scaleX' || key === 'scaleY') {
1954
- doTransform = true;
1955
- skipAttr = true;
1956
-
1957
- // apply opacity as subnode (required by legacy WebKit and Batik)
1958
- } else if (key === 'stroke') {
1959
- value = renderer.color(value, element, key);
1960
-
1961
- // emulate VML's dashstyle implementation
1962
- } else if (key === 'dashstyle') {
1963
- key = 'stroke-dasharray';
1964
- value = value && value.toLowerCase();
1965
- if (value === 'solid') {
1966
- value = NONE;
1967
- } else if (value) {
1968
- value = value
1969
- .replace('shortdashdotdot', '3,1,1,1,1,1,')
1970
- .replace('shortdashdot', '3,1,1,1')
1971
- .replace('shortdot', '1,1,')
1972
- .replace('shortdash', '3,1,')
1973
- .replace('longdash', '8,3,')
1974
- .replace(/dot/g, '1,3,')
1975
- .replace('dash', '4,3,')
1976
- .replace(/,$/, '')
1977
- .split(','); // ending comma
1978
-
1979
- i = value.length;
1980
- while (i--) {
1981
- value[i] = pInt(value[i]) * pick(hash['stroke-width'], wrapper['stroke-width']);
1982
- }
1983
- value = value.join(',');
1984
- }
1985
-
1986
- // IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
1987
- // is unable to cast them. Test again with final IE9.
1988
- } else if (key === 'width') {
1989
- value = pInt(value);
1990
-
1991
- // Text alignment
1992
- } else if (key === 'align') {
1993
- key = 'text-anchor';
1994
- value = { left: 'start', center: 'middle', right: 'end' }[value];
1995
-
1996
- // Title requires a subnode, #431
1997
- } else if (key === 'title') {
1998
- titleNode = element.getElementsByTagName('title')[0];
1999
- if (!titleNode) {
2000
- titleNode = doc.createElementNS(SVG_NS, 'title');
2001
- element.appendChild(titleNode);
2002
- }
2003
- titleNode.textContent = value;
2004
- }
2005
-
2006
- // jQuery animate changes case
2007
- if (key === 'strokeWidth') {
2008
- key = 'stroke-width';
2009
- }
2010
-
2011
- // In Chrome/Win < 6 as well as Batik, the stroke attribute can't be set when the stroke-
2012
- // width is 0. #1369
2013
- if (key === 'stroke-width' || key === 'stroke') {
2014
- wrapper[key] = value;
2015
- // Only apply the stroke attribute if the stroke width is defined and larger than 0
2016
- if (wrapper.stroke && wrapper['stroke-width']) {
2017
- attr(element, 'stroke', wrapper.stroke);
2018
- attr(element, 'stroke-width', wrapper['stroke-width']);
2019
- wrapper.hasStroke = true;
2020
- } else if (key === 'stroke-width' && value === 0 && wrapper.hasStroke) {
2021
- element.removeAttribute('stroke');
2022
- wrapper.hasStroke = false;
2023
- }
2024
- skipAttr = true;
2025
- }
2026
-
2027
- // symbols
2028
- if (wrapper.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) {
2029
-
2030
-
2031
- if (!hasSetSymbolSize) {
2032
- wrapper.symbolAttr(hash);
2033
- hasSetSymbolSize = true;
2034
- }
2035
- skipAttr = true;
2036
- }
2037
-
2038
- // let the shadow follow the main element
2039
- if (shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) {
2040
- i = shadows.length;
2041
- while (i--) {
2042
- attr(
2043
- shadows[i],
2044
- key,
2045
- key === 'height' ?
2046
- mathMax(value - (shadows[i].cutHeight || 0), 0) :
2047
- value
2048
- );
2049
- }
2050
- }
2051
-
2052
- // validate heights
2053
- if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
2054
- value = 0;
2055
- }
2056
-
2057
- // Record for animation and quick access without polling the DOM
2058
- wrapper[key] = value;
2059
1993
 
2060
1994
 
2061
- if (key === 'text') {
2062
- if (value !== wrapper.textStr) {
2063
-
2064
- // Delete bBox memo when the text changes
2065
- delete wrapper.bBox;
2066
-
2067
- wrapper.textStr = value;
2068
- if (wrapper.added) {
2069
- renderer.buildText(wrapper);
2070
- }
2071
- }
2072
- } else if (!skipAttr) {
2073
- //attr(element, key, value);
2074
- if (value !== undefined) {
2075
- element.setAttribute(key, value);
2076
- }
1995
+ if (this.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) {
1996
+ if (!hasSetSymbolSize) {
1997
+ this.symbolAttr(hash);
1998
+ hasSetSymbolSize = true;
2077
1999
  }
2000
+ skipAttr = true;
2001
+ }
2078
2002
 
2003
+ if (this.rotation && (key === 'x' || key === 'y')) {
2004
+ this.doTransform = true;
2005
+ }
2006
+
2007
+ if (!skipAttr) {
2008
+ (this[key + 'Setter'] || this._defaultSetter).call(this, value, key, element);
2079
2009
  }
2080
2010
 
2011
+ // Let the shadow follow the main element
2012
+ if (this.shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) {
2013
+ this.updateShadows(key, value);
2014
+ }
2081
2015
  }
2082
2016
 
2083
2017
  // Update transform. Do this outside the loop to prevent redundant updating for batch setting
2084
2018
  // of attributes.
2085
- if (doTransform) {
2086
- wrapper.updateTransform();
2019
+ if (this.doTransform) {
2020
+ this.updateTransform();
2021
+ this.doTransform = false;
2087
2022
  }
2088
2023
 
2089
2024
  }
@@ -2091,6 +2026,18 @@ SVGElement.prototype = {
2091
2026
  return ret;
2092
2027
  },
2093
2028
 
2029
+ updateShadows: function (key, value) {
2030
+ var shadows = this.shadows,
2031
+ i = shadows.length;
2032
+ while (i--) {
2033
+ shadows[i].setAttribute(
2034
+ key,
2035
+ key === 'height' ?
2036
+ mathMax(value - (shadows[i].cutHeight || 0), 0) :
2037
+ key === 'd' ? this.d : value
2038
+ );
2039
+ }
2040
+ },
2094
2041
 
2095
2042
  /**
2096
2043
  * Add a class name to an element
@@ -2323,6 +2270,7 @@ SVGElement.prototype = {
2323
2270
  scaleY = wrapper.scaleY,
2324
2271
  inverted = wrapper.inverted,
2325
2272
  rotation = wrapper.rotation,
2273
+ element = wrapper.element,
2326
2274
  transform;
2327
2275
 
2328
2276
  // flipping affects translate as adjustment for flipping around the group's axis
@@ -2339,7 +2287,7 @@ SVGElement.prototype = {
2339
2287
  if (inverted) {
2340
2288
  transform.push('rotate(90) scale(-1,1)');
2341
2289
  } else if (rotation) { // text rotation
2342
- transform.push('rotate(' + rotation + ' ' + (wrapper.x || 0) + ' ' + (wrapper.y || 0) + ')');
2290
+ transform.push('rotate(' + rotation + ' ' + (element.getAttribute('x') || 0) + ' ' + (element.getAttribute('y') || 0) + ')');
2343
2291
  }
2344
2292
 
2345
2293
  // apply scale
@@ -2348,7 +2296,7 @@ SVGElement.prototype = {
2348
2296
  }
2349
2297
 
2350
2298
  if (transform.length) {
2351
- attr(wrapper.element, 'transform', transform.join(' '));
2299
+ element.setAttribute('transform', transform.join(' '));
2352
2300
  }
2353
2301
  },
2354
2302
  /**
@@ -2447,14 +2395,21 @@ SVGElement.prototype = {
2447
2395
  styles = wrapper.styles,
2448
2396
  rad = rotation * deg2rad,
2449
2397
  textStr = wrapper.textStr,
2450
- numKey;
2398
+ cacheKey;
2451
2399
 
2452
2400
  // Since numbers are monospaced, and numerical labels appear a lot in a chart,
2453
2401
  // we assume that a label of n characters has the same bounding box as others
2454
2402
  // of the same length.
2455
2403
  if (textStr === '' || numRegex.test(textStr)) {
2456
- numKey = textStr.toString().length + (styles ? ('|' + styles.fontSize + '|' + styles.fontFamily) : '');
2457
- bBox = renderer.cache[numKey];
2404
+ cacheKey = 'num.' + textStr.toString().length + (styles ? ('|' + styles.fontSize + '|' + styles.fontFamily) : '');
2405
+
2406
+ } //else { // This code block made demo/waterfall fail, related to buildText
2407
+ // Caching all strings reduces rendering time by 4-5%.
2408
+ // TODO: Check how this affects places where bBox is found on the element
2409
+ //cacheKey = textStr + (styles ? ('|' + styles.fontSize + '|' + styles.fontFamily) : '');
2410
+ //}
2411
+ if (cacheKey) {
2412
+ bBox = renderer.cache[cacheKey];
2458
2413
  }
2459
2414
 
2460
2415
  // No cache found
@@ -2509,8 +2464,8 @@ SVGElement.prototype = {
2509
2464
 
2510
2465
  // Cache it
2511
2466
  wrapper.bBox = bBox;
2512
- if (numKey) {
2513
- renderer.cache[numKey] = bBox;
2467
+ if (cacheKey) {
2468
+ renderer.cache[cacheKey] = bBox;
2514
2469
  }
2515
2470
  }
2516
2471
  return bBox;
@@ -2520,7 +2475,13 @@ SVGElement.prototype = {
2520
2475
  * Show the element
2521
2476
  */
2522
2477
  show: function (inherit) {
2523
- return this.attr({ visibility: inherit ? 'inherit' : VISIBLE });
2478
+ // IE9-11 doesn't handle visibilty:inherit well, so we remove the attribute instead (#2881)
2479
+ if (inherit && this.element.namespaceURI === SVG_NS) {
2480
+ this.element.removeAttribute('visibility');
2481
+ return this;
2482
+ } else {
2483
+ return this.attr({ visibility: inherit ? 'inherit' : VISIBLE });
2484
+ }
2524
2485
  },
2525
2486
 
2526
2487
  /**
@@ -2735,9 +2696,141 @@ SVGElement.prototype = {
2735
2696
  }
2736
2697
  return this;
2737
2698
 
2699
+ },
2700
+
2701
+ xGetter: function (key) {
2702
+ if (this.element.nodeName === 'circle') {
2703
+ key = { x: 'cx', y: 'cy' }[key] || key;
2704
+ }
2705
+ return this._defaultGetter(key);
2706
+ },
2707
+
2708
+ /**
2709
+ * Get the current value of an attribute or pseudo attribute, used mainly
2710
+ * for animation.
2711
+ */
2712
+ _defaultGetter: function (key) {
2713
+ var ret = pick(this[key], this.element ? this.element.getAttribute(key) : null, 0);
2714
+
2715
+ if (/^[0-9\.]+$/.test(ret)) { // is numerical
2716
+ ret = parseFloat(ret);
2717
+ }
2718
+ return ret;
2719
+ },
2720
+
2721
+
2722
+ dSetter: function (value, key, element) {
2723
+ if (value && value.join) { // join path
2724
+ value = value.join(' ');
2725
+ }
2726
+ if (/(NaN| {2}|^$)/.test(value)) {
2727
+ value = 'M 0 0';
2728
+ }
2729
+ element.setAttribute(key, value);
2730
+
2731
+ this[key] = value;
2732
+ },
2733
+ dashstyleSetter: function (value) {
2734
+ var i;
2735
+ value = value && value.toLowerCase();
2736
+ if (value) {
2737
+ value = value
2738
+ .replace('shortdashdotdot', '3,1,1,1,1,1,')
2739
+ .replace('shortdashdot', '3,1,1,1')
2740
+ .replace('shortdot', '1,1,')
2741
+ .replace('shortdash', '3,1,')
2742
+ .replace('longdash', '8,3,')
2743
+ .replace(/dot/g, '1,3,')
2744
+ .replace('dash', '4,3,')
2745
+ .replace(/,$/, '')
2746
+ .split(','); // ending comma
2747
+
2748
+ i = value.length;
2749
+ while (i--) {
2750
+ value[i] = pInt(value[i]) * this.element.getAttribute('stroke-width');
2751
+ }
2752
+ value = value.join(',');
2753
+ this.element.setAttribute('stroke-dasharray', value);
2754
+ }
2755
+ },
2756
+ alignSetter: function (value) {
2757
+ this.element.setAttribute('text-anchor', { left: 'start', center: 'middle', right: 'end' }[value]);
2758
+ },
2759
+ opacitySetter: function (value, key, element) {
2760
+ this[key] = value;
2761
+ element.setAttribute(key, value);
2762
+ },
2763
+ // In Chrome/Win < 6 as well as Batik and PhantomJS as of 1.9.7, the stroke attribute can't be set when the stroke-
2764
+ // width is 0. #1369
2765
+ 'stroke-widthSetter': function (value, key, element) {
2766
+ if (value === 0) {
2767
+ value = 0.00001;
2768
+ }
2769
+ this.strokeWidth = value; // read in symbol paths like 'callout'
2770
+ element.setAttribute(key, value);
2771
+ },
2772
+ titleSetter: function (value) {
2773
+ var titleNode = this.element.getElementsByTagName('title')[0];
2774
+ if (!titleNode) {
2775
+ titleNode = doc.createElementNS(SVG_NS, 'title');
2776
+ this.element.appendChild(titleNode);
2777
+ }
2778
+ titleNode.textContent = value;
2779
+ },
2780
+ textSetter: function (value) {
2781
+ if (value !== this.textStr) {
2782
+ // Delete bBox memo when the text changes
2783
+ delete this.bBox;
2784
+
2785
+ this.textStr = value;
2786
+ if (this.added) {
2787
+ this.renderer.buildText(this);
2788
+ }
2789
+ }
2790
+ },
2791
+ fillSetter: function (value, key, element) {
2792
+
2793
+ if (typeof value === 'string') {
2794
+ element.setAttribute(key, value);
2795
+ } else if (value) {
2796
+ this.colorGradient(value, key, element);
2797
+ }
2798
+ },
2799
+ zIndexSetter: function (value, key, element) {
2800
+ element.setAttribute(key, value);
2801
+ this[key] = value;
2802
+ },
2803
+ _defaultSetter: function (value, key, element) {
2804
+ element.setAttribute(key, value);
2738
2805
  }
2739
2806
  };
2740
2807
 
2808
+ // Some shared setters and getters
2809
+ SVGElement.prototype.yGetter = SVGElement.prototype.xGetter;
2810
+ SVGElement.prototype.translateXSetter = SVGElement.prototype.translateYSetter =
2811
+ SVGElement.prototype.rotationSetter = SVGElement.prototype.verticalAlignSetter =
2812
+ SVGElement.prototype.scaleXSetter = SVGElement.prototype.scaleYSetter = function (value, key) {
2813
+ this[key] = value;
2814
+ this.doTransform = true;
2815
+ };
2816
+
2817
+
2818
+
2819
+ // In Chrome/Win < 6 as well as Batik, the stroke attribute can't be set when the stroke-
2820
+ // width is 0. #1369
2821
+ /*SVGElement.prototype['stroke-widthSetter'] = SVGElement.prototype.strokeSetter = function (value, key) {
2822
+ this[key] = value;
2823
+ // Only apply the stroke attribute if the stroke width is defined and larger than 0
2824
+ if (this.stroke && this['stroke-width']) {
2825
+ this.element.setAttribute('stroke', this.stroke);
2826
+ this.element.setAttribute('stroke-width', this['stroke-width']);
2827
+ this.hasStroke = true;
2828
+ } else if (key === 'stroke-width' && value === 0 && this.hasStroke) {
2829
+ this.element.removeAttribute('stroke');
2830
+ this.hasStroke = false;
2831
+ }
2832
+ };*/
2833
+
2741
2834
 
2742
2835
  /**
2743
2836
  * The default SVG renderer
@@ -2830,7 +2923,7 @@ SVGRenderer.prototype = {
2830
2923
 
2831
2924
  getStyle: function (style) {
2832
2925
  return (this.style = extend({
2833
- fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
2926
+ fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif', // default font
2834
2927
  fontSize: '12px'
2835
2928
  }, style));
2836
2929
  },
@@ -2898,15 +2991,12 @@ SVGRenderer.prototype = {
2898
2991
  var textNode = wrapper.element,
2899
2992
  renderer = this,
2900
2993
  forExport = renderer.forExport,
2901
- lines = pick(wrapper.textStr, '').toString()
2902
- .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
2903
- .replace(/<(i|em)>/g, '<span style="font-style:italic">')
2904
- .replace(/<a/g, '<span')
2905
- .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
2906
- .split(/<br.*?>/g),
2994
+ textStr = pick(wrapper.textStr, '').toString(),
2995
+ hasMarkup = textStr.indexOf('<') !== -1,
2996
+ lines,
2907
2997
  childNodes = textNode.childNodes,
2908
- styleRegex = /<.*style="([^"]+)".*>/,
2909
- hrefRegex = /<.*href="(http[^"]+)".*>/,
2998
+ styleRegex,
2999
+ hrefRegex,
2910
3000
  parentX = attr(textNode, 'x'),
2911
3001
  textStyles = wrapper.styles,
2912
3002
  width = wrapper.textWidth,
@@ -2918,7 +3008,7 @@ SVGRenderer.prototype = {
2918
3008
  renderer.fontMetrics(
2919
3009
  /(px|em)$/.test(tspan && tspan.style.fontSize) ?
2920
3010
  tspan.style.fontSize :
2921
- (textStyles.fontSize || 11)
3011
+ ((textStyles && textStyles.fontSize) || renderer.style.fontSize || 12)
2922
3012
  ).h;
2923
3013
  };
2924
3014
 
@@ -2927,142 +3017,170 @@ SVGRenderer.prototype = {
2927
3017
  textNode.removeChild(childNodes[i]);
2928
3018
  }
2929
3019
 
2930
- if (width && !wrapper.added) {
2931
- this.box.appendChild(textNode); // attach it to the DOM to read offset width
2932
- }
3020
+ // Skip tspans, add text directly to text node
3021
+ if (!hasMarkup && textStr.indexOf(' ') === -1) {
3022
+ textNode.appendChild(doc.createTextNode(textStr));
3023
+ return;
2933
3024
 
2934
- // remove empty line at end
2935
- if (lines[lines.length - 1] === '') {
2936
- lines.pop();
2937
- }
3025
+ // Complex strings, add more logic
3026
+ } else {
2938
3027
 
2939
- // build the lines
2940
- each(lines, function (line, lineNo) {
2941
- var spans, spanNo = 0;
3028
+ styleRegex = /<.*style="([^"]+)".*>/;
3029
+ hrefRegex = /<.*href="(http[^"]+)".*>/;
2942
3030
 
2943
- line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
2944
- spans = line.split('|||');
3031
+ if (width && !wrapper.added) {
3032
+ this.box.appendChild(textNode); // attach it to the DOM to read offset width
3033
+ }
2945
3034
 
2946
- each(spans, function (span) {
2947
- if (span !== '' || spans.length === 1) {
2948
- var attributes = {},
2949
- tspan = doc.createElementNS(SVG_NS, 'tspan'),
2950
- spanStyle; // #390
2951
- if (styleRegex.test(span)) {
2952
- spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2');
2953
- attr(tspan, 'style', spanStyle);
2954
- }
2955
- if (hrefRegex.test(span) && !forExport) { // Not for export - #1529
2956
- attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
2957
- css(tspan, { cursor: 'pointer' });
2958
- }
3035
+ if (hasMarkup) {
3036
+ lines = textStr
3037
+ .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
3038
+ .replace(/<(i|em)>/g, '<span style="font-style:italic">')
3039
+ .replace(/<a/g, '<span')
3040
+ .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
3041
+ .split(/<br.*?>/g);
2959
3042
 
2960
- span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
2961
- .replace(/&lt;/g, '<')
2962
- .replace(/&gt;/g, '>');
3043
+ } else {
3044
+ lines = [textStr];
3045
+ }
2963
3046
 
2964
- // Nested tags aren't supported, and cause crash in Safari (#1596)
2965
- if (span !== ' ') {
2966
3047
 
2967
- // add the text node
2968
- tspan.appendChild(doc.createTextNode(span));
3048
+ // remove empty line at end
3049
+ if (lines[lines.length - 1] === '') {
3050
+ lines.pop();
3051
+ }
2969
3052
 
2970
- if (!spanNo) { // first span in a line, align it to the left
2971
- attributes.x = parentX;
2972
- } else {
2973
- attributes.dx = 0; // #16
3053
+
3054
+ // build the lines
3055
+ each(lines, function (line, lineNo) {
3056
+ var spans, spanNo = 0;
3057
+
3058
+ line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
3059
+ spans = line.split('|||');
3060
+
3061
+ each(spans, function (span) {
3062
+ if (span !== '' || spans.length === 1) {
3063
+ var attributes = {},
3064
+ tspan = doc.createElementNS(SVG_NS, 'tspan'),
3065
+ spanStyle; // #390
3066
+ if (styleRegex.test(span)) {
3067
+ spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2');
3068
+ attr(tspan, 'style', spanStyle);
3069
+ }
3070
+ if (hrefRegex.test(span) && !forExport) { // Not for export - #1529
3071
+ attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
3072
+ css(tspan, { cursor: 'pointer' });
2974
3073
  }
2975
3074
 
2976
- // add attributes
2977
- attr(tspan, attributes);
3075
+ span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
3076
+ .replace(/&lt;/g, '<')
3077
+ .replace(/&gt;/g, '>');
2978
3078
 
2979
- // first span on subsequent line, add the line height
2980
- if (!spanNo && lineNo) {
3079
+ // Nested tags aren't supported, and cause crash in Safari (#1596)
3080
+ if (span !== ' ') {
2981
3081
 
2982
- // allow getting the right offset height in exporting in IE
2983
- if (!hasSVG && forExport) {
2984
- css(tspan, { display: 'block' });
3082
+ // add the text node
3083
+ tspan.appendChild(doc.createTextNode(span));
3084
+
3085
+ if (!spanNo) { // first span in a line, align it to the left
3086
+ if (lineNo && parentX !== null) {
3087
+ attributes.x = parentX;
3088
+ }
3089
+ } else {
3090
+ attributes.dx = 0; // #16
2985
3091
  }
2986
3092
 
2987
- // Set the line height based on the font size of either
2988
- // the text element or the tspan element
2989
- attr(
2990
- tspan,
2991
- 'dy',
2992
- getLineHeight(tspan),
2993
- // Safari 6.0.2 - too optimized for its own good (#1539)
2994
- // TODO: revisit this with future versions of Safari
2995
- isWebKit && tspan.offsetHeight
2996
- );
2997
- }
3093
+ // add attributes
3094
+ attr(tspan, attributes);
2998
3095
 
2999
- // Append it
3000
- textNode.appendChild(tspan);
3001
-
3002
- spanNo++;
3003
-
3004
- // check width and apply soft breaks
3005
- if (width) {
3006
- var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
3007
- hasWhiteSpace = words.length > 1 && textStyles.whiteSpace !== 'nowrap',
3008
- tooLong,
3009
- actualWidth,
3010
- clipHeight = wrapper._clipHeight,
3011
- rest = [],
3012
- dy = getLineHeight(),
3013
- softLineNo = 1,
3014
- bBox;
3015
-
3016
- while (hasWhiteSpace && (words.length || rest.length)) {
3017
- delete wrapper.bBox; // delete cache
3018
- bBox = wrapper.getBBox();
3019
- actualWidth = bBox.width;
3096
+ // first span on subsequent line, add the line height
3097
+ if (!spanNo && lineNo) {
3020
3098
 
3021
- // Old IE cannot measure the actualWidth for SVG elements (#2314)
3022
- if (!hasSVG && renderer.forExport) {
3023
- actualWidth = renderer.measureSpanWidth(tspan.firstChild.data, wrapper.styles);
3099
+ // allow getting the right offset height in exporting in IE
3100
+ if (!hasSVG && forExport) {
3101
+ css(tspan, { display: 'block' });
3024
3102
  }
3025
3103
 
3026
- tooLong = actualWidth > width;
3027
- if (!tooLong || words.length === 1) { // new line needed
3028
- words = rest;
3029
- rest = [];
3030
- if (words.length) {
3031
- softLineNo++;
3032
-
3033
- if (clipHeight && softLineNo * dy > clipHeight) {
3034
- words = ['...'];
3035
- wrapper.attr('title', wrapper.textStr);
3036
- } else {
3037
-
3038
- tspan = doc.createElementNS(SVG_NS, 'tspan');
3039
- attr(tspan, {
3040
- dy: dy,
3041
- x: parentX
3042
- });
3043
- if (spanStyle) { // #390
3044
- attr(tspan, 'style', spanStyle);
3045
- }
3046
- textNode.appendChild(tspan);
3104
+ // Set the line height based on the font size of either
3105
+ // the text element or the tspan element
3106
+ attr(
3107
+ tspan,
3108
+ 'dy',
3109
+ getLineHeight(tspan),
3110
+ // Safari 6.0.2 - too optimized for its own good (#1539)
3111
+ // TODO: revisit this with future versions of Safari
3112
+ isWebKit && tspan.offsetHeight
3113
+ );
3114
+ }
3115
+
3116
+ // Append it
3117
+ textNode.appendChild(tspan);
3118
+
3119
+ spanNo++;
3120
+
3121
+ // check width and apply soft breaks
3122
+ if (width) {
3123
+ var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
3124
+ hasWhiteSpace = words.length > 1 && textStyles.whiteSpace !== 'nowrap',
3125
+ tooLong,
3126
+ actualWidth,
3127
+ clipHeight = wrapper._clipHeight,
3128
+ rest = [],
3129
+ dy = getLineHeight(),
3130
+ softLineNo = 1,
3131
+ bBox;
3132
+
3133
+ while (hasWhiteSpace && (words.length || rest.length)) {
3134
+ delete wrapper.bBox; // delete cache
3135
+ bBox = wrapper.getBBox();
3136
+ actualWidth = bBox.width;
3137
+
3138
+ // Old IE cannot measure the actualWidth for SVG elements (#2314)
3139
+ if (!hasSVG && renderer.forExport) {
3140
+ actualWidth = renderer.measureSpanWidth(tspan.firstChild.data, wrapper.styles);
3141
+ }
3047
3142
 
3048
- if (actualWidth > width) { // a single word is pressing it out
3049
- width = actualWidth;
3143
+ tooLong = actualWidth > width;
3144
+ if (!tooLong || words.length === 1) { // new line needed
3145
+ words = rest;
3146
+ rest = [];
3147
+ if (words.length) {
3148
+ softLineNo++;
3149
+
3150
+ if (clipHeight && softLineNo * dy > clipHeight) {
3151
+ words = ['...'];
3152
+ wrapper.attr('title', wrapper.textStr);
3153
+ } else {
3154
+
3155
+ tspan = doc.createElementNS(SVG_NS, 'tspan');
3156
+ attr(tspan, {
3157
+ dy: dy,
3158
+ x: parentX
3159
+ });
3160
+ if (spanStyle) { // #390
3161
+ attr(tspan, 'style', spanStyle);
3162
+ }
3163
+ textNode.appendChild(tspan);
3164
+
3165
+ if (actualWidth > width) { // a single word is pressing it out
3166
+ width = actualWidth;
3167
+ }
3050
3168
  }
3051
3169
  }
3170
+ } else { // append to existing line tspan
3171
+ tspan.removeChild(tspan.firstChild);
3172
+ rest.unshift(words.pop());
3173
+ }
3174
+ if (words.length) {
3175
+ tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
3052
3176
  }
3053
- } else { // append to existing line tspan
3054
- tspan.removeChild(tspan.firstChild);
3055
- rest.unshift(words.pop());
3056
- }
3057
- if (words.length) {
3058
- tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
3059
3177
  }
3060
3178
  }
3061
3179
  }
3062
3180
  }
3063
- }
3181
+ });
3064
3182
  });
3065
- });
3183
+ }
3066
3184
  },
3067
3185
 
3068
3186
  /**
@@ -3084,7 +3202,6 @@ SVGRenderer.prototype = {
3084
3202
  hoverStyle,
3085
3203
  pressedStyle,
3086
3204
  disabledStyle,
3087
- STYLE = 'style',
3088
3205
  verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };
3089
3206
 
3090
3207
  // Normal state - prepare the attributes
@@ -3104,8 +3221,8 @@ SVGRenderer.prototype = {
3104
3221
  color: 'black'
3105
3222
  }
3106
3223
  }, normalState);
3107
- normalStyle = normalState[STYLE];
3108
- delete normalState[STYLE];
3224
+ normalStyle = normalState.style;
3225
+ delete normalState.style;
3109
3226
 
3110
3227
  // Hover state
3111
3228
  hoverState = merge(normalState, {
@@ -3118,8 +3235,8 @@ SVGRenderer.prototype = {
3118
3235
  ]
3119
3236
  }
3120
3237
  }, hoverState);
3121
- hoverStyle = hoverState[STYLE];
3122
- delete hoverState[STYLE];
3238
+ hoverStyle = hoverState.style;
3239
+ delete hoverState.style;
3123
3240
 
3124
3241
  // Pressed state
3125
3242
  pressedState = merge(normalState, {
@@ -3132,8 +3249,8 @@ SVGRenderer.prototype = {
3132
3249
  ]
3133
3250
  }
3134
3251
  }, pressedState);
3135
- pressedStyle = pressedState[STYLE];
3136
- delete pressedState[STYLE];
3252
+ pressedStyle = pressedState.style;
3253
+ delete pressedState.style;
3137
3254
 
3138
3255
  // Disabled state
3139
3256
  disabledState = merge(normalState, {
@@ -3141,8 +3258,8 @@ SVGRenderer.prototype = {
3141
3258
  color: '#CCC'
3142
3259
  }
3143
3260
  }, disabledState);
3144
- disabledStyle = disabledState[STYLE];
3145
- delete disabledState[STYLE];
3261
+ disabledStyle = disabledState.style;
3262
+ delete disabledState.style;
3146
3263
 
3147
3264
  // Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667).
3148
3265
  addEvent(label.element, isIE ? 'mouseover' : 'mouseenter', function () {
@@ -3232,9 +3349,16 @@ SVGRenderer.prototype = {
3232
3349
  x: x,
3233
3350
  y: y,
3234
3351
  r: r
3235
- };
3352
+ },
3353
+ wrapper = this.createElement('circle');
3236
3354
 
3237
- return this.createElement('circle').attr(attr);
3355
+ wrapper.xSetter = function (value) {
3356
+ this.element.setAttribute('cx', value);
3357
+ };
3358
+ wrapper.ySetter = function (value) {
3359
+ this.element.setAttribute('cy', value);
3360
+ };
3361
+ return wrapper.attr(attr);
3238
3362
  },
3239
3363
 
3240
3364
  /**
@@ -3283,7 +3407,7 @@ SVGRenderer.prototype = {
3283
3407
  r = isObject(x) ? x.r : r;
3284
3408
 
3285
3409
  var wrapper = this.createElement('rect'),
3286
- attr = isObject(x) ? x : x === UNDEFINED ? {} : {
3410
+ attribs = isObject(x) ? x : x === UNDEFINED ? {} : {
3287
3411
  x: x,
3288
3412
  y: y,
3289
3413
  width: mathMax(width, 0),
@@ -3291,15 +3415,22 @@ SVGRenderer.prototype = {
3291
3415
  };
3292
3416
 
3293
3417
  if (strokeWidth !== UNDEFINED) {
3294
- attr.strokeWidth = strokeWidth;
3295
- attr = wrapper.crisp(attr);
3418
+ attribs.strokeWidth = strokeWidth;
3419
+ attribs = wrapper.crisp(attribs);
3296
3420
  }
3297
3421
 
3298
3422
  if (r) {
3299
- attr.r = r;
3300
- }
3423
+ attribs.r = r;
3424
+ }
3425
+
3426
+ wrapper.rSetter = function (value) {
3427
+ attr(this.element, {
3428
+ rx: value,
3429
+ ry: value
3430
+ });
3431
+ };
3301
3432
 
3302
- return wrapper.attr(attr);
3433
+ return wrapper.attr(attribs);
3303
3434
  },
3304
3435
 
3305
3436
  /**
@@ -3563,6 +3694,65 @@ SVGRenderer.prototype = {
3563
3694
 
3564
3695
  open ? '' : 'Z' // close
3565
3696
  ];
3697
+ },
3698
+
3699
+ /**
3700
+ * Callout shape used for default tooltips, also used for rounded rectangles in VML
3701
+ */
3702
+ callout: function (x, y, w, h, options) {
3703
+ var arrowLength = 6,
3704
+ halfDistance = 6,
3705
+ r = mathMin((options && options.r) || 0, w, h),
3706
+ safeDistance = r + halfDistance,
3707
+ anchorX = options && options.anchorX,
3708
+ anchorY = options && options.anchorY,
3709
+ path,
3710
+ normalizer = mathRound(options.strokeWidth || 0) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors;
3711
+
3712
+ x += normalizer;
3713
+ y += normalizer;
3714
+ path = [
3715
+ 'M', x + r, y,
3716
+ 'L', x + w - r, y, // top side
3717
+ 'C', x + w, y, x + w, y, x + w, y + r, // top-right corner
3718
+ 'L', x + w, y + h - r, // right side
3719
+ 'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-right corner
3720
+ 'L', x + r, y + h, // bottom side
3721
+ 'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner
3722
+ 'L', x, y + r, // left side
3723
+ 'C', x, y, x, y, x + r, y // top-right corner
3724
+ ];
3725
+
3726
+ if (anchorX && anchorX > w && anchorY > y + safeDistance && anchorY < y + h - safeDistance) { // replace right side
3727
+ path.splice(13, 3,
3728
+ 'L', x + w, anchorY - halfDistance,
3729
+ x + w + arrowLength, anchorY,
3730
+ x + w, anchorY + halfDistance,
3731
+ x + w, y + h - r
3732
+ );
3733
+ } else if (anchorX && anchorX < 0 && anchorY > y + safeDistance && anchorY < y + h - safeDistance) { // replace left side
3734
+ path.splice(33, 3,
3735
+ 'L', x, anchorY + halfDistance,
3736
+ x - arrowLength, anchorY,
3737
+ x, anchorY - halfDistance,
3738
+ x, y + r
3739
+ );
3740
+ } else if (anchorY && anchorY > h && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { // replace bottom
3741
+ path.splice(23, 3,
3742
+ 'L', anchorX + halfDistance, y + h,
3743
+ anchorX, y + h + arrowLength,
3744
+ anchorX - halfDistance, y + h,
3745
+ x + r, y + h
3746
+ );
3747
+ } else if (anchorY && anchorY < 0 && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { // replace top
3748
+ path.splice(3, 3,
3749
+ 'L', anchorX - halfDistance, y,
3750
+ anchorX, y - arrowLength,
3751
+ anchorX + halfDistance, y,
3752
+ w - r, y
3753
+ );
3754
+ }
3755
+ return path;
3566
3756
  }
3567
3757
  },
3568
3758
 
@@ -3590,132 +3780,7 @@ SVGRenderer.prototype = {
3590
3780
  },
3591
3781
 
3592
3782
 
3593
- /**
3594
- * Take a color and return it if it's a string, make it a gradient if it's a
3595
- * gradient configuration object. Prior to Highstock, an array was used to define
3596
- * a linear gradient with pixel positions relative to the SVG. In newer versions
3597
- * we change the coordinates to apply relative to the shape, using coordinates
3598
- * 0-1 within the shape. To preserve backwards compatibility, linearGradient
3599
- * in this definition is an object of x1, y1, x2 and y2.
3600
- *
3601
- * @param {Object} color The color or config object
3602
- */
3603
- color: function (color, elem, prop) {
3604
- var renderer = this,
3605
- colorObject,
3606
- regexRgba = /^rgba/,
3607
- gradName,
3608
- gradAttr,
3609
- gradients,
3610
- gradientObject,
3611
- stops,
3612
- stopColor,
3613
- stopOpacity,
3614
- radialReference,
3615
- n,
3616
- id,
3617
- key = [];
3618
-
3619
- // Apply linear or radial gradients
3620
- if (color && color.linearGradient) {
3621
- gradName = 'linearGradient';
3622
- } else if (color && color.radialGradient) {
3623
- gradName = 'radialGradient';
3624
- }
3625
-
3626
- if (gradName) {
3627
- gradAttr = color[gradName];
3628
- gradients = renderer.gradients;
3629
- stops = color.stops;
3630
- radialReference = elem.radialReference;
3631
-
3632
- // Keep < 2.2 kompatibility
3633
- if (isArray(gradAttr)) {
3634
- color[gradName] = gradAttr = {
3635
- x1: gradAttr[0],
3636
- y1: gradAttr[1],
3637
- x2: gradAttr[2],
3638
- y2: gradAttr[3],
3639
- gradientUnits: 'userSpaceOnUse'
3640
- };
3641
- }
3642
-
3643
- // Correct the radial gradient for the radial reference system
3644
- if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {
3645
- gradAttr = merge(gradAttr, {
3646
- cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],
3647
- cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],
3648
- r: gradAttr.r * radialReference[2],
3649
- gradientUnits: 'userSpaceOnUse'
3650
- });
3651
- }
3652
-
3653
- // Build the unique key to detect whether we need to create a new element (#1282)
3654
- for (n in gradAttr) {
3655
- if (n !== 'id') {
3656
- key.push(n, gradAttr[n]);
3657
- }
3658
- }
3659
- for (n in stops) {
3660
- key.push(stops[n]);
3661
- }
3662
- key = key.join(',');
3663
-
3664
- // Check if a gradient object with the same config object is created within this renderer
3665
- if (gradients[key]) {
3666
- id = gradients[key].id;
3667
-
3668
- } else {
3669
-
3670
- // Set the id and create the element
3671
- gradAttr.id = id = PREFIX + idCounter++;
3672
- gradients[key] = gradientObject = renderer.createElement(gradName)
3673
- .attr(gradAttr)
3674
- .add(renderer.defs);
3675
-
3676
-
3677
- // The gradient needs to keep a list of stops to be able to destroy them
3678
- gradientObject.stops = [];
3679
- each(stops, function (stop) {
3680
- var stopObject;
3681
- if (regexRgba.test(stop[1])) {
3682
- colorObject = Color(stop[1]);
3683
- stopColor = colorObject.get('rgb');
3684
- stopOpacity = colorObject.get('a');
3685
- } else {
3686
- stopColor = stop[1];
3687
- stopOpacity = 1;
3688
- }
3689
- stopObject = renderer.createElement('stop').attr({
3690
- offset: stop[0],
3691
- 'stop-color': stopColor,
3692
- 'stop-opacity': stopOpacity
3693
- }).add(gradientObject);
3694
-
3695
- // Add the stop element to the gradient
3696
- gradientObject.stops.push(stopObject);
3697
- });
3698
- }
3699
-
3700
- // Return the reference to the gradient object
3701
- return 'url(' + renderer.url + '#' + id + ')';
3702
-
3703
- // Webkit and Batik can't show rgba.
3704
- } else if (regexRgba.test(color)) {
3705
- colorObject = Color(color);
3706
- attr(elem, prop + '-opacity', colorObject.get('a'));
3707
-
3708
- return colorObject.get('rgb');
3709
-
3710
-
3711
- } else {
3712
- // Remove the opacity attribute added above. Does not throw if the attribute is not there.
3713
- elem.removeAttribute(prop + '-opacity');
3714
-
3715
- return color;
3716
- }
3717
-
3718
- },
3783
+
3719
3784
 
3720
3785
 
3721
3786
  /**
@@ -3730,21 +3795,23 @@ SVGRenderer.prototype = {
3730
3795
  // declare variables
3731
3796
  var renderer = this,
3732
3797
  fakeSVG = useCanVG || (!hasSVG && renderer.forExport),
3733
- wrapper;
3798
+ wrapper,
3799
+ attr = {};
3734
3800
 
3735
3801
  if (useHTML && !renderer.forExport) {
3736
3802
  return renderer.html(str, x, y);
3737
3803
  }
3738
3804
 
3739
- x = mathRound(pick(x, 0));
3740
- y = mathRound(pick(y, 0));
3805
+ attr.x = Math.round(x || 0); // X is always needed for line-wrap logic
3806
+ if (y) {
3807
+ attr.y = Math.round(y);
3808
+ }
3809
+ if (str || str === 0) {
3810
+ attr.text = str;
3811
+ }
3741
3812
 
3742
3813
  wrapper = renderer.createElement('text')
3743
- .attr({
3744
- x: x,
3745
- y: y,
3746
- text: str
3747
- });
3814
+ .attr(attr);
3748
3815
 
3749
3816
  // Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063)
3750
3817
  if (fakeSVG) {
@@ -3753,8 +3820,22 @@ SVGRenderer.prototype = {
3753
3820
  });
3754
3821
  }
3755
3822
 
3756
- wrapper.x = x;
3757
- wrapper.y = y;
3823
+ if (!useHTML) {
3824
+ wrapper.xSetter = function (value, key, element) {
3825
+ var childNodes = element.childNodes,
3826
+ child,
3827
+ i;
3828
+ for (i = 1; i < childNodes.length; i++) {
3829
+ child = childNodes[i];
3830
+ // if the x values are equal, the tspan represents a linebreak
3831
+ if (child.getAttribute('x') === element.getAttribute('x')) {
3832
+ child.setAttribute('x', value);
3833
+ }
3834
+ }
3835
+ element.setAttribute(key, value);
3836
+ };
3837
+ }
3838
+
3758
3839
  return wrapper;
3759
3840
  },
3760
3841
 
@@ -3811,7 +3892,6 @@ SVGRenderer.prototype = {
3811
3892
  crispAdjust = 0,
3812
3893
  deferredAttr = {},
3813
3894
  baselineOffset,
3814
- attrSetters = wrapper.attrSetters,
3815
3895
  needsBox;
3816
3896
 
3817
3897
  /**
@@ -3832,6 +3912,7 @@ SVGRenderer.prototype = {
3832
3912
  // update the label-scoped y offset
3833
3913
  baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b;
3834
3914
 
3915
+
3835
3916
  if (needsBox) {
3836
3917
 
3837
3918
  // create the border box if it is not already present
@@ -3847,9 +3928,9 @@ SVGRenderer.prototype = {
3847
3928
 
3848
3929
  // apply the box attributes
3849
3930
  if (!box.isImg) { // #1630
3850
- box.attr(merge({
3851
- width: wrapper.width,
3852
- height: wrapper.height
3931
+ box.attr(extend({
3932
+ width: mathRound(wrapper.width),
3933
+ height: mathRound(wrapper.height)
3853
3934
  }, deferredAttr));
3854
3935
  }
3855
3936
  deferredAttr = null;
@@ -3875,10 +3956,10 @@ SVGRenderer.prototype = {
3875
3956
 
3876
3957
  // update if anything changed
3877
3958
  if (x !== text.x || y !== text.y) {
3878
- text.attr({
3879
- x: x,
3880
- y: y
3881
- });
3959
+ text.attr('x', x);
3960
+ if (y !== UNDEFINED) {
3961
+ text.attr('y', y);
3962
+ }
3882
3963
  }
3883
3964
 
3884
3965
  // record current values
@@ -3906,7 +3987,7 @@ SVGRenderer.prototype = {
3906
3987
  wrapper.onAdd = function () {
3907
3988
  text.add(wrapper);
3908
3989
  wrapper.attr({
3909
- text: str, // alignment is available now
3990
+ text: str || '', // alignment is available now
3910
3991
  x: x,
3911
3992
  y: y
3912
3993
  });
@@ -3924,84 +4005,75 @@ SVGRenderer.prototype = {
3924
4005
  */
3925
4006
 
3926
4007
  // only change local variables
3927
- attrSetters.width = function (value) {
4008
+ wrapper.widthSetter = function (value) {
3928
4009
  width = value;
3929
- return false;
3930
4010
  };
3931
- attrSetters.height = function (value) {
4011
+ wrapper.heightSetter = function (value) {
3932
4012
  height = value;
3933
- return false;
3934
4013
  };
3935
- attrSetters.padding = function (value) {
4014
+ wrapper.paddingSetter = function (value) {
3936
4015
  if (defined(value) && value !== padding) {
3937
4016
  padding = value;
3938
4017
  updateTextPadding();
3939
4018
  }
3940
- return false;
3941
4019
  };
3942
- attrSetters.paddingLeft = function (value) {
4020
+ wrapper.paddingLeftSetter = function (value) {
3943
4021
  if (defined(value) && value !== paddingLeft) {
3944
4022
  paddingLeft = value;
3945
4023
  updateTextPadding();
3946
4024
  }
3947
- return false;
3948
4025
  };
3949
4026
 
3950
4027
 
3951
- // change local variable and set attribue as well
3952
- attrSetters.align = function (value) {
4028
+ // change local variable and prevent setting attribute on the group
4029
+ wrapper.alignSetter = function (value) {
3953
4030
  alignFactor = { left: 0, center: 0.5, right: 1 }[value];
3954
- return false; // prevent setting text-anchor on the group
3955
4031
  };
3956
4032
 
3957
4033
  // apply these to the box and the text alike
3958
- attrSetters.text = function (value, key) {
3959
- text.attr(key, value);
4034
+ wrapper.textSetter = function (value) {
4035
+ if (value !== UNDEFINED) {
4036
+ text.textSetter(value);
4037
+ }
3960
4038
  updateBoxSize();
3961
4039
  updateTextPadding();
3962
- return false;
3963
4040
  };
3964
4041
 
3965
4042
  // apply these to the box but not to the text
3966
- attrSetters[STROKE_WIDTH] = function (value, key) {
4043
+ wrapper['stroke-widthSetter'] = function (value, key) {
3967
4044
  if (value) {
3968
4045
  needsBox = true;
3969
4046
  }
3970
4047
  crispAdjust = value % 2 / 2;
3971
4048
  boxAttr(key, value);
3972
- return false;
3973
4049
  };
3974
- attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) {
4050
+ wrapper.strokeSetter = wrapper.fillSetter = wrapper.rSetter = function (value, key) {
3975
4051
  if (key === 'fill' && value) {
3976
4052
  needsBox = true;
3977
4053
  }
3978
4054
  boxAttr(key, value);
3979
- return false;
3980
4055
  };
3981
- attrSetters.anchorX = function (value, key) {
4056
+ wrapper.anchorXSetter = function (value, key) {
3982
4057
  anchorX = value;
3983
4058
  boxAttr(key, value + crispAdjust - wrapperX);
3984
- return false;
3985
4059
  };
3986
- attrSetters.anchorY = function (value, key) {
4060
+ wrapper.anchorYSetter = function (value, key) {
3987
4061
  anchorY = value;
3988
4062
  boxAttr(key, value - wrapperY);
3989
- return false;
3990
4063
  };
3991
4064
 
3992
4065
  // rename attributes
3993
- attrSetters.x = function (value) {
4066
+ wrapper.xSetter = function (value) {
3994
4067
  wrapper.x = value; // for animation getter
3995
- value -= alignFactor * ((width || bBox.width) + padding);
4068
+ if (alignFactor) {
4069
+ value -= alignFactor * ((width || bBox.width) + padding);
4070
+ }
3996
4071
  wrapperX = mathRound(value);
3997
-
3998
4072
  wrapper.attr('translateX', wrapperX);
3999
- return false;
4000
4073
  };
4001
- attrSetters.y = function (value) {
4074
+ wrapper.ySetter = function (value) {
4002
4075
  wrapperY = wrapper.y = mathRound(value);
4003
4076
  wrapper.attr('translateY', wrapperY);
4004
- return false;
4005
4077
  };
4006
4078
 
4007
4079
  // Redirect certain methods to either the box or the text
@@ -4251,28 +4323,24 @@ extend(SVGRenderer.prototype, {
4251
4323
  */
4252
4324
  html: function (str, x, y) {
4253
4325
  var wrapper = this.createElement('span'),
4254
- attrSetters = wrapper.attrSetters,
4255
4326
  element = wrapper.element,
4256
4327
  renderer = wrapper.renderer;
4257
4328
 
4258
4329
  // Text setter
4259
- attrSetters.text = function (value) {
4330
+ wrapper.textSetter = function (value) {
4260
4331
  if (value !== element.innerHTML) {
4261
4332
  delete this.bBox;
4262
4333
  }
4263
4334
  element.innerHTML = this.textStr = value;
4264
- return false;
4265
4335
  };
4266
4336
 
4267
4337
  // Various setters which rely on update transform
4268
- attrSetters.x = attrSetters.y = attrSetters.align = attrSetters.rotation = function (value, key) {
4338
+ wrapper.xSetter = wrapper.ySetter = wrapper.alignSetter = wrapper.rotationSetter = function (value, key) {
4269
4339
  if (key === 'align') {
4270
4340
  key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML.
4271
4341
  }
4272
4342
  wrapper[key] = value;
4273
4343
  wrapper.htmlUpdateTransform();
4274
-
4275
- return false;
4276
4344
  };
4277
4345
 
4278
4346
  // Set the default attributes
@@ -4336,14 +4404,18 @@ extend(SVGRenderer.prototype, {
4336
4404
 
4337
4405
  // Set listeners to update the HTML div's position whenever the SVG group
4338
4406
  // position is changed
4339
- extend(parentGroup.attrSetters, {
4340
- translateX: function (value) {
4407
+ extend(parentGroup, {
4408
+ translateXSetter: function (value, key) {
4341
4409
  htmlGroupStyle.left = value + PX;
4410
+ parentGroup[key] = value;
4411
+ parentGroup.doTransform = true;
4342
4412
  },
4343
- translateY: function (value) {
4413
+ translateYSetter: function (value, key) {
4344
4414
  htmlGroupStyle.top = value + PX;
4415
+ parentGroup[key] = value;
4416
+ parentGroup.doTransform = true;
4345
4417
  },
4346
- visibility: function (value, key) {
4418
+ visibilitySetter: function (value, key) {
4347
4419
  htmlGroupStyle[key] = value;
4348
4420
  }
4349
4421
  });
@@ -4418,7 +4490,6 @@ Highcharts.VMLElement = VMLElement = {
4418
4490
  }
4419
4491
 
4420
4492
  wrapper.renderer = renderer;
4421
- wrapper.attrSetters = {};
4422
4493
  },
4423
4494
 
4424
4495
  /**
@@ -4560,237 +4631,12 @@ Highcharts.VMLElement = VMLElement = {
4560
4631
  if (path[i] === 'H') { // horizontal line to
4561
4632
  path[i] = 'L';
4562
4633
  path.splice(i + 2, 0, path[i - 1]);
4563
- } else if (path[i] === 'V') { // vertical line to
4564
- path[i] = 'L';
4565
- path.splice(i + 1, 0, path[i - 2]);
4566
- }
4567
- }*/
4568
- return path.join(' ') || 'x';
4569
- },
4570
-
4571
- /**
4572
- * Get or set attributes
4573
- */
4574
- attr: function (hash, val) {
4575
- var wrapper = this,
4576
- key,
4577
- value,
4578
- i,
4579
- result,
4580
- element = wrapper.element || {},
4581
- elemStyle = element.style,
4582
- nodeName = element.nodeName,
4583
- renderer = wrapper.renderer,
4584
- symbolName = wrapper.symbolName,
4585
- hasSetSymbolSize,
4586
- shadows = wrapper.shadows,
4587
- skipAttr,
4588
- attrSetters = wrapper.attrSetters,
4589
- ret = wrapper;
4590
-
4591
- // single key-value pair
4592
- if (isString(hash) && defined(val)) {
4593
- key = hash;
4594
- hash = {};
4595
- hash[key] = val;
4596
- }
4597
-
4598
- // used as a getter, val is undefined
4599
- if (isString(hash)) {
4600
- key = hash;
4601
- if (key === 'strokeWidth' || key === 'stroke-width') {
4602
- ret = wrapper.strokeweight;
4603
- } else {
4604
- ret = wrapper[key];
4605
- }
4606
-
4607
- // setter
4608
- } else {
4609
- for (key in hash) {
4610
- value = hash[key];
4611
- skipAttr = false;
4612
-
4613
- // check for a specific attribute setter
4614
- result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);
4615
-
4616
- if (result !== false && value !== null) { // #620
4617
-
4618
- if (result !== UNDEFINED) {
4619
- value = result; // the attribute setter has returned a new value to set
4620
- }
4621
-
4622
-
4623
- // prepare paths
4624
- // symbols
4625
- if (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) {
4626
- // if one of the symbol size affecting parameters are changed,
4627
- // check all the others only once for each call to an element's
4628
- // .attr() method
4629
- if (!hasSetSymbolSize) {
4630
- wrapper.symbolAttr(hash);
4631
-
4632
- hasSetSymbolSize = true;
4633
- }
4634
- skipAttr = true;
4635
-
4636
- } else if (key === 'd') {
4637
- value = value || [];
4638
- wrapper.d = value.join(' '); // used in getter for animation
4639
-
4640
- element.path = value = wrapper.pathToVML(value);
4641
-
4642
- // update shadows
4643
- if (shadows) {
4644
- i = shadows.length;
4645
- while (i--) {
4646
- shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value;
4647
- }
4648
- }
4649
- skipAttr = true;
4650
-
4651
- // handle visibility
4652
- } else if (key === 'visibility') {
4653
-
4654
- // Handle inherited visibility
4655
- if (value === 'inherit') {
4656
- value = VISIBLE;
4657
- }
4658
-
4659
- // Let the shadow follow the main element
4660
- if (shadows) {
4661
- i = shadows.length;
4662
- while (i--) {
4663
- shadows[i].style[key] = value;
4664
- }
4665
- }
4666
-
4667
- // Instead of toggling the visibility CSS property, move the div out of the viewport.
4668
- // This works around #61 and #586
4669
- if (nodeName === 'DIV') {
4670
- value = value === HIDDEN ? '-999em' : 0;
4671
-
4672
- // In order to redraw, IE7 needs the div to be visible when tucked away
4673
- // outside the viewport. So the visibility is actually opposite of
4674
- // the expected value. This applies to the tooltip only.
4675
- if (!docMode8) {
4676
- elemStyle[key] = value ? VISIBLE : HIDDEN;
4677
- }
4678
- key = 'top';
4679
- }
4680
- elemStyle[key] = value;
4681
- skipAttr = true;
4682
-
4683
- // directly mapped to css
4684
- } else if (key === 'zIndex') {
4685
-
4686
- if (value) {
4687
- elemStyle[key] = value;
4688
- }
4689
- skipAttr = true;
4690
-
4691
- // x, y, width, height
4692
- } else if (inArray(key, ['x', 'y', 'width', 'height']) !== -1) {
4693
-
4694
- wrapper[key] = value; // used in getter
4695
-
4696
- if (key === 'x' || key === 'y') {
4697
- key = { x: 'left', y: 'top' }[key];
4698
- } else {
4699
- value = mathMax(0, value); // don't set width or height below zero (#311)
4700
- }
4701
-
4702
- // clipping rectangle special
4703
- if (wrapper.updateClipping) {
4704
- wrapper[key] = value; // the key is now 'left' or 'top' for 'x' and 'y'
4705
- wrapper.updateClipping();
4706
- } else {
4707
- // normal
4708
- elemStyle[key] = value;
4709
- }
4710
-
4711
- skipAttr = true;
4712
-
4713
- // class name
4714
- } else if (key === 'class' && nodeName === 'DIV') {
4715
- // IE8 Standards mode has problems retrieving the className
4716
- element.className = value;
4717
-
4718
- // stroke
4719
- } else if (key === 'stroke') {
4720
-
4721
- value = renderer.color(value, element, key);
4722
-
4723
- key = 'strokecolor';
4724
-
4725
- // stroke width
4726
- } else if (key === 'stroke-width' || key === 'strokeWidth') {
4727
- element.stroked = value ? true : false;
4728
- key = 'strokeweight';
4729
- wrapper[key] = value; // used in getter, issue #113
4730
- if (isNumber(value)) {
4731
- value += PX;
4732
- }
4733
-
4734
- // dashStyle
4735
- } else if (key === 'dashstyle') {
4736
- var strokeElem = element.getElementsByTagName('stroke')[0] ||
4737
- createElement(renderer.prepVML(['<stroke/>']), null, null, element);
4738
- strokeElem[key] = value || 'solid';
4739
- wrapper.dashstyle = value; /* because changing stroke-width will change the dash length
4740
- and cause an epileptic effect */
4741
- skipAttr = true;
4742
-
4743
- // fill
4744
- } else if (key === 'fill') {
4745
-
4746
- if (nodeName === 'SPAN') { // text color
4747
- elemStyle.color = value;
4748
- } else if (nodeName !== 'IMG') { // #1336
4749
- element.filled = value !== NONE ? true : false;
4750
-
4751
- value = renderer.color(value, element, key, wrapper);
4752
-
4753
- key = 'fillcolor';
4754
- }
4755
-
4756
- // opacity: don't bother - animation is too slow and filters introduce artifacts
4757
- } else if (key === 'opacity') {
4758
- /*css(element, {
4759
- opacity: value
4760
- });*/
4761
- skipAttr = true;
4762
-
4763
- // rotation on VML elements
4764
- } else if (nodeName === 'shape' && key === 'rotation') {
4765
-
4766
- wrapper[key] = element.style[key] = value; // style is for #1873
4767
-
4768
- // Correction for the 1x1 size of the shape container. Used in gauge needles.
4769
- element.style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX;
4770
- element.style.top = mathRound(mathCos(value * deg2rad)) + PX;
4771
-
4772
- // translation for animation
4773
- } else if (key === 'translateX' || key === 'translateY' || key === 'rotation') {
4774
- wrapper[key] = value;
4775
- wrapper.updateTransform();
4776
-
4777
- skipAttr = true;
4778
-
4779
- }
4780
-
4781
-
4782
- if (!skipAttr) {
4783
- if (docMode8) { // IE8 setAttribute bug
4784
- element[key] = value;
4785
- } else {
4786
- attr(element, key, value);
4787
- }
4788
- }
4789
-
4790
- }
4634
+ } else if (path[i] === 'V') { // vertical line to
4635
+ path[i] = 'L';
4636
+ path.splice(i + 1, 0, path[i - 2]);
4791
4637
  }
4792
- }
4793
- return ret;
4638
+ }*/
4639
+ return path.join(' ') || 'x';
4794
4640
  },
4795
4641
 
4796
4642
  /**
@@ -4953,11 +4799,138 @@ Highcharts.VMLElement = VMLElement = {
4953
4799
  this.shadows = shadows;
4954
4800
  }
4955
4801
  return this;
4802
+ },
4803
+ updateShadows: noop, // Used in SVG only
4804
+
4805
+ setAttr: function (key, value) {
4806
+ if (docMode8) { // IE8 setAttribute bug
4807
+ this.element[key] = value;
4808
+ } else {
4809
+ this.element.setAttribute(key, value);
4810
+ }
4811
+ },
4812
+ classSetter: function (value) {
4813
+ // IE8 Standards mode has problems retrieving the className unless set like this
4814
+ this.element.className = value;
4815
+ },
4816
+ dashstyleSetter: function (value, key, element) {
4817
+ var strokeElem = element.getElementsByTagName('stroke')[0] ||
4818
+ createElement(this.renderer.prepVML(['<stroke/>']), null, null, element);
4819
+ strokeElem[key] = value || 'solid';
4820
+ this[key] = value; /* because changing stroke-width will change the dash length
4821
+ and cause an epileptic effect */
4822
+ },
4823
+ dSetter: function (value, key, element) {
4824
+ var i,
4825
+ shadows = this.shadows;
4826
+ value = value || [];
4827
+ this.d = value.join(' '); // used in getter for animation
4828
+
4829
+ element.path = value = this.pathToVML(value);
4830
+
4831
+ // update shadows
4832
+ if (shadows) {
4833
+ i = shadows.length;
4834
+ while (i--) {
4835
+ shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value;
4836
+ }
4837
+ }
4838
+ this.setAttr(key, value);
4839
+ },
4840
+ fillSetter: function (value, key, element) {
4841
+ var nodeName = element.nodeName;
4842
+ if (nodeName === 'SPAN') { // text color
4843
+ element.style.color = value;
4844
+ } else if (nodeName !== 'IMG') { // #1336
4845
+ element.filled = value !== NONE;
4846
+ this.setAttr('fillcolor', this.renderer.color(value, element, key, this));
4847
+ }
4848
+ },
4849
+ opacitySetter: noop, // Don't bother - animation is too slow and filters introduce artifacts
4850
+ rotationSetter: function (value, key, element) {
4851
+ var style = element.style;
4852
+ this[key] = style[key] = value; // style is for #1873
4853
+
4854
+ // Correction for the 1x1 size of the shape container. Used in gauge needles.
4855
+ style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX;
4856
+ style.top = mathRound(mathCos(value * deg2rad)) + PX;
4857
+ },
4858
+ strokeSetter: function (value, key, element) {
4859
+ this.setAttr('strokecolor', this.renderer.color(value, element, key));
4860
+ },
4861
+ 'stroke-widthSetter': function (value, key, element) {
4862
+ element.stroked = !!value; // VML "stroked" attribute
4863
+ this[key] = value; // used in getter, issue #113
4864
+ if (isNumber(value)) {
4865
+ value += PX;
4866
+ }
4867
+ this.setAttr('strokeweight', value);
4868
+ },
4869
+ titleSetter: function (value, key) {
4870
+ this.setAttr(key, value);
4871
+ },
4872
+ visibilitySetter: function (value, key, element) {
4873
+
4874
+ // Handle inherited visibility
4875
+ if (value === 'inherit') {
4876
+ value = VISIBLE;
4877
+ }
4878
+
4879
+ // Let the shadow follow the main element
4880
+ if (this.shadows) {
4881
+ each(this.shadows, function (shadow) {
4882
+ shadow.style[key] = value;
4883
+ });
4884
+ }
4885
+
4886
+ // Instead of toggling the visibility CSS property, move the div out of the viewport.
4887
+ // This works around #61 and #586
4888
+ if (element.nodeName === 'DIV') {
4889
+ value = value === HIDDEN ? '-999em' : 0;
4890
+
4891
+ // In order to redraw, IE7 needs the div to be visible when tucked away
4892
+ // outside the viewport. So the visibility is actually opposite of
4893
+ // the expected value. This applies to the tooltip only.
4894
+ if (!docMode8) {
4895
+ element.style[key] = value ? VISIBLE : HIDDEN;
4896
+ }
4897
+ key = 'top';
4898
+ }
4899
+ element.style[key] = value;
4900
+ },
4901
+ xSetter: function (value, key, element) {
4902
+ this[key] = value; // used in getter
4956
4903
 
4904
+ if (key === 'x') {
4905
+ key = 'left';
4906
+ } else if (key === 'y') {
4907
+ key = 'top';
4908
+ }/* else {
4909
+ value = mathMax(0, value); // don't set width or height below zero (#311)
4910
+ }*/
4911
+
4912
+ // clipping rectangle special
4913
+ if (this.updateClipping) {
4914
+ this[key] = value; // the key is now 'left' or 'top' for 'x' and 'y'
4915
+ this.updateClipping();
4916
+ } else {
4917
+ // normal
4918
+ element.style[key] = value;
4919
+ }
4920
+ },
4921
+ zIndexSetter: function (value, key, element) {
4922
+ element.style[key] = value;
4957
4923
  }
4958
4924
  };
4959
4925
  VMLElement = extendClass(SVGElement, VMLElement);
4960
4926
 
4927
+ // Some shared setters
4928
+ VMLElement.prototype.ySetter =
4929
+ VMLElement.prototype.widthSetter =
4930
+ VMLElement.prototype.heightSetter =
4931
+ VMLElement.prototype.xSetter;
4932
+
4933
+
4961
4934
  /**
4962
4935
  * The VML renderer
4963
4936
  */
@@ -5077,7 +5050,9 @@ var VMLRendererExtension = { // inherit SVGRenderer
5077
5050
  // used in attr and animation to update the clipping of all members
5078
5051
  updateClipping: function () {
5079
5052
  each(clipRect.members, function (member) {
5080
- member.css(clipRect.getCSS(member));
5053
+ if (member.element) { // Deleted series, like in stock/members/series-remove demo. Should be removed from members, but this will do.
5054
+ member.css(clipRect.getCSS(member));
5055
+ }
5081
5056
  });
5082
5057
  }
5083
5058
  });
@@ -5493,71 +5468,13 @@ var VMLRendererExtension = { // inherit SVGRenderer
5493
5468
  },
5494
5469
  /**
5495
5470
  * Add rectangle symbol path which eases rotation and omits arcsize problems
5496
- * compared to the built-in VML roundrect shape
5497
- *
5498
- * @param {Number} left Left position
5499
- * @param {Number} top Top position
5500
- * @param {Number} r Border radius
5501
- * @param {Object} options Width and height
5471
+ * compared to the built-in VML roundrect shape. When borders are not rounded,
5472
+ * use the simpler square path, else use the callout path without the arrow.
5502
5473
  */
5503
-
5504
- rect: function (left, top, width, height, options) {
5505
-
5506
- var right = left + width,
5507
- bottom = top + height,
5508
- ret,
5509
- r;
5510
-
5511
- // No radius, return the more lightweight square
5512
- if (!defined(options) || !options.r) {
5513
- ret = SVGRenderer.prototype.symbols.square.apply(0, arguments);
5514
-
5515
- // Has radius add arcs for the corners
5516
- } else {
5517
-
5518
- r = mathMin(options.r, width, height);
5519
- ret = [
5520
- M,
5521
- left + r, top,
5522
-
5523
- L,
5524
- right - r, top,
5525
- 'wa',
5526
- right - 2 * r, top,
5527
- right, top + 2 * r,
5528
- right - r, top,
5529
- right, top + r,
5530
-
5531
- L,
5532
- right, bottom - r,
5533
- 'wa',
5534
- right - 2 * r, bottom - 2 * r,
5535
- right, bottom,
5536
- right, bottom - r,
5537
- right - r, bottom,
5538
-
5539
- L,
5540
- left + r, bottom,
5541
- 'wa',
5542
- left, bottom - 2 * r,
5543
- left + 2 * r, bottom,
5544
- left + r, bottom,
5545
- left, bottom - r,
5546
-
5547
- L,
5548
- left, top + r,
5549
- 'wa',
5550
- left, top,
5551
- left + 2 * r, top + 2 * r,
5552
- left, top + r,
5553
- left + r, top,
5554
-
5555
-
5556
- 'x',
5557
- 'e'
5558
- ];
5559
- }
5560
- return ret;
5474
+ rect: function (x, y, w, h, options) {
5475
+ return SVGRenderer.prototype.symbols[
5476
+ !defined(options) || !options.r ? 'square' : 'callout'
5477
+ ].call(0, x, y, w, h, options);
5561
5478
  }
5562
5479
  }
5563
5480
  };
@@ -6135,7 +6052,7 @@ Highcharts.PlotLineOrBand.prototype = {
6135
6052
  color = options.color,
6136
6053
  zIndex = options.zIndex,
6137
6054
  events = options.events,
6138
- attribs,
6055
+ attribs = {},
6139
6056
  renderer = axis.chart.renderer;
6140
6057
 
6141
6058
  // logarithmic conversion
@@ -6162,9 +6079,9 @@ Highcharts.PlotLineOrBand.prototype = {
6162
6079
  to = mathMin(to, axis.max + halfPointRange);
6163
6080
 
6164
6081
  path = axis.getPlotBandPath(from, to, options);
6165
- attribs = {
6166
- fill: color
6167
- };
6082
+ if (color) {
6083
+ attribs.fill = color;
6084
+ }
6168
6085
  if (options.borderWidth) {
6169
6086
  attribs.stroke = options.borderColor;
6170
6087
  attribs['stroke-width'] = options.borderWidth;
@@ -6222,17 +6139,20 @@ Highcharts.PlotLineOrBand.prototype = {
6222
6139
 
6223
6140
  // add the SVG element
6224
6141
  if (!label) {
6142
+ attribs = {
6143
+ align: optionsLabel.textAlign || optionsLabel.align,
6144
+ rotation: optionsLabel.rotation
6145
+ };
6146
+ if (defined(zIndex)) {
6147
+ attribs.zIndex = zIndex;
6148
+ }
6225
6149
  plotLine.label = label = renderer.text(
6226
6150
  optionsLabel.text,
6227
6151
  0,
6228
6152
  0,
6229
6153
  optionsLabel.useHTML
6230
6154
  )
6231
- .attr({
6232
- align: optionsLabel.textAlign || optionsLabel.align,
6233
- rotation: optionsLabel.rotation,
6234
- zIndex: zIndex
6235
- })
6155
+ .attr(attribs)
6236
6156
  .css(optionsLabel.style)
6237
6157
  .add();
6238
6158
  }
@@ -6424,7 +6344,7 @@ Axis.prototype = {
6424
6344
  startOnTick: false,
6425
6345
  tickColor: '#C0D0E0',
6426
6346
  //tickInterval: null,
6427
- tickLength: 5,
6347
+ tickLength: 10,
6428
6348
  tickmarkPlacement: 'between', // on or between
6429
6349
  tickPixelInterval: 100,
6430
6350
  tickPosition: 'outside',
@@ -6436,9 +6356,7 @@ Axis.prototype = {
6436
6356
  //rotation: 0,
6437
6357
  //side: 'outside',
6438
6358
  style: {
6439
- color: '#4d759e',
6440
- //font: defaultFont.replace('normal', 'bold')
6441
- fontWeight: 'bold'
6359
+ color: '#707070' // docs
6442
6360
  }
6443
6361
  //x: 0,
6444
6362
  //y: 0
@@ -6487,7 +6405,7 @@ Axis.prototype = {
6487
6405
  */
6488
6406
  defaultLeftAxisOptions: {
6489
6407
  labels: {
6490
- x: -8,
6408
+ x: -15,
6491
6409
  y: null
6492
6410
  },
6493
6411
  title: {
@@ -6500,7 +6418,7 @@ Axis.prototype = {
6500
6418
  */
6501
6419
  defaultRightAxisOptions: {
6502
6420
  labels: {
6503
- x: 8,
6421
+ x: 15,
6504
6422
  y: null
6505
6423
  },
6506
6424
  title: {
@@ -6514,7 +6432,7 @@ Axis.prototype = {
6514
6432
  defaultBottomAxisOptions: {
6515
6433
  labels: {
6516
6434
  x: 0,
6517
- y: 14
6435
+ y: 20
6518
6436
  // overflow: undefined,
6519
6437
  // staggerLines: null
6520
6438
  },
@@ -6528,7 +6446,7 @@ Axis.prototype = {
6528
6446
  defaultTopAxisOptions: {
6529
6447
  labels: {
6530
6448
  x: 0,
6531
- y: -5
6449
+ y: -15
6532
6450
  // overflow: undefined
6533
6451
  // staggerLines: null
6534
6452
  },
@@ -6970,6 +6888,11 @@ Axis.prototype = {
6970
6888
  roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval),
6971
6889
  tickPositions = [];
6972
6890
 
6891
+ // For single points, add a tick regardless of the relative position (#2662)
6892
+ if (min === max && isNumber(min)) {
6893
+ return [min];
6894
+ }
6895
+
6973
6896
  // Populate the intermediate values
6974
6897
  pos = roundedMin;
6975
6898
  while (pos <= roundedMax) {
@@ -7133,7 +7056,7 @@ Axis.prototype = {
7133
7056
 
7134
7057
  } else {
7135
7058
  each(axis.series, function (series) {
7136
- var seriesPointRange = mathMax(axis.isXAxis ? series.pointRange : (axis.axisPointRange || 0), +hasCategories),
7059
+ var seriesPointRange = hasCategories ? 1 : (axis.isXAxis ? series.pointRange : (axis.axisPointRange || 0)), // #2806
7137
7060
  pointPlacement = series.options.pointPlacement,
7138
7061
  seriesClosestPointRange = series.closestPointRange;
7139
7062
 
@@ -7265,6 +7188,14 @@ Axis.prototype = {
7265
7188
  }
7266
7189
  }
7267
7190
 
7191
+ // Stay within floor and ceiling
7192
+ if (isNumber(options.floor)) {
7193
+ axis.min = mathMax(axis.min, options.floor);
7194
+ }
7195
+ if (isNumber(options.ceiling)) {
7196
+ axis.max = mathMin(axis.max, options.ceiling);
7197
+ }
7198
+
7268
7199
  // get tickInterval
7269
7200
  if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) {
7270
7201
  axis.tickInterval = 1;
@@ -7384,10 +7315,10 @@ Axis.prototype = {
7384
7315
  }
7385
7316
 
7386
7317
  // When there is only one point, or all points have the same value on this axis, then min
7387
- // and max are equal and tickPositions.length is 1. In this case, add some padding
7318
+ // and max are equal and tickPositions.length is 0 or 1. In this case, add some padding
7388
7319
  // in order to center the point, but leave it with one tick. #1337.
7389
7320
  if (tickPositions.length === 1) {
7390
- singlePad = mathAbs(axis.max || 1) * 0.001; // The lowest possible number to avoid extra padding on columns (#2619)
7321
+ singlePad = mathAbs(axis.max) > 10e12 ? 1 : 0.001; // The lowest possible number to avoid extra padding on columns (#2619, #2846)
7391
7322
  axis.min -= singlePad;
7392
7323
  axis.max += singlePad;
7393
7324
  }
@@ -7603,16 +7534,25 @@ Axis.prototype = {
7603
7534
  offsetLeft = options.offsetLeft || 0,
7604
7535
  offsetRight = options.offsetRight || 0,
7605
7536
  horiz = this.horiz,
7606
- width,
7607
- height,
7608
- top,
7609
- left;
7537
+ width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight),
7538
+ height = pick(options.height, chart.plotHeight),
7539
+ top = pick(options.top, chart.plotTop),
7540
+ left = pick(options.left, chart.plotLeft + offsetLeft),
7541
+ percentRegex = /%$/; // docs
7542
+
7543
+ // Check for percentage based input values
7544
+ if (percentRegex.test(height)) {
7545
+ height = parseInt(height, 10) / 100 * chart.plotHeight;
7546
+ }
7547
+ if (percentRegex.test(top)) {
7548
+ top = parseInt(top, 10) / 100 * chart.plotHeight + chart.plotTop;
7549
+ }
7610
7550
 
7611
7551
  // Expose basic values to use in Series object and navigator
7612
- this.left = left = pick(options.left, chart.plotLeft + offsetLeft);
7613
- this.top = top = pick(options.top, chart.plotTop);
7614
- this.width = width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight);
7615
- this.height = height = pick(options.height, chart.plotHeight);
7552
+ this.left = left;
7553
+ this.top = top;
7554
+ this.width = width;
7555
+ this.height = height;
7616
7556
  this.bottom = chart.chartHeight - height - top;
7617
7557
  this.right = chart.chartWidth - width - left;
7618
7558
 
@@ -7711,7 +7651,8 @@ Axis.prototype = {
7711
7651
  bBox,
7712
7652
  x,
7713
7653
  w,
7714
- lineNo;
7654
+ lineNo,
7655
+ lineHeightCorrection = side === 2 ? renderer.fontMetrics(labelOptions.style.fontSize).b : 0;
7715
7656
 
7716
7657
  // For reuse in Axis.render
7717
7658
  axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions));
@@ -7844,7 +7785,7 @@ Axis.prototype = {
7844
7785
  axis.axisTitleMargin =
7845
7786
  pick(titleOffsetOption,
7846
7787
  labelOffset + titleMargin +
7847
- (side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x'])
7788
+ (labelOffset && (directionFactor * options.labels[horiz ? 'y' : 'x'] - lineHeightCorrection))
7848
7789
  );
7849
7790
 
7850
7791
  axisOffset[side] = mathMax(
@@ -8610,7 +8551,7 @@ Tooltip.prototype = {
8610
8551
 
8611
8552
 
8612
8553
  // create the label
8613
- this.label = chart.renderer.label('', 0, 0, options.shape, null, null, options.useHTML, null, 'tooltip')
8554
+ this.label = chart.renderer.label('', 0, 0, options.shape || 'callout', null, null, options.useHTML, null, 'tooltip')
8614
8555
  .attr({
8615
8556
  padding: padding,
8616
8557
  fill: options.backgroundColor,
@@ -8655,14 +8596,15 @@ Tooltip.prototype = {
8655
8596
  move: function (x, y, anchorX, anchorY) {
8656
8597
  var tooltip = this,
8657
8598
  now = tooltip.now,
8658
- animate = tooltip.options.animation !== false && !tooltip.isHidden;
8599
+ animate = tooltip.options.animation !== false && !tooltip.isHidden,
8600
+ skipAnchor = tooltip.followPointer || tooltip.len > 1;
8659
8601
 
8660
8602
  // get intermediate values for animation
8661
8603
  extend(now, {
8662
8604
  x: animate ? (2 * now.x + x) / 3 : x,
8663
8605
  y: animate ? (now.y + y) / 2 : y,
8664
- anchorX: animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
8665
- anchorY: animate ? (now.anchorY + anchorY) / 2 : anchorY
8606
+ anchorX: skipAnchor ? UNDEFINED : animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
8607
+ anchorY: skipAnchor ? UNDEFINED : animate ? (now.anchorY + anchorY) / 2 : anchorY
8666
8608
  });
8667
8609
 
8668
8610
  // move to the intermediate value
@@ -8770,49 +8712,89 @@ Tooltip.prototype = {
8770
8712
  */
8771
8713
  getPosition: function (boxWidth, boxHeight, point) {
8772
8714
 
8773
- // Set up the variables
8774
8715
  var chart = this.chart,
8775
- plotLeft = chart.plotLeft,
8776
- plotTop = chart.plotTop,
8777
- plotWidth = chart.plotWidth,
8778
- plotHeight = chart.plotHeight,
8779
- distance = pick(this.options.distance, 12),
8780
- pointX = (isNaN(point.plotX) ? 0 : point.plotX), //#2599
8781
- pointY = point.plotY,
8782
- x = pointX + plotLeft + (chart.inverted ? distance : -boxWidth - distance),
8783
- y = pointY - boxHeight + plotTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip
8784
- alignedRight;
8716
+ distance = this.distance,
8717
+ ret = {},
8718
+ swapped,
8719
+ first = ['y', chart.chartHeight, boxHeight, point.plotY + chart.plotTop],
8720
+ second = ['x', chart.chartWidth, boxWidth, point.plotX + chart.plotLeft],
8721
+ // The far side is right or bottom
8722
+ preferFarSide = point.ttBelow || (chart.inverted && !point.negative) || (!chart.inverted && point.negative),
8723
+ /**
8724
+ * Handle the preferred dimension. When the preferred dimension is tooltip
8725
+ * on top or bottom of the point, it will look for space there.
8726
+ */
8727
+ firstDimension = function (dim, outerSize, innerSize, point) {
8728
+ var roomLeft = innerSize < point - distance,
8729
+ roomRight = point + distance + innerSize < outerSize,
8730
+ alignedLeft = point - distance - innerSize,
8731
+ alignedRight = point + distance;
8732
+
8733
+ if (preferFarSide && roomRight) {
8734
+ ret[dim] = alignedRight;
8735
+ } else if (!preferFarSide && roomLeft) {
8736
+ ret[dim] = alignedLeft;
8737
+ } else if (roomLeft) {
8738
+ ret[dim] = alignedLeft;
8739
+ } else if (roomRight) {
8740
+ ret[dim] = alignedRight;
8741
+ } else {
8742
+ return false;
8743
+ }
8744
+ },
8745
+ /**
8746
+ * Handle the secondary dimension. If the preferred dimension is tooltip
8747
+ * on top or bottom of the point, the second dimension is to align the tooltip
8748
+ * above the point, trying to align center but allowing left or right
8749
+ * align within the chart box.
8750
+ */
8751
+ secondDimension = function (dim, outerSize, innerSize, point) {
8752
+ // Too close to the edge, return false and swap dimensions
8753
+ if (point < distance || point > outerSize - distance) {
8754
+ return false;
8755
+
8756
+ // Align left/top
8757
+ } else if (point < innerSize / 2) {
8758
+ ret[dim] = 1;
8759
+ // Align right/bottom
8760
+ } else if (point > outerSize - innerSize / 2) {
8761
+ ret[dim] = outerSize - innerSize - 2;
8762
+ // Align center
8763
+ } else {
8764
+ ret[dim] = point - innerSize / 2;
8765
+ }
8766
+ },
8767
+ /**
8768
+ * Swap the dimensions
8769
+ */
8770
+ swap = function (count) {
8771
+ var temp = first;
8772
+ first = second;
8773
+ second = temp;
8774
+ swapped = count;
8775
+ },
8776
+ run = function () {
8777
+ if (firstDimension.apply(0, first) !== false) {
8778
+ if (secondDimension.apply(0, second) === false && !swapped) {
8779
+ swap(true);
8780
+ run();
8781
+ }
8782
+ } else if (!swapped) {
8783
+ swap(true);
8784
+ run();
8785
+ } else {
8786
+ ret.x = ret.y = 0;
8787
+ }
8788
+ };
8785
8789
 
8786
- // It is too far to the left, adjust it
8787
- if (x < 7) {
8788
- x = plotLeft + mathMax(pointX, 0) + distance;
8789
- }
8790
-
8791
- // Test to see if the tooltip is too far to the right,
8792
- // if it is, move it back to be inside and then up to not cover the point.
8793
- if ((x + boxWidth) > (plotLeft + plotWidth)) {
8794
- x -= (x + boxWidth) - (plotLeft + plotWidth);
8795
- y = pointY - boxHeight + plotTop - distance;
8796
- alignedRight = true;
8797
- }
8798
-
8799
- // If it is now above the plot area, align it to the top of the plot area
8800
- if (y < plotTop + 5) {
8801
- y = plotTop + 5;
8802
-
8803
- // If the tooltip is still covering the point, move it below instead
8804
- if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {
8805
- y = pointY + plotTop + distance; // below
8806
- }
8807
- }
8808
-
8809
- // Now if the tooltip is below the chart, move it up. It's better to cover the
8810
- // point than to disappear outside the chart. #834.
8811
- if (y + boxHeight > plotTop + plotHeight) {
8812
- y = mathMax(plotTop, plotTop + plotHeight - boxHeight - distance); // below
8790
+ // Under these conditions, prefer the tooltip on the side of the point
8791
+ if (chart.inverted || this.len > 1) {
8792
+ swap();
8813
8793
  }
8794
+ run();
8795
+
8796
+ return ret;
8814
8797
 
8815
- return {x: x, y: y};
8816
8798
  },
8817
8799
 
8818
8800
  /**
@@ -8892,6 +8874,7 @@ Tooltip.prototype = {
8892
8874
  y: point[0].y
8893
8875
  };
8894
8876
  textConfig.points = pointConfig;
8877
+ this.len = pointConfig.length;
8895
8878
  point = point[0];
8896
8879
 
8897
8880
  // single point tooltip
@@ -8902,6 +8885,7 @@ Tooltip.prototype = {
8902
8885
 
8903
8886
  // register the current series
8904
8887
  currentSeries = point.series;
8888
+ this.distance = pick(currentSeries.tooltipOptions.distance, 16);
8905
8889
 
8906
8890
  // update the inner HTML
8907
8891
  if (text === false) {
@@ -8925,7 +8909,7 @@ Tooltip.prototype = {
8925
8909
  stroke: borderColor
8926
8910
  });
8927
8911
 
8928
- tooltip.updatePosition({ plotX: x, plotY: y });
8912
+ tooltip.updatePosition({ plotX: x, plotY: y, negative: point.negative, ttBelow: point.ttBelow });
8929
8913
 
8930
8914
  this.isHidden = false;
8931
8915
  }
@@ -9044,6 +9028,7 @@ Pointer.prototype = {
9044
9028
  this.zoomY = zoomY = /y/.test(zoomType);
9045
9029
  this.zoomHor = (zoomX && !inverted) || (zoomY && inverted);
9046
9030
  this.zoomVert = (zoomY && !inverted) || (zoomX && inverted);
9031
+ this.hasZoom = zoomX || zoomY;
9047
9032
 
9048
9033
  // Do we need to handle click on a touch device?
9049
9034
  this.runChartClick = chartEvents && !!chartEvents.click;
@@ -9053,6 +9038,7 @@ Pointer.prototype = {
9053
9038
 
9054
9039
  if (Highcharts.Tooltip && options.tooltip.enabled) {
9055
9040
  chart.tooltip = new Tooltip(chart, options.tooltip);
9041
+ this.followTouchMove = options.tooltip.followTouchMove;
9056
9042
  }
9057
9043
 
9058
9044
  this.setDOMEvents();
@@ -9068,7 +9054,7 @@ Pointer.prototype = {
9068
9054
  ePos;
9069
9055
 
9070
9056
  // common IE normalizing
9071
- e = e || win.event;
9057
+ e = e || window.event;
9072
9058
 
9073
9059
  // Framework specific normalizing (#1165)
9074
9060
  e = washMouseEvent(e);
@@ -9078,8 +9064,8 @@ Pointer.prototype = {
9078
9064
  e.target = e.srcElement;
9079
9065
  }
9080
9066
 
9081
- // iOS
9082
- ePos = e.touches ? e.touches.item(0) : e;
9067
+ // iOS (#2757)
9068
+ ePos = e.touches ? (e.touches.length ? e.touches.item(0) : e.changedTouches[0]) : e;
9083
9069
 
9084
9070
  // Get mouse position
9085
9071
  if (!chartPosition) {
@@ -9142,6 +9128,7 @@ Pointer.prototype = {
9142
9128
  chart = pointer.chart,
9143
9129
  series = chart.series,
9144
9130
  tooltip = chart.tooltip,
9131
+ followPointer,
9145
9132
  point,
9146
9133
  points,
9147
9134
  hoverPoint = chart.hoverPoint,
@@ -9184,8 +9171,9 @@ Pointer.prototype = {
9184
9171
  }
9185
9172
  }
9186
9173
 
9187
- // separate tooltip and general mouse events
9188
- if (hoverSeries && hoverSeries.tracker && (!tooltip || !tooltip.followPointer)) { // only use for line-type series with common tracker and while not following the pointer #2584
9174
+ // Separate tooltip and general mouse events
9175
+ followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer;
9176
+ if (hoverSeries && hoverSeries.tracker && !followPointer) { // #2584, #2830
9189
9177
 
9190
9178
  // get the point
9191
9179
  point = hoverSeries.tooltipPoints[index];
@@ -9198,7 +9186,7 @@ Pointer.prototype = {
9198
9186
 
9199
9187
  }
9200
9188
 
9201
- } else if (tooltip && tooltip.followPointer && !tooltip.isHidden) {
9189
+ } else if (tooltip && followPointer && !tooltip.isHidden) {
9202
9190
  anchor = tooltip.getAnchor([{}], e);
9203
9191
  tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] });
9204
9192
  }
@@ -9206,7 +9194,7 @@ Pointer.prototype = {
9206
9194
  // Start the event listener to pick up the tooltip
9207
9195
  if (tooltip && !pointer._onDocumentMouseMove) {
9208
9196
  pointer._onDocumentMouseMove = function (e) {
9209
- if (defined(hoverChartIndex)) {
9197
+ if (charts[hoverChartIndex]) {
9210
9198
  charts[hoverChartIndex].pointer.onDocumentMouseMove(e);
9211
9199
  }
9212
9200
  };
@@ -9418,9 +9406,12 @@ Pointer.prototype = {
9418
9406
  originalEvent: e.originalEvent || e
9419
9407
  },
9420
9408
  selectionBox = this.selectionMarker,
9421
- selectionLeft = selectionBox.x,
9422
- selectionTop = selectionBox.y,
9409
+ selectionLeft = selectionBox.attr ? selectionBox.attr('x') : selectionBox.x,
9410
+ selectionTop = selectionBox.attr ? selectionBox.attr('y') : selectionBox.y,
9411
+ selectionWidth = selectionBox.attr ? selectionBox.attr('width') : selectionBox.width,
9412
+ selectionHeight = selectionBox.attr ? selectionBox.attr('height') : selectionBox.height,
9423
9413
  runZoom;
9414
+
9424
9415
  // a selection has been made
9425
9416
  if (this.hasDragged || hasPinched) {
9426
9417
 
@@ -9429,7 +9420,7 @@ Pointer.prototype = {
9429
9420
  if (axis.zoomEnabled) {
9430
9421
  var horiz = axis.horiz,
9431
9422
  selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop)),
9432
- selectionMax = axis.toValue((horiz ? selectionLeft + selectionBox.width : selectionTop + selectionBox.height));
9423
+ selectionMax = axis.toValue((horiz ? selectionLeft + selectionWidth : selectionTop + selectionHeight));
9433
9424
 
9434
9425
  if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859
9435
9426
  selectionData[axis.coll].push({
@@ -9480,7 +9471,7 @@ Pointer.prototype = {
9480
9471
 
9481
9472
 
9482
9473
  onDocumentMouseUp: function (e) {
9483
- if (defined(hoverChartIndex)) {
9474
+ if (charts[hoverChartIndex]) {
9484
9475
  charts[hoverChartIndex].pointer.drop(e);
9485
9476
  }
9486
9477
  },
@@ -9512,7 +9503,6 @@ Pointer.prototype = {
9512
9503
  chart.pointer.reset();
9513
9504
  chart.pointer.chartPosition = null; // also reset the chart position, used in #149 fix
9514
9505
  }
9515
- hoverChartIndex = null;
9516
9506
  },
9517
9507
 
9518
9508
  // The mousemove, touchmove and touchstart event handler
@@ -9640,8 +9630,9 @@ Pointer.prototype = {
9640
9630
  pointer.onContainerClick(e);
9641
9631
  };
9642
9632
  addEvent(container, 'mouseleave', pointer.onContainerMouseLeave);
9643
- addEvent(doc, 'mouseup', pointer.onDocumentMouseUp);
9644
-
9633
+ if (chartCount === 1) {
9634
+ addEvent(doc, 'mouseup', pointer.onDocumentMouseUp);
9635
+ }
9645
9636
  if (hasTouch) {
9646
9637
  container.ontouchstart = function (e) {
9647
9638
  pointer.onContainerTouchStart(e);
@@ -9649,7 +9640,9 @@ Pointer.prototype = {
9649
9640
  container.ontouchmove = function (e) {
9650
9641
  pointer.onContainerTouchMove(e);
9651
9642
  };
9652
- addEvent(doc, 'touchend', pointer.onDocumentTouchEnd);
9643
+ if (chartCount === 1) {
9644
+ addEvent(doc, 'touchend', pointer.onDocumentTouchEnd);
9645
+ }
9653
9646
  }
9654
9647
 
9655
9648
  },
@@ -9661,9 +9654,11 @@ Pointer.prototype = {
9661
9654
  var prop;
9662
9655
 
9663
9656
  removeEvent(this.chart.container, 'mouseleave', this.onContainerMouseLeave);
9664
- removeEvent(doc, 'mouseup', this.onDocumentMouseUp);
9665
- removeEvent(doc, 'touchend', this.onDocumentTouchEnd);
9666
-
9657
+ if (!chartCount) {
9658
+ removeEvent(doc, 'mouseup', this.onDocumentMouseUp);
9659
+ removeEvent(doc, 'touchend', this.onDocumentTouchEnd);
9660
+ }
9661
+
9667
9662
  // memory and CPU leak
9668
9663
  clearInterval(this.tooltipTimeout);
9669
9664
 
@@ -9680,11 +9675,11 @@ extend(Highcharts.Pointer.prototype, {
9680
9675
  /**
9681
9676
  * Run translation operations
9682
9677
  */
9683
- pinchTranslate: function (zoomHor, zoomVert, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {
9684
- if (zoomHor) {
9678
+ pinchTranslate: function (pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {
9679
+ if (this.zoomHor || this.pinchHor) {
9685
9680
  this.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
9686
9681
  }
9687
- if (zoomVert) {
9682
+ if (this.zoomVert || this.pinchVert) {
9688
9683
  this.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
9689
9684
  }
9690
9685
  },
@@ -9775,13 +9770,11 @@ extend(Highcharts.Pointer.prototype, {
9775
9770
  var self = this,
9776
9771
  chart = self.chart,
9777
9772
  pinchDown = self.pinchDown,
9778
- followTouchMove = chart.tooltip && chart.tooltip.options.followTouchMove,
9773
+ followTouchMove = self.followTouchMove,
9779
9774
  touches = e.touches,
9780
9775
  touchesLength = touches.length,
9781
9776
  lastValidTouch = self.lastValidTouch,
9782
- zoomHor = self.zoomHor || self.pinchHor,
9783
- zoomVert = self.zoomVert || self.pinchVert,
9784
- hasZoom = zoomHor || zoomVert,
9777
+ hasZoom = self.hasZoom,
9785
9778
  selectionMarker = self.selectionMarker,
9786
9779
  transform = {},
9787
9780
  fireClickEvent = touchesLength === 1 && ((self.inClass(e.target, PREFIX + 'tracker') &&
@@ -9833,7 +9826,7 @@ extend(Highcharts.Pointer.prototype, {
9833
9826
  }, chart.plotBox);
9834
9827
  }
9835
9828
 
9836
- self.pinchTranslate(zoomHor, zoomVert, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
9829
+ self.pinchTranslate(pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
9837
9830
 
9838
9831
  self.hasPinched = hasZoom;
9839
9832
 
@@ -9858,11 +9851,6 @@ extend(Highcharts.Pointer.prototype, {
9858
9851
 
9859
9852
  if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
9860
9853
 
9861
- // Prevent the click pseudo event from firing unless it is set in the options
9862
- /*if (!chart.runChartClick) {
9863
- e.preventDefault();
9864
- }*/
9865
-
9866
9854
  // Run mouse events and display tooltip etc
9867
9855
  this.runPointActions(e);
9868
9856
 
@@ -9885,7 +9873,7 @@ extend(Highcharts.Pointer.prototype, {
9885
9873
  },
9886
9874
 
9887
9875
  onDocumentTouchEnd: function (e) {
9888
- if (defined(hoverChartIndex)) {
9876
+ if (charts[hoverChartIndex]) {
9889
9877
  charts[hoverChartIndex].pointer.drop(e);
9890
9878
  }
9891
9879
  }
@@ -9960,17 +9948,21 @@ if (win.PointerEvent || win.MSPointerEvent) {
9960
9948
 
9961
9949
  // Disable default IE actions for pinch and such on chart element
9962
9950
  wrap(Pointer.prototype, 'init', function (proceed, chart, options) {
9963
- css(chart.container, {
9964
- '-ms-touch-action': NONE,
9965
- 'touch-action': NONE
9966
- });
9967
9951
  proceed.call(this, chart, options);
9952
+ if (this.hasZoom || this.followTouchMove) {
9953
+ css(chart.container, {
9954
+ '-ms-touch-action': NONE,
9955
+ 'touch-action': NONE
9956
+ });
9957
+ }
9968
9958
  });
9969
9959
 
9970
9960
  // Add IE specific touch events to chart
9971
9961
  wrap(Pointer.prototype, 'setDOMEvents', function (proceed) {
9972
9962
  proceed.apply(this);
9973
- this.batchMSEvents(addEvent);
9963
+ if (this.hasZoom || this.followTouchMove) {
9964
+ this.batchMSEvents(addEvent);
9965
+ }
9974
9966
  });
9975
9967
  // Destroy MS events also
9976
9968
  wrap(Pointer.prototype, 'destroy', function (proceed) {
@@ -10043,10 +10035,7 @@ Legend.prototype = {
10043
10035
  textColor = visible ? options.itemStyle.color : hiddenColor,
10044
10036
  symbolColor = visible ? (item.legendColor || item.color || '#CCC') : hiddenColor,
10045
10037
  markerOptions = item.options && item.options.marker,
10046
- symbolAttr = {
10047
- stroke: symbolColor,
10048
- fill: symbolColor
10049
- },
10038
+ symbolAttr = { fill: symbolColor },
10050
10039
  key,
10051
10040
  val;
10052
10041
 
@@ -10061,6 +10050,7 @@ Legend.prototype = {
10061
10050
 
10062
10051
  // Apply marker options
10063
10052
  if (markerOptions && legendSymbol.isMarker) { // #585
10053
+ symbolAttr.stroke = symbolColor;
10064
10054
  markerOptions = item.convertAttribs(markerOptions);
10065
10055
  for (key in markerOptions) {
10066
10056
  val = markerOptions[key];
@@ -10203,7 +10193,7 @@ Legend.prototype = {
10203
10193
  itemStyle = legend.itemStyle,
10204
10194
  itemHiddenStyle = legend.itemHiddenStyle,
10205
10195
  padding = legend.padding,
10206
- itemDistance = horizontal ? pick(options.itemDistance, 8) : 0,
10196
+ itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, // docs
10207
10197
  ltr = !options.rtl,
10208
10198
  itemHeight,
10209
10199
  widthOption = options.width,
@@ -10648,7 +10638,7 @@ var LegendSymbolMixin = Highcharts.LegendSymbolMixin = {
10648
10638
  legend.baseline - 5 - (symbolHeight / 2),
10649
10639
  legend.symbolWidth,
10650
10640
  symbolHeight,
10651
- pick(legend.options.symbolRadius, 2)
10641
+ legend.options.symbolRadius || 0
10652
10642
  ).attr({
10653
10643
  zIndex: 3
10654
10644
  }).add(item.legendGroup);
@@ -10695,7 +10685,7 @@ var LegendSymbolMixin = Highcharts.LegendSymbolMixin = {
10695
10685
  }
10696
10686
 
10697
10687
  // Draw the marker
10698
- if (markerOptions && markerOptions.enabled) {
10688
+ if (markerOptions && markerOptions.enabled !== false) {
10699
10689
  radius = markerOptions.radius;
10700
10690
  this.legendSymbol = legendSymbol = renderer.symbol(
10701
10691
  this.symbol,
@@ -10723,11 +10713,11 @@ if (/Trident\/7\.0/.test(userAgent) || isFirefox) {
10723
10713
  }
10724
10714
  };
10725
10715
 
10726
- if (legend.chart.renderer.forExport) {
10727
- runPositionItem();
10728
- } else {
10729
- setTimeout(runPositionItem);
10730
- }
10716
+ // Do it now, for export and to get checkbox placement
10717
+ runPositionItem();
10718
+
10719
+ // Do it after to work around the core issue
10720
+ setTimeout(runPositionItem);
10731
10721
  });
10732
10722
  }
10733
10723
  /**
@@ -10810,6 +10800,7 @@ Chart.prototype = {
10810
10800
  // Add the chart to the global lookup
10811
10801
  chart.index = charts.length;
10812
10802
  charts.push(chart);
10803
+ chartCount++;
10813
10804
 
10814
10805
  // Set up auto resize
10815
10806
  if (optionsChart.reflow !== false) {
@@ -11221,11 +11212,6 @@ Chart.prototype = {
11221
11212
 
11222
11213
  if (!titleOptions.floating && !titleOptions.verticalAlign) {
11223
11214
  titleOffset = title.getBBox().height;
11224
-
11225
- // Adjust for browser consistency + backwards compat after #776 fix
11226
- if (titleOffset >= 18 && titleOffset <= 25) {
11227
- titleOffset = 15;
11228
- }
11229
11215
  }
11230
11216
  }
11231
11217
  if (subtitle) {
@@ -11414,7 +11400,7 @@ Chart.prototype = {
11414
11400
  legend = chart.legend,
11415
11401
  margin = chart.margin,
11416
11402
  legendOptions = chart.options.legend,
11417
- legendMargin = pick(legendOptions.margin, 10),
11403
+ legendMargin = pick(legendOptions.margin, 20),
11418
11404
  legendX = legendOptions.x,
11419
11405
  legendY = legendOptions.y,
11420
11406
  align = legendOptions.align,
@@ -11604,6 +11590,7 @@ Chart.prototype = {
11604
11590
  chart.isDirtyLegend = true; // force legend redraw
11605
11591
  chart.isDirtyBox = true; // force redraw of plot and chart border
11606
11592
 
11593
+ chart.layOutTitles(); // #2857
11607
11594
  chart.getMargins();
11608
11595
 
11609
11596
  chart.redraw(animation);
@@ -12018,6 +12005,7 @@ Chart.prototype = {
12018
12005
 
12019
12006
  // Delete the chart from charts lookup array
12020
12007
  charts[chart.index] = UNDEFINED;
12008
+ chartCount--;
12021
12009
  chart.renderTo.removeAttribute('data-highcharts-chart');
12022
12010
 
12023
12011
  // remove events
@@ -12416,6 +12404,34 @@ Point.prototype = {
12416
12404
  point: this,
12417
12405
  series: this.series
12418
12406
  });
12407
+ },
12408
+
12409
+ /**
12410
+ * Fire an event on the Point object. Must not be renamed to fireEvent, as this
12411
+ * causes a name clash in MooTools
12412
+ * @param {String} eventType
12413
+ * @param {Object} eventArgs Additional event arguments
12414
+ * @param {Function} defaultFunction Default event handler
12415
+ */
12416
+ firePointEvent: function (eventType, eventArgs, defaultFunction) {
12417
+ var point = this,
12418
+ series = this.series,
12419
+ seriesOptions = series.options;
12420
+
12421
+ // load event handlers on demand to save time on mouseover/out
12422
+ if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
12423
+ this.importEvents();
12424
+ }
12425
+
12426
+ // add default handler if in selection mode
12427
+ if (eventType === 'click' && seriesOptions.allowPointSelect) {
12428
+ defaultFunction = function (event) {
12429
+ // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
12430
+ point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
12431
+ };
12432
+ }
12433
+
12434
+ fireEvent(this, eventType, eventArgs, defaultFunction);
12419
12435
  }
12420
12436
  };/**
12421
12437
  * @classDescription The base function which all other series types inherit from. The data in the series is stored
@@ -12904,7 +12920,10 @@ Series.prototype = {
12904
12920
  i, // loop variable
12905
12921
  options = series.options,
12906
12922
  cropThreshold = options.cropThreshold,
12907
- isCartesian = series.isCartesian;
12923
+ activePointCount = 0,
12924
+ isCartesian = series.isCartesian,
12925
+ min,
12926
+ max;
12908
12927
 
12909
12928
  // If the series data or axes haven't changed, don't go through this. Return false to pass
12910
12929
  // the message on to override methods like in data grouping.
@@ -12915,8 +12934,9 @@ Series.prototype = {
12915
12934
 
12916
12935
  // optionally filter out points outside the plot area
12917
12936
  if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) {
12918
- var min = xAxis.min,
12919
- max = xAxis.max;
12937
+
12938
+ min = xAxis.min;
12939
+ max = xAxis.max;
12920
12940
 
12921
12941
  // it's outside current extremes
12922
12942
  if (processedXData[dataLength - 1] < min || processedXData[0] > max) {
@@ -12930,6 +12950,7 @@ Series.prototype = {
12930
12950
  processedYData = croppedData.yData;
12931
12951
  cropStart = croppedData.start;
12932
12952
  cropped = true;
12953
+ activePointCount = processedXData.length;
12933
12954
  }
12934
12955
  }
12935
12956
 
@@ -12937,6 +12958,10 @@ Series.prototype = {
12937
12958
  // Find the closest distance between processed points
12938
12959
  for (i = processedXData.length - 1; i >= 0; i--) {
12939
12960
  distance = processedXData[i] - processedXData[i - 1];
12961
+
12962
+ if (!cropped && processedXData[i] > min && processedXData[i] < max) {
12963
+ activePointCount++;
12964
+ }
12940
12965
  if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {
12941
12966
  closestPointRange = distance;
12942
12967
 
@@ -12952,6 +12977,7 @@ Series.prototype = {
12952
12977
  series.cropStart = cropStart;
12953
12978
  series.processedXData = processedXData;
12954
12979
  series.processedYData = processedYData;
12980
+ series.activePointCount = activePointCount;
12955
12981
 
12956
12982
  if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
12957
12983
  series.pointRange = closestPointRange || 1;
@@ -13156,7 +13182,7 @@ Series.prototype = {
13156
13182
  if (stacking && series.visible && stack && stack[xValue]) {
13157
13183
 
13158
13184
  pointStack = stack[xValue];
13159
- stackValues = pointStack.points[series.index];
13185
+ stackValues = pointStack.points[series.index + ',' + i];
13160
13186
  yBottom = stackValues[0];
13161
13187
  yValue = stackValues[1];
13162
13188
 
@@ -13218,7 +13244,7 @@ Series.prototype = {
13218
13244
  clipRect,
13219
13245
  markerClipRect,
13220
13246
  animation = series.options.animation,
13221
- clipBox = chart.clipBox,
13247
+ clipBox = series.clipBox || chart.clipBox,
13222
13248
  inverted = chart.inverted,
13223
13249
  sharedClipKey;
13224
13250
 
@@ -13226,7 +13252,7 @@ Series.prototype = {
13226
13252
  if (animation && !isObject(animation)) {
13227
13253
  animation = defaultPlotOptions[series.type].animation;
13228
13254
  }
13229
- sharedClipKey = '_sharedClip' + animation.duration + animation.easing;
13255
+ sharedClipKey = ['_sharedClip', animation.duration, animation.easing, clipBox.height].join(',');
13230
13256
 
13231
13257
  // Initialize the animation. Set up the clipping rectangle.
13232
13258
  if (init) {
@@ -13257,6 +13283,8 @@ Series.prototype = {
13257
13283
  clipRect.animate({
13258
13284
  width: chart.plotSizeX
13259
13285
  }, animation);
13286
+ }
13287
+ if (chart[sharedClipKey + 'm']) {
13260
13288
  chart[sharedClipKey + 'm'].animate({
13261
13289
  width: chart.plotSizeX + 99
13262
13290
  }, animation);
@@ -13264,12 +13292,7 @@ Series.prototype = {
13264
13292
 
13265
13293
  // Delete this function to allow it only once
13266
13294
  series.animate = null;
13267
-
13268
- // Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option
13269
- // which should be available to the user).
13270
- series.animationTimeout = setTimeout(function () {
13271
- series.afterAnimate();
13272
- }, animation.duration);
13295
+
13273
13296
  }
13274
13297
  },
13275
13298
 
@@ -13279,18 +13302,27 @@ Series.prototype = {
13279
13302
  afterAnimate: function () {
13280
13303
  var chart = this.chart,
13281
13304
  sharedClipKey = this.sharedClipKey,
13282
- group = this.group;
13305
+ group = this.group,
13306
+ clipBox = this.clipBox;
13283
13307
 
13284
13308
  if (group && this.options.clip !== false) {
13285
- group.clip(chart.clipRect);
13309
+ if (!sharedClipKey || !clipBox) {
13310
+ group.clip(clipBox ? chart.renderer.clipRect(clipBox) : chart.clipRect);
13311
+ }
13286
13312
  this.markerGroup.clip(); // no clip
13287
13313
  }
13288
13314
 
13315
+ fireEvent(this, 'afterAnimate');
13316
+
13289
13317
  // Remove the shared clipping rectancgle when all series are shown
13290
13318
  setTimeout(function () {
13291
13319
  if (sharedClipKey && chart[sharedClipKey]) {
13292
- chart[sharedClipKey] = chart[sharedClipKey].destroy();
13293
- chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
13320
+ if (!clipBox) {
13321
+ chart[sharedClipKey] = chart[sharedClipKey].destroy();
13322
+ }
13323
+ if (chart[sharedClipKey + 'm']) {
13324
+ chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
13325
+ }
13294
13326
  }
13295
13327
  }, 100);
13296
13328
  },
@@ -13317,9 +13349,13 @@ Series.prototype = {
13317
13349
  pointMarkerOptions,
13318
13350
  enabled,
13319
13351
  isInside,
13320
- markerGroup = series.markerGroup;
13352
+ markerGroup = series.markerGroup,
13353
+ globallyEnabled = pick(
13354
+ seriesMarkerOptions.enabled,
13355
+ series.activePointCount < (0.5 * series.xAxis.len / seriesMarkerOptions.radius)
13356
+ );
13321
13357
 
13322
- if (seriesMarkerOptions.enabled || series._hasPointMarkers) {
13358
+ if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) {
13323
13359
 
13324
13360
  i = points.length;
13325
13361
  while (i--) {
@@ -13328,7 +13364,7 @@ Series.prototype = {
13328
13364
  plotY = point.plotY;
13329
13365
  graphic = point.graphic;
13330
13366
  pointMarkerOptions = point.marker || {};
13331
- enabled = (seriesMarkerOptions.enabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled;
13367
+ enabled = (globallyEnabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled;
13332
13368
  isInside = chart.isInsidePlot(mathRound(plotX), plotY, chart.inverted); // #1858
13333
13369
 
13334
13370
  // only draw the point if y is defined
@@ -13341,10 +13377,7 @@ Series.prototype = {
13341
13377
  isImage = symbol.indexOf('url') === 0;
13342
13378
 
13343
13379
  if (graphic) { // update
13344
- graphic
13345
- .attr({ // Since the marker group isn't clipped, each individual marker must be toggled
13346
- visibility: isInside ? 'inherit' : HIDDEN
13347
- })
13380
+ graphic[isInside ? 'show' : 'hide'](true) // Since the marker group isn't clipped, each individual marker must be toggled
13348
13381
  .animate(extend({
13349
13382
  x: plotX - radius,
13350
13383
  y: plotY - radius
@@ -13900,9 +13933,18 @@ Series.prototype = {
13900
13933
  * Get the translation and scale for the plot area of this series
13901
13934
  */
13902
13935
  getPlotBox: function () {
13936
+ var chart = this.chart,
13937
+ xAxis = this.xAxis,
13938
+ yAxis = this.yAxis;
13939
+
13940
+ // Swap axes for inverted (#2339)
13941
+ if (chart.inverted) {
13942
+ xAxis = yAxis;
13943
+ yAxis = this.xAxis;
13944
+ }
13903
13945
  return {
13904
- translateX: this.xAxis ? this.xAxis.left : this.chart.plotLeft,
13905
- translateY: this.yAxis ? this.yAxis.top : this.chart.plotTop,
13946
+ translateX: xAxis ? xAxis.left : chart.plotLeft,
13947
+ translateY: yAxis ? yAxis.top : chart.plotTop,
13906
13948
  scaleX: 1, // #1623
13907
13949
  scaleY: 1
13908
13950
  };
@@ -13917,9 +13959,9 @@ Series.prototype = {
13917
13959
  group,
13918
13960
  options = series.options,
13919
13961
  animation = options.animation,
13920
- doAnimation = animation && !!series.animate &&
13921
- chart.renderer.isSVG, // this animation doesn't work in IE8 quirks when the group div is hidden,
13922
- // and looks bad in other oldIE
13962
+ // Animation doesn't work in IE8 quirks when the group div is hidden,
13963
+ // and looks bad in other oldIE
13964
+ animDuration = (animation && !!series.animate && chart.renderer.isSVG && pick(animation.duration, 500)) || 0,
13923
13965
  visibility = series.visible ? VISIBLE : HIDDEN,
13924
13966
  zIndex = options.zIndex,
13925
13967
  hasRendered = series.hasRendered,
@@ -13943,7 +13985,7 @@ Series.prototype = {
13943
13985
  );
13944
13986
 
13945
13987
  // initiate the animation
13946
- if (doAnimation) {
13988
+ if (animDuration) {
13947
13989
  series.animate(true);
13948
13990
  }
13949
13991
 
@@ -13986,10 +14028,20 @@ Series.prototype = {
13986
14028
  }
13987
14029
 
13988
14030
  // Run the animation
13989
- if (doAnimation) {
14031
+ if (animDuration) {
13990
14032
  series.animate();
13991
- } else if (!hasRendered) {
13992
- series.afterAnimate();
14033
+ }
14034
+
14035
+ // Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option
14036
+ // which should be available to the user).
14037
+ if (!hasRendered) {
14038
+ if (animDuration) {
14039
+ series.animationTimeout = setTimeout(function () {
14040
+ series.afterAnimate();
14041
+ }, animDuration);
14042
+ } else {
14043
+ series.afterAnimate();
14044
+ }
13993
14045
  }
13994
14046
 
13995
14047
  series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
@@ -14024,7 +14076,9 @@ Series.prototype = {
14024
14076
  }
14025
14077
 
14026
14078
  series.translate();
14027
- series.setTooltipPoints(true);
14079
+ if (series.setTooltipPoints) {
14080
+ series.setTooltipPoints(true);
14081
+ }
14028
14082
  series.render();
14029
14083
 
14030
14084
  if (wasDirtyData) {
@@ -14036,7 +14090,7 @@ Series.prototype = {
14036
14090
  /**
14037
14091
  * The class for stack items
14038
14092
  */
14039
- function StackItem(axis, options, isNegative, x, stackOption, stacking) {
14093
+ function StackItem(axis, options, isNegative, x, stackOption) {
14040
14094
 
14041
14095
  var inverted = axis.chart.inverted;
14042
14096
 
@@ -14054,12 +14108,11 @@ function StackItem(axis, options, isNegative, x, stackOption, stacking) {
14054
14108
  // Initialize total value
14055
14109
  this.total = null;
14056
14110
 
14057
- // This will keep each points' extremes stored by series.index
14111
+ // This will keep each points' extremes stored by series.index and point index
14058
14112
  this.points = {};
14059
14113
 
14060
14114
  // Save the stack option on the series configuration object, and whether to treat it as percent
14061
14115
  this.stack = stackOption;
14062
- this.percent = stacking === 'percent';
14063
14116
 
14064
14117
  // The align options and text align varies on whether the stack is negative and
14065
14118
  // if the chart is inverted or not.
@@ -14095,7 +14148,7 @@ StackItem.prototype = {
14095
14148
  // Create new label
14096
14149
  } else {
14097
14150
  this.label =
14098
- this.axis.chart.renderer.text(str, 0, 0, options.useHTML) // dummy positions, actual position updated with setOffset method in columnseries
14151
+ this.axis.chart.renderer.text(str, null, null, options.useHTML) // dummy positions, actual position updated with setOffset method in columnseries
14099
14152
  .css(options.style) // apply style
14100
14153
  .attr({
14101
14154
  align: this.textAlign, // fix the text-anchor
@@ -14115,7 +14168,7 @@ StackItem.prototype = {
14115
14168
  chart = axis.chart,
14116
14169
  inverted = chart.inverted,
14117
14170
  neg = this.isNegative, // special treatment is needed for negative stacks
14118
- y = axis.translate(this.percent ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates
14171
+ y = axis.translate(axis.usePercentage ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates
14119
14172
  yZero = axis.translate(0), // stack origin
14120
14173
  h = mathAbs(y - yZero), // stack height
14121
14174
  x = chart.xAxis[0].translate(this.x) + xOffset, // stack x position
@@ -14227,6 +14280,7 @@ Series.prototype.setStackedPoints = function () {
14227
14280
  stack,
14228
14281
  other,
14229
14282
  key,
14283
+ pointKey,
14230
14284
  i,
14231
14285
  x,
14232
14286
  y;
@@ -14235,6 +14289,7 @@ Series.prototype.setStackedPoints = function () {
14235
14289
  for (i = 0; i < yDataLength; i++) {
14236
14290
  x = xData[i];
14237
14291
  y = yData[i];
14292
+ pointKey = series.index + ',' + i;
14238
14293
 
14239
14294
  // Read stacked values into a stack based on the x value,
14240
14295
  // the sign of y and the stack key. Stacking is also handled for null values (#739)
@@ -14252,13 +14307,13 @@ Series.prototype.setStackedPoints = function () {
14252
14307
  stacks[key][x] = oldStacks[key][x];
14253
14308
  stacks[key][x].total = null;
14254
14309
  } else {
14255
- stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption, stacking);
14310
+ stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption);
14256
14311
  }
14257
14312
  }
14258
14313
 
14259
14314
  // If the StackItem doesn't exist, create it first
14260
14315
  stack = stacks[key][x];
14261
- stack.points[series.index] = [stack.cum || 0];
14316
+ stack.points[pointKey] = [stack.cum || 0];
14262
14317
 
14263
14318
  // Add value to the stack total
14264
14319
  if (stacking === 'percent') {
@@ -14279,7 +14334,7 @@ Series.prototype.setStackedPoints = function () {
14279
14334
 
14280
14335
  stack.cum = (stack.cum || 0) + (y || 0);
14281
14336
 
14282
- stack.points[series.index].push(stack.cum);
14337
+ stack.points[pointKey].push(stack.cum);
14283
14338
  stackedYData[i] = stack.cum;
14284
14339
 
14285
14340
  }
@@ -14313,7 +14368,7 @@ Series.prototype.setPercentStacks = function () {
14313
14368
  while (i--) {
14314
14369
  x = processedXData[i];
14315
14370
  stack = stacks[key] && stacks[key][x];
14316
- pointExtremes = stack && stack.points[series.index];
14371
+ pointExtremes = stack && stack.points[series.index + ',' + i];
14317
14372
  if (pointExtremes) {
14318
14373
  totalFactor = stack.total ? 100 / stack.total : 0;
14319
14374
  pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor); // Y bottom value
@@ -14745,7 +14800,7 @@ extend(Axis.prototype, {
14745
14800
  newOptions = chart.options[this.coll][this.options.index] = merge(this.userOptions, newOptions);
14746
14801
 
14747
14802
  this.destroy(true);
14748
- this._addedPlotLB = this.userMin = this.userMax = UNDEFINED; // #1611, #2306
14803
+ this._addedPlotLB = UNDEFINED; // #1611, #2887
14749
14804
 
14750
14805
  this.init(chart, extend(newOptions, { events: UNDEFINED }));
14751
14806
 
@@ -15160,7 +15215,7 @@ seriesTypes.areaspline = AreaSplineSeries;
15160
15215
  */
15161
15216
  defaultPlotOptions.column = merge(defaultSeriesOptions, {
15162
15217
  borderColor: '#FFFFFF',
15163
- borderWidth: 1,
15218
+ //borderWidth: 1,
15164
15219
  borderRadius: 0,
15165
15220
  //colorByPoint: undefined,
15166
15221
  groupPadding: 0.2,
@@ -15174,7 +15229,8 @@ defaultPlotOptions.column = merge(defaultSeriesOptions, {
15174
15229
  states: {
15175
15230
  hover: {
15176
15231
  brightness: 0.1,
15177
- shadow: false
15232
+ shadow: false,
15233
+ halo: false
15178
15234
  },
15179
15235
  select: {
15180
15236
  color: '#C0C0C0',
@@ -15188,6 +15244,9 @@ defaultPlotOptions.column = merge(defaultSeriesOptions, {
15188
15244
  y: null
15189
15245
  },
15190
15246
  stickyTracking: false,
15247
+ tooltip: {
15248
+ distance: 6
15249
+ },
15191
15250
  threshold: 0
15192
15251
  });
15193
15252
 
@@ -15198,7 +15257,6 @@ var ColumnSeries = extendClass(Series, {
15198
15257
  type: 'column',
15199
15258
  pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
15200
15259
  stroke: 'borderColor',
15201
- 'stroke-width': 'borderWidth',
15202
15260
  fill: 'color',
15203
15261
  r: 'borderRadius'
15204
15262
  },
@@ -15301,7 +15359,10 @@ var ColumnSeries = extendClass(Series, {
15301
15359
  var series = this,
15302
15360
  chart = series.chart,
15303
15361
  options = series.options,
15304
- borderWidth = options.borderWidth,
15362
+ borderWidth = series.borderWidth = pick(
15363
+ options.borderWidth,
15364
+ series.activePointCount > 0.5 * series.xAxis.len ? 0 : 1
15365
+ ),
15305
15366
  yAxis = series.yAxis,
15306
15367
  threshold = options.threshold,
15307
15368
  translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold),
@@ -15347,6 +15408,9 @@ var ColumnSeries = extendClass(Series, {
15347
15408
  point.barX = barX;
15348
15409
  point.pointWidth = pointWidth;
15349
15410
 
15411
+ // Fix the tooltip on center of grouped columns (#1216)
15412
+ point.tooltipPos = chart.inverted ? [yAxis.len - plotY, series.xAxis.len - barX - barW / 2] : [barX + barW / 2, plotY];
15413
+
15350
15414
  // Round off to obtain crisp edges
15351
15415
  fromLeft = mathAbs(barX) < 0.5;
15352
15416
  right = mathRound(barX + barW) + xCrisp;
@@ -15404,7 +15468,9 @@ var ColumnSeries = extendClass(Series, {
15404
15468
  options = series.options,
15405
15469
  renderer = chart.renderer,
15406
15470
  animationLimit = options.animationLimit || 250,
15407
- shapeArgs;
15471
+ shapeArgs,
15472
+ pointAttr,
15473
+ borderAttr;
15408
15474
 
15409
15475
  // draw the columns
15410
15476
  each(series.points, function (point) {
@@ -15413,14 +15479,18 @@ var ColumnSeries = extendClass(Series, {
15413
15479
 
15414
15480
  if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
15415
15481
  shapeArgs = point.shapeArgs;
15416
-
15482
+ borderAttr = defined(series.borderWidth) ? {
15483
+ 'stroke-width': series.borderWidth
15484
+ } : {};
15485
+ pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] || series.pointAttr[NORMAL_STATE];
15417
15486
  if (graphic) { // update
15418
15487
  stop(graphic);
15419
- graphic[series.points.length < animationLimit ? 'animate' : 'attr'](merge(shapeArgs));
15488
+ graphic.attr(borderAttr)[chart.pointCount < animationLimit ? 'animate' : 'attr'](merge(shapeArgs));
15420
15489
 
15421
15490
  } else {
15422
15491
  point.graphic = graphic = renderer[point.shapeType](shapeArgs)
15423
- .attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])
15492
+ .attr(pointAttr)
15493
+ .attr(borderAttr)
15424
15494
  .add(series.group)
15425
15495
  .shadow(options.shadow, null, options.stacking && !options.borderRadius);
15426
15496
  }
@@ -15506,9 +15576,8 @@ seriesTypes.bar = BarSeries;
15506
15576
  defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
15507
15577
  lineWidth: 0,
15508
15578
  tooltip: {
15509
- headerFormat: '<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',
15510
- pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>',
15511
- followPointer: true
15579
+ headerFormat: '<span style="color:{series.color}">\u25CF</span> <span style="font-size: 10px;"> {series.name}</span><br/>', // docs
15580
+ pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
15512
15581
  },
15513
15582
  stickyTracking: false
15514
15583
  });
@@ -15549,9 +15618,7 @@ defaultPlotOptions.pie = merge(defaultSeriesOptions, {
15549
15618
  // connectorPadding: 5,
15550
15619
  distance: 30,
15551
15620
  enabled: true,
15552
- formatter: function () {
15553
- return this.point.name;
15554
- }
15621
+ format: '{point.name}'
15555
15622
  // softConnector: true,
15556
15623
  //y: 0
15557
15624
  },
@@ -15672,6 +15739,17 @@ var PiePoint = extendClass(Point, {
15672
15739
  point.shadowGroup.animate(translation);
15673
15740
  }
15674
15741
 
15742
+ },
15743
+
15744
+ haloPath: function (size) {
15745
+ var shapeArgs = this.shapeArgs,
15746
+ chart = this.series.chart;
15747
+
15748
+ return this.series.chart.renderer.symbols.arc(chart.plotLeft + shapeArgs.x, chart.plotTop + shapeArgs.y, shapeArgs.r + size, shapeArgs.r + size, {
15749
+ innerR: this.shapeArgs.r,
15750
+ start: shapeArgs.start,
15751
+ end: shapeArgs.end
15752
+ });
15675
15753
  }
15676
15754
  });
15677
15755
 
@@ -16016,10 +16094,17 @@ Series.prototype.drawDataLabels = function () {
16016
16094
  dataLabelsGroup = series.plotGroup(
16017
16095
  'dataLabelsGroup',
16018
16096
  'data-labels',
16019
- series.visible ? VISIBLE : HIDDEN,
16097
+ HIDDEN,
16020
16098
  options.zIndex || 6
16021
16099
  );
16022
16100
 
16101
+ if (!series.hasRendered && pick(options.defer, true)) {
16102
+ dataLabelsGroup.attr({ opacity: 0 });
16103
+ addEvent(series, 'afterAnimate', function () {
16104
+ series.dataLabelsGroup.show()[seriesOptions.animation ? 'animate' : 'attr']({ opacity: 1 }, { duration: 200 });
16105
+ });
16106
+ }
16107
+
16023
16108
  // Make the labels for each point
16024
16109
  generalOptions = options;
16025
16110
  each(points, function (point) {
@@ -16534,7 +16619,7 @@ if (seriesTypes.pie) {
16534
16619
  visibility: visibility
16535
16620
  //zIndex: 0 // #2722 (reversed)
16536
16621
  })
16537
- .add(series.group);
16622
+ .add(series.dataLabelsGroup);
16538
16623
  }
16539
16624
  } else if (connector) {
16540
16625
  point.connector = connector.destroy();
@@ -16718,26 +16803,26 @@ var TrackerMixin = Highcharts.TrackerMixin = {
16718
16803
  // Add reference to the point
16719
16804
  each(series.points, function (point) {
16720
16805
  if (point.graphic) {
16721
- point.graphic.element.point = point;
16806
+ point.graphic.element.point = point;
16722
16807
  }
16723
16808
  if (point.dataLabel) {
16724
- point.dataLabel.element.point = point;
16809
+ point.dataLabel.element.point = point;
16725
16810
  }
16726
16811
  });
16727
16812
 
16728
16813
  // Add the event listeners, we need to do this only once
16729
16814
  if (!series._hasTracking) {
16730
16815
  each(series.trackerGroups, function (key) {
16731
- if (series[key]) { // we don't always have dataLabelsGroup
16732
- series[key]
16733
- .addClass(PREFIX + 'tracker')
16734
- .on('mouseover', onMouseOver)
16735
- .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
16736
- .css(css);
16737
- if (hasTouch) {
16738
- series[key].on('touchstart', onMouseOver);
16816
+ if (series[key]) { // we don't always have dataLabelsGroup
16817
+ series[key]
16818
+ .addClass(PREFIX + 'tracker')
16819
+ .on('mouseover', onMouseOver)
16820
+ .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
16821
+ .css(css);
16822
+ if (hasTouch) {
16823
+ series[key].on('touchstart', onMouseOver);
16824
+ }
16739
16825
  }
16740
- }
16741
16826
  });
16742
16827
  series._hasTracking = true;
16743
16828
  }
@@ -16856,6 +16941,10 @@ if (seriesTypes.scatter) {
16856
16941
  ScatterSeries.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
16857
16942
  }
16858
16943
 
16944
+ if (seriesTypes.gauge) {
16945
+ seriesTypes.gauge.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
16946
+ }
16947
+
16859
16948
  /*
16860
16949
  * Extend Legend for item events
16861
16950
  */
@@ -17127,33 +17216,6 @@ extend(Point.prototype, {
17127
17216
  }
17128
17217
  },
17129
17218
 
17130
- /**
17131
- * Fire an event on the Point object. Must not be renamed to fireEvent, as this
17132
- * causes a name clash in MooTools
17133
- * @param {String} eventType
17134
- * @param {Object} eventArgs Additional event arguments
17135
- * @param {Function} defaultFunction Default event handler
17136
- */
17137
- firePointEvent: function (eventType, eventArgs, defaultFunction) {
17138
- var point = this,
17139
- series = this.series,
17140
- seriesOptions = series.options;
17141
-
17142
- // load event handlers on demand to save time on mouseover/out
17143
- if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
17144
- this.importEvents();
17145
- }
17146
-
17147
- // add default handler if in selection mode
17148
- if (eventType === 'click' && seriesOptions.allowPointSelect) {
17149
- defaultFunction = function (event) {
17150
- // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
17151
- point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
17152
- };
17153
- }
17154
-
17155
- fireEvent(this, eventType, eventArgs, defaultFunction);
17156
- },
17157
17219
  /**
17158
17220
  * Import events from the series' and point's options. Only do it on
17159
17221
  * demand, to save processing time on hovering.
@@ -17193,11 +17255,13 @@ extend(Point.prototype, {
17193
17255
  pointMarker = point.marker || {},
17194
17256
  chart = series.chart,
17195
17257
  radius,
17258
+ halo = series.halo,
17259
+ haloOptions,
17196
17260
  newSymbol,
17197
- pointAttr = point.pointAttr;
17261
+ pointAttr;
17198
17262
 
17199
17263
  state = state || NORMAL_STATE; // empty string
17200
- move = move && stateMarkerGraphic;
17264
+ pointAttr = point.pointAttr[state] || series.pointAttr[state];
17201
17265
 
17202
17266
  if (
17203
17267
  // already has this state
@@ -17207,7 +17271,7 @@ extend(Point.prototype, {
17207
17271
  // series' state options is disabled
17208
17272
  (stateOptions[state] && stateOptions[state].enabled === false) ||
17209
17273
  // general point marker's state options is disabled
17210
- (state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled))) ||
17274
+ (state && (stateDisabled || (normalDisabled && markerStateOptions.enabled === false))) ||
17211
17275
  // individual point marker's state options is disabled
17212
17276
  (state && pointMarker.states && pointMarker.states[state] && pointMarker.states[state].enabled === false) // #1610
17213
17277
 
@@ -17215,12 +17279,11 @@ extend(Point.prototype, {
17215
17279
  return;
17216
17280
  }
17217
17281
 
17218
-
17219
17282
  // apply hover styles to the existing point
17220
17283
  if (point.graphic) {
17221
- radius = markerOptions && point.graphic.symbolName && pointAttr[state].r;
17284
+ radius = markerOptions && point.graphic.symbolName && pointAttr.r;
17222
17285
  point.graphic.attr(merge(
17223
- pointAttr[state],
17286
+ pointAttr,
17224
17287
  radius ? { // new symbol attributes (#507, #612)
17225
17288
  x: plotX - radius,
17226
17289
  y: plotY - radius,
@@ -17228,6 +17291,11 @@ extend(Point.prototype, {
17228
17291
  height: 2 * radius
17229
17292
  } : {}
17230
17293
  ));
17294
+
17295
+ // Zooming in from a range with no markers to a range with markers
17296
+ if (stateMarkerGraphic) {
17297
+ stateMarkerGraphic.hide();
17298
+ }
17231
17299
  } else {
17232
17300
  // if a graphic is not applied to each point in the normal state, create a shared
17233
17301
  // graphic for the hover state
@@ -17243,16 +17311,18 @@ extend(Point.prototype, {
17243
17311
 
17244
17312
  // Add a new state marker graphic
17245
17313
  if (!stateMarkerGraphic) {
17246
- series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
17247
- newSymbol,
17248
- plotX - radius,
17249
- plotY - radius,
17250
- 2 * radius,
17251
- 2 * radius
17252
- )
17253
- .attr(pointAttr[state])
17254
- .add(series.markerGroup);
17255
- stateMarkerGraphic.currentSymbol = newSymbol;
17314
+ if (newSymbol) {
17315
+ series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
17316
+ newSymbol,
17317
+ plotX - radius,
17318
+ plotY - radius,
17319
+ 2 * radius,
17320
+ 2 * radius
17321
+ )
17322
+ .attr(pointAttr)
17323
+ .add(series.markerGroup);
17324
+ stateMarkerGraphic.currentSymbol = newSymbol;
17325
+ }
17256
17326
 
17257
17327
  // Move the existing graphic
17258
17328
  } else {
@@ -17268,7 +17338,32 @@ extend(Point.prototype, {
17268
17338
  }
17269
17339
  }
17270
17340
 
17341
+ // Show me your halo
17342
+ haloOptions = stateOptions[state] && stateOptions[state].halo;
17343
+ if (haloOptions && haloOptions.size) {
17344
+ if (!halo) {
17345
+ series.halo = halo = chart.renderer.path()
17346
+ .add(series.seriesGroup);
17347
+ }
17348
+ halo.attr(extend({
17349
+ fill: Color(point.color || series.color).setOpacity(haloOptions.opacity).get()
17350
+ }, haloOptions.attributes))[move ? 'animate' : 'attr']({
17351
+ d: point.haloPath(haloOptions.size)
17352
+ });
17353
+ } else if (halo) {
17354
+ halo.attr({ d: [] });
17355
+ }
17356
+
17271
17357
  point.state = state;
17358
+ },
17359
+ haloPath: function (size) {
17360
+ var chart = this.series.chart;
17361
+ return chart.renderer.symbols.circle(
17362
+ chart.plotLeft + this.plotX - size,
17363
+ chart.plotTop + this.plotY - size,
17364
+ size * 2,
17365
+ size * 2
17366
+ );
17272
17367
  }
17273
17368
  });
17274
17369