highcharts-rails 3.0.10 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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